diff --git a/components/pdf/PDFViewer.tsx b/components/pdf/PDFViewer.tsx index 96839c7..16a9581 100644 --- a/components/pdf/PDFViewer.tsx +++ b/components/pdf/PDFViewer.tsx @@ -364,18 +364,28 @@ function selectionTextWithin(kept: DOMRect[], range: Range): string { * ผู้ใช้ศาลไม่คุ้นไอคอนล้วน โดยเฉพาะจอสัมผัสที่ hover อ่าน title ไม่ได้ — แทน span * ชื่อ tool-active ของ H3 ที่ถอดออก) */ const TOOLS: { key: Tool; label: string; text: string; Icon: LucideIcon }[] = [ - { key: "select", label: "เลือกข้อความ", text: "เลือกข้อความ", Icon: MousePointer2 }, + { key: "select", label: "เลือก", text: "เลือก", Icon: MousePointer2 }, { key: "pan", label: "เลื่อนหน้า (pan)", text: "เลื่อนหน้า", Icon: Hand }, { key: "highlight", label: "มาร์กข้อความ (H)", text: "มาร์กข้อความ", Icon: Highlighter }, { key: "draw", label: "วาดเส้น (D)", text: "วาดเส้น", Icon: Pencil }, { key: "comment", label: "วางคอมเมนต์ (C)", text: "วางคอมเมนต์", Icon: MessageSquarePlus }, { key: "shape", label: "รูปทรง", text: "รูปทรง", Icon: Shapes }, - { key: "erase", label: "ยางลบ (ลบด้วยการลาก)", text: "ยางลบ", Icon: Eraser }, + { key: "erase", label: "ยางลบ (เส้น/รูปทรง/ตรายางของตัวเอง)", text: "ยางลบ", Icon: Eraser }, { key: "redact", label: "ปิดพราง (redaction)", text: "ปิดพราง", Icon: EyeOff }, { key: "textbox", label: "กล่องข้อความ", text: "กล่องข้อความ", Icon: Type }, { key: "stamp", label: "ตรายาง (stamp)", text: "ตรายาง", Icon: Stamp }, ]; +/** แบนเนอร์แนะนำวิธีใช้ tool ที่วางของด้วยคลิกบนหน้า (UX audit 2026-09-22): เดิมเลือก tool + * แล้วจอนิ่ง ไม่มีอะไรบอกขั้นต่อไป (hint เดิมอยู่แค่ empty state ของ sidebar ที่หายทันที + * ที่มีรายการ) — แสดงตลอดตอน tool active ให้ทำหน้าที่ mode indicator ไปพร้อมกัน + * (เงื่อนไข tool เดี่ยว ๆ จึงไม่มีวันซ้อนกับแบนเนอร์ M3/M6/H2 ข้างบน) */ +const PLACE_TOOL_HINTS: Partial> = { + comment: "โหมดวางคอมเมนต์: คลิก (แตะ) ตำแหน่งบนหน้าเพื่อวางกล่องคอมเมนต์", + textbox: "โหมดกล่องข้อความ: คลิก (แตะ) ตำแหน่งบนหน้าเพื่อวางกล่อง แล้วพิมพ์ข้อความ", + stamp: "โหมดตรายาง: คลิก (แตะ) ตำแหน่งบนหน้าเพื่อประทับตรายาง", +}; + /** UX review 2026-09-14 M4: focus ring ตอน keyboard focus — ทั้งโปรเจกต์ไม่เคยมี focus-visible * ปุ่ม toolbar จึงกลายเป็นกด Tab แล้วไม่รู้ focus อยู่ปุ่มไหน * ring เต็มสี + ring-offset คั่นขาว 1px ให้เห็นชัดแม้บนปุ่ม active ที่พื้น primary เอง @@ -383,6 +393,16 @@ const TOOLS: { key: Tool; label: string; text: string; Icon: LucideIcon }[] = [ const FOCUS_RING_CLS = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 focus-visible:z-10"; +/** สเกลที่ทำให้หน้ากระดาษพอดีความกว้างของ main (ตัด padding px-4 ซ้าย-ขวา = 32px — สูตร + * เดิมของ effect fit-width ตอนโหลด ย้ายมาเป็น helper ใช้ร่วมกับปุ่ม % ที่ header ด้วย + * (UX audit 2026-09-22: ผู้ใช้ซูมดูรายละเอียดแล้วไม่มีทางกลับมาดูทั้งหน้า) — คิดจากความ + * กว้าง main ปัจจุบันทุกครั้งเพราะ sidebar/จออาจเปลี่ยนไปแล้ว; ยังไม่รู้ขนาดหน้า = null */ +function fitWidthScale(mainW: number | undefined, pageW: number | undefined): number | null { + const avail = (mainW ?? 0) - 32; + if (!avail || !pageW) return null; + return pageW > avail ? clamp(Math.floor((avail / pageW) * 100) / 100, 0.5, 1) : 1; +} + /** ชนิดที่สร้างเสร็จแล้วเลือกรายการใหม่ให้อัตโนมัติ (กรอบ selection + sidebar ไฮไลต์) * ทุกชนิดยกเว้น COMMENT — เปิด composer พิมพ์ก่อนบันทึก และคลิก pin = เลือกอยู่แล้ว */ const AUTO_SELECT_TYPES = new Set([ @@ -506,7 +526,7 @@ function ReplyBox({

{r.userName} - + {formatThaiDate(r.createdAt, true)}

@@ -628,7 +648,7 @@ function PdfPage({ /** live preview มาร์กระหว่างลากคลุมด้วย tool highlight (null = ไม่มี) */ liveMark: { pageIndex: number; rects: Rect[] } | null; liveMarkStyle: { color: string; style: HighlightStyle; opacity?: number }; - /** selection สีฟ้าสะอาด ๆ ของ tool เลือกข้อความ — กรองแนวเส้นทางลากเหมือน liveMark + /** selection สีฟ้าสะอาด ๆ ของ tool เลือก — กรองแนวเส้นทางลากเหมือน liveMark * แต่ไม่สร้างไฮไลต์ (ซ่อนสี selection ของเบราว์เซอร์ วาดเองแทน) */ selectPreview: { pageIndex: number; rects: Rect[] } | null; /** ความกว้างหน้าแรกที่ scale 1 — แปลง px↔% ของ fontSize ในแผง selection */ @@ -892,7 +912,7 @@ function PdfPage({ liveMarkStyle.opacity, ), )} - {/* selection สีฟ้าของ tool เลือกข้อความ — กรองแนวเส้นทางลากแล้ว วาดเองแทน + {/* selection สีฟ้าของ tool เลือก — กรองแนวเส้นทางลากแล้ว วาดเองแทน สี selection ของเบราว์เซอร์ (ไม่สร้างไฮไลต์) */} {selectPreview && selectPreview.pageIndex === pageNumber - 1 && @@ -1084,7 +1104,7 @@ function PdfPage({ {a.data.message}

)} -

+

{formatThaiDate(a.createdAt, true)}

{/* F10/A5: toggle จัดการแล้ว — ใครก็ได้ (D6) */} @@ -1211,7 +1231,7 @@ export default function PDFViewer({ const [currentPage, setCurrentPage] = useState(0); // tool highlight — live preview มาร์กตามรูปแบบ/สีที่ตั้งไว้ ระหว่างลากคลุมข้อความ const [liveMark, setLiveMark] = useState<{ pageIndex: number; rects: Rect[] } | null>(null); - // tool เลือกข้อความ — selection สีฟ้าสะอาด ๆ (กรองแนวเส้นทางลากแล้ว ไม่สร้างไฮไลต์) + // tool เลือก — selection สีฟ้าสะอาด ๆ (กรองแนวเส้นทางลากแล้ว ไม่สร้างไฮไลต์) const [selectPreview, setSelectPreview] = useState<{ pageIndex: number; rects: Rect[] } | null>(null); const [composer, setComposer] = useState(null); const [draft, setDraft] = useState(""); @@ -1689,11 +1709,8 @@ export default function PDFViewer({ useEffect(() => { if (pageSizes.length === 0 || fitWidthDoneRef.current) return; fitWidthDoneRef.current = true; - const avail = (mainRef.current?.clientWidth ?? 0) - 32; - const pageW = pageSizes[0].width; - if (avail > 0 && pageW > avail) { - setScale(clamp(Math.floor((avail / pageW) * 100) / 100, 0.5, 1)); - } + const s = fitWidthScale(mainRef.current?.clientWidth, pageSizes[0].width); + if (s !== null && s !== 1) setScale(s); }, [pageSizes]); useEffect(() => { @@ -2641,7 +2658,7 @@ export default function PDFViewer({ pageEl: HTMLElement, ) { // ปล่อยเมาส์จากการลากคลุมข้อความ (comment/textbox/stamp ก็คลุมได้) ไม่ถือเป็นคลิก — - // selection ยังค้างตอน click ยิง ให้ผ่านไปเฉย ๆ เหมือน tool เลือกข้อความ ไม่เปิด + // selection ยังค้างตอน click ยิง ให้ผ่านไปเฉย ๆ เหมือน tool เลือก ไม่เปิด // composer/วางตรายาง (คลิกล้วน ๆ selection ถูกยุบตั้งแต่ mousedown จึงไม่โดน guard นี้) const selection = window.getSelection(); if (selection && !selection.isCollapsed) return; @@ -2998,9 +3015,20 @@ export default function PDFViewer({ > - + {/* UX audit 2026-09-22: ตัวเลข % เป็นปุ่มกลับไป fit-width — PDF viewer ที่ + ผู้ใช้เคยใช้ทุกตัวมี affordance รีเซ็ตซูม คิดจากความกว้าง main ปัจจุบัน */} +