เพิ่มตัวเลือก mock user 10 คนที่หน้าแรก + ปรับหน้าแรก + select ลูกศรเองทั่วแอป
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 47s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 47s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
- utils/user.ts: MOCK_USERS (userId คงที่) + setLocalUser/getStoredLocalUser เขียนลง localStorage key เดิม — PDFViewer อ่าน getLocalUser() เหมือนเดิม - components/UserPicker.tsx: dropdown เลือกผู้ใช้ก่อนเข้า annotate (ยังไม่มี identity = default คนแรก, identity สุ่มเดิมแสดงเป็น option ชั่วคราว) - components/Select.tsx: select ถอดลูกศร native (ชิดขอบ) ใส่ ChevronDown เอง ใช้ที่ UserPicker + ตัวกรองรายคน + เลือกหัวข้อ bookmark 2 จุด - app/page.tsx: header monogram, การ์ดไฟล์ hover, empty state ใหม่ Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
79c9e2cc41
commit
289d735bf3
6 changed files with 162 additions and 21 deletions
19
components/Select.tsx
Normal file
19
components/Select.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"use client";
|
||||
|
||||
// <select> ที่ถอดลูกศร native ของ browser ออก (วาดชิดขอบและขยับด้วย padding ไม่ได้)
|
||||
// แล้วใส่ลูกศรเอง — ใช้เหมือน <select> ปกติทุกอย่าง (props/children)
|
||||
// className ที่ส่งเข้าไปใช้กับตัว <select> โดย append `appearance-none pr-7` ให้
|
||||
import type { SelectHTMLAttributes } from "react";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
export default function Select({
|
||||
className = "",
|
||||
...props
|
||||
}: SelectHTMLAttributes<HTMLSelectElement>) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<select {...props} className={`appearance-none pr-7 ${className}`} />
|
||||
<ChevronDown className="pointer-events-none absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
components/UserPicker.tsx
Normal file
66
components/UserPicker.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"use client";
|
||||
|
||||
// เลือก mock user ที่หน้าแรกก่อนเข้า annotate — เขียนลง localStorage key เดิม
|
||||
// ที่ getLocalUser() อ่าน (PDFViewer ไม่ต้องแก้อะไร)
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import Select from "./Select";
|
||||
import {
|
||||
MOCK_USERS,
|
||||
getStoredLocalUser,
|
||||
setLocalUser,
|
||||
type LocalUser,
|
||||
} from "@/utils/user";
|
||||
|
||||
export default function UserPicker() {
|
||||
const [current, setCurrent] = useState<LocalUser | null>(null);
|
||||
|
||||
// ต้องรันใน effect เพราะ SSR ไม่มี localStorage — ก่อน mount แสดงคนแรกไว้ก่อน
|
||||
useEffect(() => {
|
||||
const stored = getStoredLocalUser();
|
||||
if (!stored) {
|
||||
// ยังไม่เคยมี identity → เริ่มด้วย mock user คนแรก
|
||||
setLocalUser(MOCK_USERS[0]);
|
||||
setCurrent(MOCK_USERS[0]);
|
||||
return;
|
||||
}
|
||||
setCurrent(stored);
|
||||
}, []);
|
||||
|
||||
// identity สุ่มเดิมไม่อยู่ในรายการ mock → แสดงเป็น option ชั่วคราวให้เห็นว่าปัจจุบันคือใคร
|
||||
const isMock = current !== null && MOCK_USERS.some((u) => u.userId === current.userId);
|
||||
|
||||
const select = (userId: string) => {
|
||||
const user = MOCK_USERS.find((u) => u.userId === userId);
|
||||
if (!user) return;
|
||||
setLocalUser(user);
|
||||
setCurrent(user);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="mt-6 rounded-lg border bg-white p-4 shadow-sm">
|
||||
<h2 className="text-sm font-semibold text-primary">เลือกผู้ใช้ (mock)</h2>
|
||||
<p className="mt-0.5 text-xs text-slate-400">
|
||||
annotation ทั้งหมดจะบันทึกในชื่อผู้ใช้ที่เลือก
|
||||
</p>
|
||||
<Select
|
||||
value={current ? (isMock ? current.userId : "") : MOCK_USERS[0].userId}
|
||||
onChange={(e) => {
|
||||
if (e.target.value) select(e.target.value);
|
||||
}}
|
||||
className="mt-3 block w-full rounded-md border bg-white py-2 pl-3 text-sm"
|
||||
>
|
||||
{current && !isMock && (
|
||||
<option value="" disabled>
|
||||
{current.userName} (สุ่มอัตโนมัติ)
|
||||
</option>
|
||||
)}
|
||||
{MOCK_USERS.map((u) => (
|
||||
<option key={u.userId} value={u.userId}>
|
||||
{u.userName}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -109,6 +109,7 @@ import InkLayer, { type InkRedaction, type InkShape, type InkStroke } from "./In
|
|||
import TextboxStampLayer, { type InkStamp, type InkTextbox } from "./TextboxStampLayer";
|
||||
import AnnotationSelectionLayer from "./AnnotationSelectionLayer";
|
||||
import AnnotationSidebar from "../sidebar/AnnotationSidebar";
|
||||
import Select from "../Select";
|
||||
import {
|
||||
ColorSwatches,
|
||||
HIGHLIGHT_STYLE_BTNS,
|
||||
|
|
@ -2634,7 +2635,7 @@ export default function PDFViewer({
|
|||
className="mt-1.5 w-full resize-none rounded-md border border-slate-300 px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
/>
|
||||
<p className="mt-1.5 text-[11px] text-slate-400">วางที่</p>
|
||||
<select
|
||||
<Select
|
||||
value={bookmarkComposer.parentId ?? ""}
|
||||
onChange={(e) =>
|
||||
setBookmarkComposer({
|
||||
|
|
@ -2654,7 +2655,7 @@ export default function PDFViewer({
|
|||
ใต้ "{h.title}"
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Select>
|
||||
{bookmarkError && (
|
||||
<p className="mt-1.5 rounded bg-red-50 px-2 py-1 text-[11px] text-red-600">
|
||||
{bookmarkError}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import {
|
|||
X,
|
||||
} from "lucide-react";
|
||||
|
||||
import Select from "../Select";
|
||||
|
||||
import {
|
||||
listVersions,
|
||||
restoreVersion,
|
||||
|
|
@ -359,7 +361,7 @@ export default function AnnotationSidebar({
|
|||
className="mt-1 w-full resize-none rounded border border-slate-300 p-1.5 text-[11px] focus:outline-none focus:ring-1 focus:ring-slate-400"
|
||||
/>
|
||||
<p className="mt-1 text-[10px] text-slate-400">วางที่</p>
|
||||
<select
|
||||
<Select
|
||||
value={eb.parentId ?? ""}
|
||||
onChange={(e) =>
|
||||
setEditBookmark({ ...eb, parentId: e.target.value || null })
|
||||
|
|
@ -373,7 +375,7 @@ export default function AnnotationSidebar({
|
|||
ใต้ "{h.title}"
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Select>
|
||||
<div className="mt-1.5 flex justify-end gap-1.5 text-[11px]">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -622,7 +624,7 @@ export default function AnnotationSidebar({
|
|||
<div className="flex h-full flex-col">
|
||||
{/* F4: กรองแสดง annotation ตามผู้เขียน (viewer + sidebar พร้อมกัน) */}
|
||||
<div className="shrink-0 border-b border-slate-200 p-2">
|
||||
<select
|
||||
<Select
|
||||
aria-label="กรอง annotation ตามผู้เขียน"
|
||||
className="w-full rounded-md border border-slate-300 bg-white px-2 py-1.5 text-xs text-slate-600 focus:outline-none focus:ring-2 focus:ring-slate-400"
|
||||
value={userFilter}
|
||||
|
|
@ -635,7 +637,7 @@ export default function AnnotationSidebar({
|
|||
เฉพาะ {u.userName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* F14/D1: ค้นหาในหมายเหตุทั้งไฟล์ — คลิกผลลัพธ์ = กระโดดไปตำแหน่ง + เลือก */}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue