test


import { useEffect, useMemo, useState } from "react"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Camera, ChevronDown, ChevronUp, Clock, FileText, Image as ImageIcon, MapPin, Paperclip, Pin, } from "lucide-react"; /** * Prototype: SINGLE LOG per visit (Check-in + Check-out combined) * UI aligned to user-provided structure: * - Summary (visit no + duration) * - Divider * - Location card * - Divider * - Check-in block + Check-out block * - Single "แสดงเพิ่มเติม" to expand BOTH stages' attachments */ type Attachment = { id: string; kind: "image" | "doc"; name: string; }; type VisitLog = { id: string; userDisplayName: string; isYou?: boolean; visitNo: number; checkInAt: Date; checkOutAt: Date; address: string; noteIn?: string; noteOut?: string; attachmentsIn: Attachment[]; attachmentsOut: Attachment[]; pinned?: boolean; }; function formatTime(d: Date) { return d.toLocaleTimeString("th-TH", { hour: "2-digit", minute: "2-digit" }); } function durationLabel(start: Date, end: Date) { const mins = Math.max(0, Math.floor((end.getTime() - start.getTime()) / 60000)); const h = Math.floor(mins / 60); const m = mins % 60; return `${h} ชม. ${m} นาที`; } function attachmentCountLabel(items: Attachment[]) { if (!items || items.length === 0) return "แนบ 0 รายการ"; const img = items.filter((x) => x.kind === "image").length; const doc = items.filter((x) => x.kind === "doc").length; const parts: string[] = []; if (img) parts.push(`รูป ${img}`); if (doc) parts.push(`เอกสาร ${doc}`); return `แนบ ${items.length} รายการ (${parts.join(", ")})`; } function AttachmentPill({ a }: { a: Attachment }) { const Icon = a.kind === "image" ? ImageIcon : FileText; return (
{a.name}
); } function AttachmentPreviewGrid({ items }: { items: Attachment[] }) { const images = items.filter((x) => x.kind === "image"); const docs = items.filter((x) => x.kind === "doc"); return (
{images.length > 0 && (
รูปภาพ
{images.map((a) => (
(รูป)
))}
)} {docs.length > 0 && (
เอกสาร
{docs.map((a) => ( ))}
)}
); } function Divider() { return
; } // Dev-only helper tests function runDevTests() { try { const t = new Date("2026-01-06T11:31:00"); console.assert(formatTime(t).includes("11"), "formatTime should include hour"); console.assert(durationLabel(t, t) === "0 ชม. 0 นาที", "durationLabel zero"); console.assert( attachmentCountLabel([ { id: "1", kind: "image", name: "a.jpg" }, { id: "2", kind: "doc", name: "b.pdf" }, { id: "3", kind: "doc", name: "c.pdf" }, ]) === "แนบ 3 รายการ (รูป 1, เอกสาร 2)", "attachmentCountLabel should match expected format" ); console.assert( attachmentCountLabel([]) === "แนบ 0 รายการ", "attachmentCountLabel empty" ); } catch { // no-op } } export default function CheckInOutSingleLogPrototype() { const [status, setStatus] = useState<"idle" | "checked_in">("idle"); const [visitNo, setVisitNo] = useState(4); const [checkInAt, setCheckInAt] = useState(null); const [noteIn, setNoteIn] = useState("มาพบลูกค้า"); const [noteOut, setNoteOut] = useState("สรุปการเข้าพบลูกค้าต้องการสั่งสินค้าเพิ่ม"); const [attachmentsIn, setAttachmentsIn] = useState([ { id: "in-1", kind: "image", name: "meeting_photo_1.jpg" }, { id: "in-2", kind: "doc", name: "quotation_v4.pdf" }, { id: "in-3", kind: "doc", name: "catalog_2026.xlsx" }, ]); const [attachmentsOut, setAttachmentsOut] = useState([ { id: "out-1", kind: "image", name: "after_meeting.jpg" }, ]); const address = "51 ถ. พระราม 9 แขวงหัวหมาก บางกะปิ กรุงเทพมหานคร 10240 ประเทศไทย"; const [logs, setLogs] = useState(() => { const t = new Date("2026-01-06T11:31:00"); return [ { id: `seed-${t.getTime()}`, userDisplayName: "pathon", isYou: true, visitNo: 4, checkInAt: new Date(t.getTime()), checkOutAt: new Date(t.getTime()), noteIn: "มาพบลูกค้า", noteOut: "สรุปการเข้าพบลูกค้าต้องการสั่งสินค้าเพิ่ม", address, attachmentsIn: [ { id: "s-in-1", kind: "image", name: "meeting_photo_1.jpg" }, { id: "s-in-2", kind: "doc", name: "quotation_v4.pdf" }, { id: "s-in-3", kind: "doc", name: "catalog_2026.xlsx" }, ], attachmentsOut: [{ id: "s-out-1", kind: "image", name: "after_meeting.jpg" }], }, ]; }); const [expanded, setExpanded] = useState>({}); function toggleVisitExpanded(id: string) { setExpanded((p) => ({ ...p, [id]: !p[id] })); } function addMockAttachment(stage: "in" | "out", kind: Attachment["kind"]) { const stamp = Date.now(); const item: Attachment = kind === "image" ? { id: `${stage}-${stamp}`, kind, name: `photo_${stamp}.jpg` } : { id: `${stage}-${stamp}`, kind, name: `document_${stamp}.pdf` }; if (stage === "in") setAttachmentsIn((p) => [item, ...p]); else setAttachmentsOut((p) => [item, ...p]); } function checkIn() { setCheckInAt(new Date()); setStatus("checked_in"); } function checkOut() { if (!checkInAt) return; const out = new Date(); setLogs((prev) => [ { id: `visit-${Date.now()}`, userDisplayName: "pathon", isYou: true, visitNo, checkInAt, checkOutAt: out, noteIn: noteIn.trim() || undefined, noteOut: noteOut.trim() || undefined, address, attachmentsIn, attachmentsOut, }, ...prev, ]); setVisitNo((v) => v + 1); setCheckInAt(null); setStatus("idle"); // reset stage inputs for demo setNoteIn("มาพบลูกค้า"); setNoteOut("สรุปการเข้าพบลูกค้าต้องการสั่งสินค้าเพิ่ม"); setAttachmentsIn([ { id: `in-${Date.now()}-1`, kind: "image", name: "meeting_photo_1.jpg" }, { id: `in-${Date.now()}-2`, kind: "doc", name: "quotation_v4.pdf" }, { id: `in-${Date.now()}-3`, kind: "doc", name: "catalog_2026.xlsx" }, ]); setAttachmentsOut([ { id: `out-${Date.now()}-1`, kind: "image", name: "after_meeting.jpg" }, ]); } const stageTitle = status === "checked_in" ? "เช็คเอ้า" : "เช็คอิน"; const stageNoteLabel = status === "checked_in" ? "บันทึกตอนเช็คเอ้า" : "บันทึกตอนเช็คอิน"; const stageAttachmentsLabel = useMemo(() => { return status === "checked_in" ? attachmentCountLabel(attachmentsOut) : attachmentCountLabel(attachmentsIn); }, [status, attachmentsIn, attachmentsOut]); useEffect(() => { // @ts-expect-error - process may not exist in some runtimes const env = typeof process !== "undefined" ? process.env?.NODE_ENV : "development"; if (env !== "production") runDevTests(); }, []); return (
{/* Action card */}
เช็คอิน / เช็คเอ้า
{status === "checked_in" ? "กำลังเข้าพบ" : "ยังไม่เช็คอิน"}
{status === "checked_in" && checkInAt && (
เช็คอินแล้ว เวลา {formatTime(checkInAt)}
)}
{stageNoteLabel}