feat : update
This commit is contained in:
@@ -12,7 +12,7 @@ export function EditorPresence({ guideId }: { guideId: string }) {
|
||||
<div className="flex flex-wrap items-center gap-1" aria-live="polite">
|
||||
<Badge variant="secondary">
|
||||
<UsersIcon data-icon="inline-start" />
|
||||
Editing {editors.length || (presence ? 0 : "—")}
|
||||
Editing {editors.length || (presence ? 0 : "")}
|
||||
</Badge>
|
||||
{editors.map((editor) => (
|
||||
<Badge key={editor.userId} variant="outline">{editor.name}</Badge>
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
Columns3Icon,
|
||||
PlusIcon,
|
||||
PencilIcon,
|
||||
PaletteIcon,
|
||||
Rows3Icon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import Spreadsheet, { type ColumnIndicatorProps, type DataEditorProps, type Point } from "react-spreadsheet";
|
||||
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";
|
||||
@@ -48,16 +49,21 @@ import {
|
||||
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 } }) {
|
||||
function LiteralDataViewer({ cell }: { cell?: { value?: unknown; backgroundColor?: string } }) {
|
||||
return (
|
||||
<div className="Spreadsheet__data-viewer Spreadsheet__data-viewer--preserve-breaks">
|
||||
<div
|
||||
className="Spreadsheet__data-viewer Spreadsheet__data-viewer--preserve-breaks size-full"
|
||||
style={cell?.backgroundColor ? { backgroundColor: cell.backgroundColor } : undefined}
|
||||
>
|
||||
<RichTextContent html={normalizeExtraRichText(String(cell?.value ?? ""))} />
|
||||
</div>
|
||||
);
|
||||
@@ -119,6 +125,7 @@ export function ExtraSheetEditor({
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
const [cellDialogOpen, setCellDialogOpen] = useState(false);
|
||||
const [cellDraft, setCellDraft] = useState("");
|
||||
const [selection, setSelection] = useState<Selection | null>(null);
|
||||
const matrix = useMemo(() => extraSheetToMatrix(value), [value]);
|
||||
const activeColumn = value.columns[active.column];
|
||||
const activeRow = value.rows[active.row];
|
||||
@@ -127,6 +134,11 @@ export function ExtraSheetEditor({
|
||||
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);
|
||||
@@ -200,7 +212,30 @@ export function ExtraSheetEditor({
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" onClick={openActiveCellEditor}>
|
||||
<PencilIcon data-icon="inline-start" />
|
||||
แก้ไขเซลล์
|
||||
แก้ไข {selectedCount > 1 ? `${selectedCount} เซลล์` : "เซลล์"}
|
||||
</Button>
|
||||
<label className="relative">
|
||||
<Button type="button" variant="outline" size="sm" render={<span />} nativeButton={false}>
|
||||
<PaletteIcon data-icon="inline-start" />
|
||||
สีพื้นหลัง {selectedCount > 1 ? `${selectedCount} เซลล์` : "เซลล์"}
|
||||
</Button>
|
||||
<input
|
||||
type="color"
|
||||
aria-label="เลือกสีพื้นหลังเซลล์"
|
||||
className="absolute inset-0 cursor-pointer opacity-0"
|
||||
value={activeRow?.backgrounds[activeColumn?.id ?? ""] ?? DEFAULT_CELL_BACKGROUND}
|
||||
onChange={(event) => onChange(updateExtraSheetCells(value, selectedPoints(), {
|
||||
backgroundColor: event.target.value,
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => onChange(updateExtraSheetCells(value, selectedPoints(), { backgroundColor: null }))}
|
||||
>
|
||||
ล้างสีพื้นหลัง
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
@@ -300,6 +335,7 @@ export function ExtraSheetEditor({
|
||||
DataViewer={LiteralDataViewer}
|
||||
DataEditor={dataEditor}
|
||||
onActivate={setActive}
|
||||
onSelect={setSelection}
|
||||
onChange={(next) => onChange(matrixToExtraSheet(
|
||||
value,
|
||||
next as ExtraSheetMatrix,
|
||||
@@ -313,13 +349,17 @@ export function ExtraSheetEditor({
|
||||
<DialogHeader>
|
||||
<DialogTitle>แก้ไขเซลล์</DialogTitle>
|
||||
<DialogDescription>
|
||||
แถว {active.row + 1} · {activeColumn?.label ?? "คอลัมน์"}
|
||||
{selectedCount > 1
|
||||
? `นำข้อความเดียวกันไปใช้กับ ${selectedCount} เซลล์ที่เลือก`
|
||||
: `แถว ${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));
|
||||
onChange(selectedCount > 1
|
||||
? updateExtraSheetCells(value, selectedPoints(), { value: cellDraft })
|
||||
: updateExtraSheetCell(value, active.row, active.column, cellDraft));
|
||||
setCellDialogOpen(false);
|
||||
}}>บันทึกเซลล์</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -16,8 +16,10 @@ import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import type { GlossaryKind } from "@/lib/glossary";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CatalogItem = { key: string; name: string; imageUrl: string };
|
||||
type CatalogItem = { key: string; name: string; imageUrl: string; rarity?: number | null };
|
||||
type Alias = { id: string; kind: string; catalogKey: string; alias: string };
|
||||
|
||||
const kindLabels: Record<GlossaryKind, string> = {
|
||||
@@ -101,7 +103,10 @@ export function GlossaryManager({
|
||||
<ComboboxList>
|
||||
{(catalogItem) => (
|
||||
<ComboboxItem key={catalogItem.key} value={catalogItem}>
|
||||
<span className="relative size-8 shrink-0 overflow-hidden rounded-md bg-muted">
|
||||
<span className={cn(
|
||||
"relative size-8 shrink-0 overflow-hidden rounded-md bg-muted",
|
||||
rarityGradientClass(catalogItem.rarity),
|
||||
)}>
|
||||
<Image src={catalogItem.imageUrl} alt="" fill className="object-contain" />
|
||||
</span>
|
||||
{catalogItem.name}
|
||||
|
||||
@@ -89,7 +89,7 @@ export function GuideVisibilityControl({
|
||||
onClick={() => updateVisibility(true)}
|
||||
>
|
||||
{pending ? <Spinner data-icon="inline-start" /> : <EyeIcon data-icon="inline-start" />}
|
||||
Publish
|
||||
Make Publish
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user