feat : update
This commit is contained in:
@@ -7,13 +7,16 @@ import {
|
||||
ArrowUpIcon,
|
||||
Columns3Icon,
|
||||
PlusIcon,
|
||||
PencilIcon,
|
||||
Rows3Icon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import Spreadsheet, { type Point } from "react-spreadsheet";
|
||||
import { useMemo, useState } from "react";
|
||||
import Spreadsheet, { type ColumnIndicatorProps, type DataEditorProps, type Point } 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,
|
||||
@@ -44,17 +47,56 @@ import {
|
||||
moveExtraSheetColumn,
|
||||
moveExtraSheetRow,
|
||||
nextExtraSheetColumnLabel,
|
||||
updateExtraSheetCell,
|
||||
type ExtraSheet,
|
||||
type ExtraSheetMatrix,
|
||||
} from "@/lib/guides/extra-sheet";
|
||||
import { normalizeExtraRichText } from "@/lib/rich-text";
|
||||
|
||||
type DeleteTarget = "column" | "row" | null;
|
||||
|
||||
function LiteralDataViewer({ cell }: { cell?: { value?: unknown } }) {
|
||||
return (
|
||||
<span className="Spreadsheet__data-viewer Spreadsheet__data-viewer--preserve-breaks">
|
||||
{String(cell?.value ?? "")}
|
||||
</span>
|
||||
<div className="Spreadsheet__data-viewer Spreadsheet__data-viewer--preserve-breaks">
|
||||
<RichTextContent html={normalizeExtraRichText(String(cell?.value ?? ""))} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <LiteralDataViewer cell={cell} />;
|
||||
}
|
||||
|
||||
function EditableColumnIndicator({
|
||||
column,
|
||||
label,
|
||||
selected,
|
||||
onRename,
|
||||
}: ColumnIndicatorProps & { onRename: (column: number) => void }) {
|
||||
return (
|
||||
<th className={selected ? "Spreadsheet__header Spreadsheet__header--selected" : "Spreadsheet__header"}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => onRename(column)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,6 +117,8 @@ export function ExtraSheetEditor({
|
||||
const [columnLabel, setColumnLabel] = useState("");
|
||||
const [deleteTarget, setDeleteTarget] = useState<DeleteTarget>(null);
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
const [cellDialogOpen, setCellDialogOpen] = useState(false);
|
||||
const [cellDraft, setCellDraft] = useState("");
|
||||
const matrix = useMemo(() => extraSheetToMatrix(value), [value]);
|
||||
const activeColumn = value.columns[active.column];
|
||||
const activeRow = value.rows[active.row];
|
||||
@@ -89,12 +133,14 @@ export function ExtraSheetEditor({
|
||||
setColumnLabel(nextExtraSheetColumnLabel(value.columns));
|
||||
setLabelDialogOpen(true);
|
||||
};
|
||||
const openRenameColumn = () => {
|
||||
if (!activeColumn) return;
|
||||
const openRenameColumn = useCallback((columnIndex = active.column) => {
|
||||
const column = value.columns[columnIndex];
|
||||
if (!column) return;
|
||||
setActive((current) => ({ ...current, column: columnIndex }));
|
||||
setRenaming(true);
|
||||
setColumnLabel(activeColumn.label);
|
||||
setColumnLabel(column.label);
|
||||
setLabelDialogOpen(true);
|
||||
};
|
||||
}, [active.column, value.columns]);
|
||||
const saveColumnLabel = () => {
|
||||
if (labelInvalid) return;
|
||||
if (renaming) {
|
||||
@@ -115,6 +161,22 @@ export function ExtraSheetEditor({
|
||||
}
|
||||
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) => (
|
||||
<EditableColumnIndicator {...props} onRename={openRenameColumn} />
|
||||
), [openRenameColumn]);
|
||||
const dataEditor = useCallback((props: DataEditorProps<{ value: string }>) => (
|
||||
<DialogDataEditor {...props} onEdit={openCellEditorAt} />
|
||||
), [openCellEditorAt]);
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-3" aria-label="ตารางข้อมูลเสริม">
|
||||
@@ -136,6 +198,10 @@ export function ExtraSheetEditor({
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
เพิ่มคอลัมน์
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" onClick={openActiveCellEditor}>
|
||||
<PencilIcon data-icon="inline-start" />
|
||||
แก้ไขเซลล์
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button type="button" variant="outline" size="sm" />}
|
||||
@@ -187,7 +253,7 @@ export function ExtraSheetEditor({
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>{activeColumn?.label}</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={openRenameColumn}>แก้ไขชื่อ</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => openRenameColumn()}>แก้ไขชื่อ</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!activeColumn || active.column === 0}
|
||||
onClick={() => {
|
||||
@@ -230,7 +296,9 @@ export function ExtraSheetEditor({
|
||||
className="extra-sheet-grid"
|
||||
data={matrix}
|
||||
columnLabels={value.columns.map((column) => column.label)}
|
||||
ColumnIndicator={columnIndicator}
|
||||
DataViewer={LiteralDataViewer}
|
||||
DataEditor={dataEditor}
|
||||
onActivate={setActive}
|
||||
onChange={(next) => onChange(matrixToExtraSheet(
|
||||
value,
|
||||
@@ -240,6 +308,24 @@ export function ExtraSheetEditor({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Dialog open={cellDialogOpen} onOpenChange={setCellDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>แก้ไขเซลล์</DialogTitle>
|
||||
<DialogDescription>
|
||||
แถว {active.row + 1} · {activeColumn?.label ?? "คอลัมน์"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<RichTextEditor value={cellDraft} onChange={setCellDraft} showBubbleMenu={false} />
|
||||
<DialogFooter showCloseButton>
|
||||
<Button type="button" onClick={() => {
|
||||
onChange(updateExtraSheetCell(value, active.row, active.column, cellDraft));
|
||||
setCellDialogOpen(false);
|
||||
}}>บันทึกเซลล์</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user