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 (
(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 (
{images.length > 0 && (
)}
{docs.length > 0 && (
)}
);
}
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รูปภาพ
{images.map((a) => (
(รูป)
))}
เอกสาร
{docs.map((a) => (
))}
{/* Action card */}
{status === "checked_in" ? "กำลังเข้าพบ" : "ยังไม่เช็คอิน"}
{status === "checked_in" && checkInAt && (
เช็คอินแล้ว เวลา {formatTime(checkInAt)}
)}
เช็คอิน / เช็คเอ้า
{stageNoteLabel}