เลือก annotation บนหน้า PDF → sidebar ตามไปเน้นรายการนั้น + สร้างเสร็จเลือกให้ทันที + กัน duplicate ชั่วขณะตอนสร้าง: (1) ส่ง selectedId ให้ AnnotationSidebar — ไฮไลต์แถว (ทั้งลิสต์หลักและผลค้นหา) + scrollIntoView block:nearest + สลับมาแท็บ Annotations ให้เองถ้ากำลังอยู่ Bookmarks (rAF รอแถว render หลังสลับแท็บค่อยเลื่อน) (2) AUTO_SELECT_TYPES — สร้าง DRAW/SHAPE/TEXTBOX/STAMP/HIGHLIGHT/REDACTION เสร็จ = เลือกตัวใหม่ทันทีด้วย saved.id (กรอบ selection + sidebar ตามไปเลย; COMMENT ยกเว้นเพราะ composer/pin ทำหน้าที่นั้นอยู่แล้ว) — แก้ปัญหา "วาดเสร็จคลิกที่เส้น tool ยังเป็นวาด sidebar นิ่ง" (3) กันอาการ row โผล่แล้วหาย (0→2→1): เดิม optimistic row กับ row จาก server คนละ id ทำให้ SSE event มาก่อน action เสร็จ dedupe ไม่ได้ — ตอนนี้ client ส่ง id ของ optimistic row ให้ createAnnotation ใช้ซ้ำ (validate UUID, ไม่ส่ง = สุ่มเองตามเดิมสำหรับ redo path) ทุกชั้น dedupe ด้วย id เดียวกันตั้งแต่แรก — sync PLAN.md §4 + rules/realtime.md ให้ตรงพฤติกรรมใหม่

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-09-14 11:35:05 +07:00
parent 4934b9d0b3
commit 6ddc35d6d5
5 changed files with 67 additions and 12 deletions

View file

@ -1,6 +1,6 @@
"use client";
import { useRef, useState, type FormEvent } from "react";
import { useEffect, useRef, useState, type FormEvent } from "react";
import { createPortal } from "react-dom";
import {
ArrowRight,
@ -132,6 +132,7 @@ export default function AnnotationSidebar({
onUpdateBookmark,
onGoToBookmark,
onGoToAnnotation,
selectedId,
}: {
annotations: AnnotationDTO[];
bookmarks: BookmarkDTO[];
@ -157,9 +158,26 @@ export default function AnnotationSidebar({
) => void;
onGoToBookmark: (b: BookmarkDTO) => void;
onGoToAnnotation: (a: AnnotationDTO) => void;
/** id ของ annotation ที่ถูกเลือกอยู่บนหน้า PDF (null = ไม่มี) — แถวในแผงนี้ไฮไลต์ตาม */
selectedId: string | null;
}) {
const [tab, setTab] = useState<"bookmarks" | "annotations">("bookmarks");
// คลิกเลือก annotation บนหน้าเอกสาร = สลับมาแท็บ annotations (ถ้ายังอยู่ bookmarks)
// แล้วเลื่อนแถวนั้นเข้ามุมมอง — แถว render หลัง setTab จึงค่อยเลื่อนใน rAF ถัดไป
// (dep รวม tab ตาม lint — ผลข้างเคียง: เปิดแท็บนี้ตอนยังมีรายการถูกเลือก = เลื่อนไปแถวนั้นเช่นกัน)
const listRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!selectedId) return;
if (tab !== "annotations") setTab("annotations");
const raf = requestAnimationFrame(() => {
listRef.current
?.querySelector(`[data-ann-id="${CSS.escape(selectedId)}"]`)
?.scrollIntoView({ block: "nearest" });
});
return () => cancelAnimationFrame(raf);
}, [selectedId, tab]);
// ─── F14/D1: ค้นหาคำในหมายเหตุทั้งไฟล์ (highlight text / comment message) ────
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState<AnnotationDTO[] | null>(null);
@ -488,9 +506,12 @@ export default function AnnotationSidebar({
return (
<li
key={a.id}
className={`group rounded px-2 py-1.5 hover:bg-slate-100 ${
a.resolved ? "opacity-60" : ""
}`}
data-ann-id={a.id}
className={`group rounded px-2 py-1.5 ${
a.id === selectedId
? "bg-primary/10 ring-1 ring-inset ring-primary/30"
: "hover:bg-slate-100"
} ${a.resolved ? "opacity-60" : ""}`}
>
<div className="flex items-center gap-2">
<button
@ -677,7 +698,11 @@ export default function AnnotationSidebar({
<li key={a.id}>
<button
type="button"
className="flex w-full items-start gap-2 rounded px-1 py-1 text-left hover:bg-slate-100"
className={`flex w-full items-start gap-2 rounded px-1 py-1 text-left ${
a.id === selectedId
? "bg-primary/10 ring-1 ring-inset ring-primary/30"
: "hover:bg-slate-100"
}`}
onClick={() => onGoToAnnotation(a)}
>
<TypeIcon type={a.type} data={a.data} />
@ -717,7 +742,7 @@ export default function AnnotationSidebar({
))}
</div>
<div className="flex-1 overflow-y-auto p-2">
<div ref={listRef} className="flex-1 overflow-y-auto p-2">
{tab === "bookmarks" ? (
bookmarks.length === 0 ? (
<p className="p-4 text-center text-xs text-slate-400">