Next.js 14 + Drizzle/PostgreSQL + SSE realtime sync — highlight, freehand, comment, bookmark + ฟีเจอร์ต่อยอด F1–F19 (edit/move, undo/redo, shapes, redaction, export PDF/CSV, reply, resolve, search, summary, textbox, stamp, version history) Co-Authored-By: Claude Code <noreply@anthropic.com>
55 lines
2.3 KiB
TypeScript
55 lines
2.3 KiB
TypeScript
import { asc, eq } from "drizzle-orm";
|
||
|
||
import { db } from "@/db";
|
||
import { annotations, files, toAnnotationDTO } from "@/db/schema";
|
||
import { annotationNote, annotationPreviewText, annotationTypeLabel } from "@/utils/annotation-display";
|
||
import { csvField } from "@/utils/csv";
|
||
import { encodeRFC5987 } from "@/utils/pdf-export";
|
||
|
||
// Export สรุปหมายเหตุเป็น CSV (F16/D3) — ข้อมูลเดียวกับหน้าสรุป (F15)
|
||
// ขึ้นต้นด้วย UTF-8 BOM เพื่อให้ Excel ภาษาไทยอ่านถูกต้อง (RFC 4180: ครอบ field
|
||
// ด้วย double quote, ขึ้นบรรทัดใหม่ด้วย CRLF)
|
||
|
||
const UUID_RE =
|
||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||
|
||
export async function GET(req: Request) {
|
||
const fileId = new URL(req.url).searchParams.get("fileId") ?? "";
|
||
if (!UUID_RE.test(fileId)) {
|
||
return new Response("fileId ไม่ถูกต้อง", { status: 400 });
|
||
}
|
||
|
||
const [file] = await db.select().from(files).where(eq(files.id, fileId));
|
||
if (!file) return new Response("ไม่พบไฟล์นี้", { status: 404 });
|
||
|
||
const rows = await db
|
||
.select()
|
||
.from(annotations)
|
||
.where(eq(annotations.fileId, fileId))
|
||
.orderBy(asc(annotations.pageIndex), asc(annotations.createdAt));
|
||
|
||
const header = ["หน้า", "ชนิด", "ผู้เขียน", "ข้อความ", "โน้ต", "สร้างเมื่อ"];
|
||
const lines = rows.map((row) => {
|
||
const a = toAnnotationDTO(row);
|
||
return [
|
||
String(a.pageIndex + 1),
|
||
annotationTypeLabel(a),
|
||
a.userName,
|
||
annotationPreviewText(a),
|
||
annotationNote(a),
|
||
new Date(a.createdAt).toLocaleString("th-TH", { timeZone: "Asia/Bangkok" }),
|
||
]
|
||
.map(csvField)
|
||
.join(",");
|
||
});
|
||
// = UTF-8 BOM — ต้องเป็นตัวแรกสุดของไฟล์เสมอ
|
||
const csv = `${[header.map(csvField).join(","), ...lines].join("\r\n")}\r\n`;
|
||
|
||
const filename = encodeRFC5987(`${file.title}-summary.csv`);
|
||
return new Response(csv, {
|
||
headers: {
|
||
"content-type": "text/csv; charset=utf-8",
|
||
"content-disposition": `attachment; filename="summary.csv"; filename*=UTF-8''${filename}`,
|
||
},
|
||
});
|
||
}
|