"use client"; import { ArrowDownIcon, ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, Columns3Icon, PlusIcon, PencilIcon, PaletteIcon, Rows3Icon, Trash2Icon, } from "lucide-react"; import Spreadsheet, { type ColumnIndicatorProps, type DataEditorProps, type Point, type Selection } from "react-spreadsheet"; import { useCallback, useEffect, useMemo, useState } from "react"; import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog"; import { RichTextContent } from "@/components/rich-text-content"; import { RichTextEditor } from "@/components/rich-text-editor"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { addExtraSheetColumn, addExtraSheetRow, deleteExtraSheetColumn, deleteExtraSheetRow, extraSheetToMatrix, matrixToExtraSheet, moveExtraSheetColumn, moveExtraSheetRow, nextExtraSheetColumnLabel, updateExtraSheetCell, updateExtraSheetCells, type ExtraSheet, type ExtraSheetMatrix, } from "@/lib/guides/extra-sheet"; import { normalizeExtraRichText } from "@/lib/rich-text"; type DeleteTarget = "column" | "row" | null; const DEFAULT_CELL_BACKGROUND = "#16A34A"; function LiteralDataViewer({ cell }: { cell?: { value?: unknown; backgroundColor?: string } }) { return (
); } function DialogDataEditor({ cell, row, column, exitEditMode, onEdit, }: DataEditorProps<{ value: string }> & { onEdit: (point: Point, value: string) => void; }) { useEffect(() => { onEdit({ row, column }, String(cell?.value ?? "")); exitEditMode(); }, [cell?.value, column, exitEditMode, onEdit, row]); return ; } function EditableColumnIndicator({ column, label, selected, onRename, }: ColumnIndicatorProps & { onRename: (column: number) => void }) { return ( ); } export function ExtraSheetEditor({ value, createId, onChange, onRemove, }: { value: ExtraSheet; createId: (prefix: string) => string; onChange: (value: ExtraSheet) => void; onRemove: () => void; }) { const [active, setActive] = useState({ row: 0, column: 0 }); const [labelDialogOpen, setLabelDialogOpen] = useState(false); const [renaming, setRenaming] = useState(false); const [columnLabel, setColumnLabel] = useState(""); const [deleteTarget, setDeleteTarget] = useState(null); const [removeOpen, setRemoveOpen] = useState(false); const [cellDialogOpen, setCellDialogOpen] = useState(false); const [cellDraft, setCellDraft] = useState(""); const [selection, setSelection] = useState(null); const matrix = useMemo(() => extraSheetToMatrix(value), [value]); const activeColumn = value.columns[active.column]; const activeRow = value.rows[active.row]; const normalizedLabel = columnLabel.trim().toLocaleLowerCase(); const labelInvalid = !normalizedLabel || value.columns.some((column, index) => index !== (renaming ? active.column : -1) && column.label.toLocaleLowerCase() === normalizedLabel, ); const selectedPoints = useCallback(() => { const range = selection?.toRange(matrix); return range ? Array.from(range) : [active]; }, [active, matrix, selection]); const selectedCount = selectedPoints().length; const openAddColumn = () => { setRenaming(false); setColumnLabel(nextExtraSheetColumnLabel(value.columns)); setLabelDialogOpen(true); }; const openRenameColumn = useCallback((columnIndex = active.column) => { const column = value.columns[columnIndex]; if (!column) return; setActive((current) => ({ ...current, column: columnIndex })); setRenaming(true); setColumnLabel(column.label); setLabelDialogOpen(true); }, [active.column, value.columns]); const saveColumnLabel = () => { if (labelInvalid) return; if (renaming) { onChange({ ...value, columns: value.columns.map((column, index) => index === active.column ? { ...column, label: columnLabel.trim() } : column, ), }); } else { const column = value.columns.length; onChange(addExtraSheetColumn( value, columnLabel.trim(), createId("extra-sheet-column"), )); setActive((current) => ({ ...current, column })); } setLabelDialogOpen(false); }; const openCellEditorAt = useCallback((point: Point, content?: string) => { setActive(point); const column = value.columns[point.column]; const row = value.rows[point.row]; setCellDraft(normalizeExtraRichText( content ?? (column && row ? row.cells[column.id] ?? "" : ""), )); setCellDialogOpen(true); }, [value.columns, value.rows]); const openActiveCellEditor = () => openCellEditorAt(active); const columnIndicator = useCallback((props: ColumnIndicatorProps) => ( ), [openRenameColumn]); const dataEditor = useCallback((props: DataEditorProps<{ value: string }>) => ( ), [openCellEditorAt]); return (
} > แถว {active.row + 1} จัดการแถว {active.row + 1} { onChange(moveExtraSheetRow(value, active.row, active.row - 1)); setActive((current) => ({ ...current, row: current.row - 1 })); }} > ย้ายขึ้น { onChange(moveExtraSheetRow(value, active.row, active.row + 1)); setActive((current) => ({ ...current, row: current.row + 1 })); }} > ย้ายลง setDeleteTarget("row")} > ลบแถว } > {activeColumn?.label ?? "คอลัมน์"} {activeColumn?.label} openRenameColumn()}>แก้ไขชื่อ { onChange(moveExtraSheetColumn(value, active.column, active.column - 1)); setActive((current) => ({ ...current, column: current.column - 1 })); }} > ย้ายไปทางซ้าย { onChange(moveExtraSheetColumn(value, active.column, active.column + 1)); setActive((current) => ({ ...current, column: current.column + 1 })); }} > ย้ายไปทางขวา setDeleteTarget("column")} > ลบคอลัมน์
column.label)} ColumnIndicator={columnIndicator} DataViewer={LiteralDataViewer} DataEditor={dataEditor} onActivate={setActive} onSelect={setSelection} onChange={(next) => onChange(matrixToExtraSheet( value, next as ExtraSheetMatrix, createId, ))} />
แก้ไขเซลล์ {selectedCount > 1 ? `นำข้อความเดียวกันไปใช้กับ ${selectedCount} เซลล์ที่เลือก` : `แถว ${active.row + 1} · ${activeColumn?.label ?? "คอลัมน์"}`} {renaming ? "แก้ไขชื่อคอลัมน์" : "เพิ่มคอลัมน์"} ตั้งชื่อที่ใช้อธิบายข้อมูลในคอลัมน์นี้ ชื่อคอลัมน์ setColumnLabel(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") saveColumnLabel(); }} /> { if (!open) setDeleteTarget(null); }} title={deleteTarget === "column" ? `ลบ ${activeColumn?.label}?` : `ลบแถว ${active.row + 1}?`} description="ข้อมูลในรายการนี้จะถูกลบออกจากตาราง" showSuccessToast={false} onConfirm={() => { if (deleteTarget === "column") { onChange(deleteExtraSheetColumn(value, active.column)); setActive((current) => ({ ...current, column: Math.max(0, current.column - 1) })); } else if (deleteTarget === "row") { onChange(deleteExtraSheetRow(value, active.row)); setActive((current) => ({ ...current, row: Math.max(0, current.row - 1) })); } setDeleteTarget(null); }} /> { onRemove(); setRemoveOpen(false); }} />
); }