feat : excel in website
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ArrowUpIcon,
|
||||
Columns3Icon,
|
||||
PlusIcon,
|
||||
Rows3Icon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import Spreadsheet, { type Point } from "react-spreadsheet";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog";
|
||||
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,
|
||||
type ExtraSheet,
|
||||
type ExtraSheetMatrix,
|
||||
} from "@/lib/guides/extra-sheet";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
export function ExtraSheetEditor({
|
||||
value,
|
||||
createId,
|
||||
onChange,
|
||||
onRemove,
|
||||
}: {
|
||||
value: ExtraSheet;
|
||||
createId: (prefix: string) => string;
|
||||
onChange: (value: ExtraSheet) => void;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const [active, setActive] = useState<Point>({ row: 0, column: 0 });
|
||||
const [labelDialogOpen, setLabelDialogOpen] = useState(false);
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const [columnLabel, setColumnLabel] = useState("");
|
||||
const [deleteTarget, setDeleteTarget] = useState<DeleteTarget>(null);
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
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 openAddColumn = () => {
|
||||
setRenaming(false);
|
||||
setColumnLabel(nextExtraSheetColumnLabel(value.columns));
|
||||
setLabelDialogOpen(true);
|
||||
};
|
||||
const openRenameColumn = () => {
|
||||
if (!activeColumn) return;
|
||||
setRenaming(true);
|
||||
setColumnLabel(activeColumn.label);
|
||||
setLabelDialogOpen(true);
|
||||
};
|
||||
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);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col gap-3" aria-label="ตารางข้อมูลเสริม">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const row = value.rows.length;
|
||||
onChange(addExtraSheetRow(value, createId("extra-sheet-row")));
|
||||
setActive((current) => ({ ...current, row }));
|
||||
}}
|
||||
>
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
เพิ่มแถว
|
||||
</Button>
|
||||
<Button type="button" variant="outline" size="sm" onClick={openAddColumn}>
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
เพิ่มคอลัมน์
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button type="button" variant="outline" size="sm" />}
|
||||
>
|
||||
<Rows3Icon data-icon="inline-start" />
|
||||
แถว {active.row + 1}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>จัดการแถว {active.row + 1}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
disabled={!activeRow || active.row === 0}
|
||||
onClick={() => {
|
||||
onChange(moveExtraSheetRow(value, active.row, active.row - 1));
|
||||
setActive((current) => ({ ...current, row: current.row - 1 }));
|
||||
}}
|
||||
>
|
||||
<ArrowUpIcon />ย้ายขึ้น
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!activeRow || active.row === value.rows.length - 1}
|
||||
onClick={() => {
|
||||
onChange(moveExtraSheetRow(value, active.row, active.row + 1));
|
||||
setActive((current) => ({ ...current, row: current.row + 1 }));
|
||||
}}
|
||||
>
|
||||
<ArrowDownIcon />ย้ายลง
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
disabled={value.rows.length === 1}
|
||||
onClick={() => setDeleteTarget("row")}
|
||||
>
|
||||
<Trash2Icon />ลบแถว
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={<Button type="button" variant="outline" size="sm" />}
|
||||
>
|
||||
<Columns3Icon data-icon="inline-start" />
|
||||
{activeColumn?.label ?? "คอลัมน์"}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>{activeColumn?.label}</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={openRenameColumn}>แก้ไขชื่อ</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!activeColumn || active.column === 0}
|
||||
onClick={() => {
|
||||
onChange(moveExtraSheetColumn(value, active.column, active.column - 1));
|
||||
setActive((current) => ({ ...current, column: current.column - 1 }));
|
||||
}}
|
||||
>
|
||||
<ArrowLeftIcon />ย้ายไปทางซ้าย
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
disabled={!activeColumn || active.column === value.columns.length - 1}
|
||||
onClick={() => {
|
||||
onChange(moveExtraSheetColumn(value, active.column, active.column + 1));
|
||||
setActive((current) => ({ ...current, column: current.column + 1 }));
|
||||
}}
|
||||
>
|
||||
<ArrowRightIcon />ย้ายไปทางขวา
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
disabled={value.columns.length === 1}
|
||||
onClick={() => setDeleteTarget("column")}
|
||||
>
|
||||
<Trash2Icon />ลบคอลัมน์
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button type="button" variant="ghost" size="sm" onClick={() => setRemoveOpen(true)}>
|
||||
<Trash2Icon data-icon="inline-start" />
|
||||
ลบตาราง
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto rounded-lg border">
|
||||
<Spreadsheet
|
||||
className="extra-sheet-grid"
|
||||
data={matrix}
|
||||
columnLabels={value.columns.map((column) => column.label)}
|
||||
DataViewer={LiteralDataViewer}
|
||||
onActivate={setActive}
|
||||
onChange={(next) => onChange(matrixToExtraSheet(
|
||||
value,
|
||||
next as ExtraSheetMatrix,
|
||||
createId,
|
||||
))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{renaming ? "แก้ไขชื่อคอลัมน์" : "เพิ่มคอลัมน์"}</DialogTitle>
|
||||
<DialogDescription>ตั้งชื่อที่ใช้อธิบายข้อมูลในคอลัมน์นี้</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FieldGroup>
|
||||
<Field data-invalid={labelInvalid || undefined}>
|
||||
<FieldLabel htmlFor="extra-sheet-column-label">ชื่อคอลัมน์</FieldLabel>
|
||||
<Input
|
||||
id="extra-sheet-column-label"
|
||||
value={columnLabel}
|
||||
aria-invalid={labelInvalid || undefined}
|
||||
autoFocus
|
||||
onChange={(event) => setColumnLabel(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") saveColumnLabel();
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
<DialogFooter showCloseButton>
|
||||
<Button type="button" disabled={labelInvalid} onClick={saveColumnLabel}>
|
||||
{renaming ? "บันทึกชื่อ" : "เพิ่มคอลัมน์"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DeleteConfirmationDialog
|
||||
open={deleteTarget !== null}
|
||||
onOpenChange={(open) => { 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);
|
||||
}}
|
||||
/>
|
||||
<DeleteConfirmationDialog
|
||||
open={removeOpen}
|
||||
onOpenChange={setRemoveOpen}
|
||||
title="ลบตารางนี้?"
|
||||
description="คอลัมน์ แถว และข้อมูลในเซลล์ทั้งหมดจะถูกลบออกจากการ์ด"
|
||||
confirmLabel="ยืนยันลบตาราง"
|
||||
showSuccessToast={false}
|
||||
onConfirm={() => {
|
||||
onRemove();
|
||||
setRemoveOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -164,6 +164,7 @@ import {
|
||||
} from "@/lib/guides/percent-input";
|
||||
import { formatGuideValidationIssues } from "@/lib/guides/validation-messages";
|
||||
import { normalizeExtraRichText } from "@/lib/rich-text";
|
||||
import { createExtraSheet, type ExtraSheet } from "@/lib/guides/extra-sheet";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CatalogItem = {
|
||||
@@ -276,6 +277,13 @@ const RichTextEditor = dynamic(
|
||||
loading: () => <Skeleton className="h-36 w-full" />,
|
||||
},
|
||||
);
|
||||
const ExtraSheetEditor = dynamic(
|
||||
() => import("@/components/admin/extra-sheet-editor").then((module) => module.ExtraSheetEditor),
|
||||
{
|
||||
ssr: false,
|
||||
loading: () => <Skeleton className="h-48 w-full" />,
|
||||
},
|
||||
);
|
||||
|
||||
function createClientId(prefix: string): string {
|
||||
const browserCrypto = globalThis.crypto;
|
||||
@@ -329,6 +337,7 @@ export interface GuideSectionEditorProps {
|
||||
body: string;
|
||||
percent: string | null;
|
||||
backgroundColor: string | null;
|
||||
sheet: ExtraSheet | null;
|
||||
catalogItems: ExtraCatalogItem[];
|
||||
highlights: Array<{ text: string; color: string }>;
|
||||
imageUrl: string | null;
|
||||
@@ -3508,6 +3517,7 @@ function ExtraEditor({ guide, initial, characters, weapons, artifacts }: EditorB
|
||||
? null
|
||||
: Number(row.percent),
|
||||
backgroundColor: row.backgroundColor,
|
||||
sheet: row.sheet,
|
||||
catalogItems: row.catalogItems,
|
||||
highlights: [],
|
||||
})),
|
||||
@@ -3540,6 +3550,7 @@ function ExtraEditor({ guide, initial, characters, weapons, artifacts }: EditorB
|
||||
id: row.id || createClientId("extra-row-record"),
|
||||
body: normalizeExtraRichText(row.body, row.highlights ?? []),
|
||||
backgroundColor: row.backgroundColor ?? null,
|
||||
sheet: row.sheet ?? null,
|
||||
catalogItems: row.catalogItems ?? [],
|
||||
highlights: [],
|
||||
clientId: row.clientId || createClientId("extra-row"),
|
||||
@@ -3881,6 +3892,71 @@ function ExtraEditor({ guide, initial, characters, weapons, artifacts }: EditorB
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>ตารางข้อมูล (optional)</FieldLabel>
|
||||
{row.sheet ? (
|
||||
<ExtraSheetEditor
|
||||
value={row.sheet}
|
||||
createId={createClientId}
|
||||
onChange={(sheet) =>
|
||||
setSections(
|
||||
sections.map((item, index) =>
|
||||
index === sectionIndex
|
||||
? {
|
||||
...item,
|
||||
rows: item.rows.map((candidate, inner) =>
|
||||
inner === rowIndex
|
||||
? { ...candidate, sheet }
|
||||
: candidate,
|
||||
),
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
onRemove={() =>
|
||||
setSections(
|
||||
sections.map((item, index) =>
|
||||
index === sectionIndex
|
||||
? {
|
||||
...item,
|
||||
rows: item.rows.map((candidate, inner) =>
|
||||
inner === rowIndex
|
||||
? { ...candidate, sheet: null }
|
||||
: candidate,
|
||||
),
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
setSections(
|
||||
sections.map((item, index) =>
|
||||
index === sectionIndex
|
||||
? {
|
||||
...item,
|
||||
rows: item.rows.map((candidate, inner) =>
|
||||
inner === rowIndex
|
||||
? { ...candidate, sheet: createExtraSheet(createClientId) }
|
||||
: candidate,
|
||||
),
|
||||
}
|
||||
: item,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
เพิ่มตาราง 2 × 2
|
||||
</Button>
|
||||
)}
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel>Percent (optional)</FieldLabel>
|
||||
<Input
|
||||
@@ -4052,6 +4128,7 @@ function ExtraEditor({ guide, initial, characters, weapons, artifacts }: EditorB
|
||||
body: "",
|
||||
percent: null,
|
||||
backgroundColor: null,
|
||||
sheet: null,
|
||||
catalogItems: [],
|
||||
highlights: [],
|
||||
clientId: createClientId("extra-row"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import { GuideExportBranding } from "@/components/public/guide-export-branding";
|
||||
import type { PublicGuide } from "@/lib/guides/queries";
|
||||
import { constellationChartRows } from "@/lib/guides/export-image";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -23,15 +24,7 @@ export function ConstellationImage({
|
||||
|
||||
return (
|
||||
<div className="relative isolate size-[1500px] overflow-hidden bg-[#070b20] font-heading text-white">
|
||||
<Image
|
||||
src={background}
|
||||
alt=""
|
||||
fill
|
||||
loading="eager"
|
||||
sizes="1500px"
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#050817]/65" />
|
||||
<GuideExportBranding backgroundUrl={background} variant="constellation" />
|
||||
|
||||
<div className="absolute inset-x-20 top-7 z-10 flex items-start justify-center gap-5">
|
||||
<Image
|
||||
@@ -95,14 +88,6 @@ export function ConstellationImage({
|
||||
{footer}
|
||||
</p>
|
||||
) : null}
|
||||
<Image
|
||||
src="/icon/Watermark.png"
|
||||
alt=""
|
||||
width={280}
|
||||
height={158}
|
||||
loading="eager"
|
||||
className="absolute right-4 bottom-0 z-30 h-auto w-[280px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export function GuideExportBranding({
|
||||
backgroundUrl,
|
||||
variant = "standard",
|
||||
}: {
|
||||
backgroundUrl: string;
|
||||
variant?: "standard" | "constellation";
|
||||
}) {
|
||||
const constellation = variant === "constellation";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
src={backgroundUrl}
|
||||
alt=""
|
||||
fill
|
||||
loading="eager"
|
||||
sizes={constellation ? "1500px" : "1200px"}
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#050817]/65" />
|
||||
<Image
|
||||
src="/icon/Watermark.png"
|
||||
alt=""
|
||||
width={constellation ? 280 : 180}
|
||||
height={constellation ? 158 : 101}
|
||||
loading="eager"
|
||||
className={
|
||||
constellation
|
||||
? "absolute right-4 bottom-0 z-30 h-auto w-[280px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
: "absolute top-4 right-4 z-30 h-auto w-[180px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -15,11 +15,20 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { SiteFooter } from "@/components/public/site-footer";
|
||||
import { SiteHeader } from "@/components/public/site-header";
|
||||
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
||||
import { TeamSearch } from "@/components/public/team-search";
|
||||
import { GuideImageExport } from "@/components/public/guide-image-export";
|
||||
import { GuideExportBranding } from "@/components/public/guide-export-branding";
|
||||
import { ConstellationImage } from "@/components/public/constellation-image";
|
||||
import { RichTextContent } from "@/components/rich-text-content";
|
||||
import { LiveRefresh } from "@/components/live-refresh";
|
||||
@@ -358,25 +367,9 @@ function WeaponSectionContent({
|
||||
)}
|
||||
>
|
||||
{exporting ? (
|
||||
<>
|
||||
<Image
|
||||
src={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
alt=""
|
||||
fill
|
||||
loading="eager"
|
||||
sizes="1200px"
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#050817]/65" />
|
||||
<Image
|
||||
src="/icon/Watermark.png"
|
||||
alt=""
|
||||
width={180}
|
||||
height={101}
|
||||
loading="eager"
|
||||
className="absolute top-4 right-4 z-30 h-auto w-[180px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
/>
|
||||
</>
|
||||
<GuideExportBranding
|
||||
backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
/>
|
||||
) : null}
|
||||
<header
|
||||
className={cn(
|
||||
@@ -609,25 +602,9 @@ function ArtifactSectionContent({
|
||||
)}
|
||||
>
|
||||
{exporting ? (
|
||||
<>
|
||||
<Image
|
||||
src={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
alt=""
|
||||
fill
|
||||
loading="eager"
|
||||
sizes="1200px"
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#050817]/65" />
|
||||
<Image
|
||||
src="/icon/Watermark.png"
|
||||
alt=""
|
||||
width={180}
|
||||
height={101}
|
||||
loading="eager"
|
||||
className="absolute top-4 right-4 z-30 h-auto w-[180px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
/>
|
||||
</>
|
||||
<GuideExportBranding
|
||||
backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
/>
|
||||
) : null}
|
||||
<header
|
||||
className={cn(
|
||||
@@ -1214,25 +1191,9 @@ function ExtraSectionContent({
|
||||
return (
|
||||
<section className={cn("flex flex-col gap-6", exporting && "relative isolate overflow-hidden bg-[#070b20] p-10 text-white")}>
|
||||
{exporting ? (
|
||||
<>
|
||||
<Image
|
||||
src={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
alt=""
|
||||
fill
|
||||
loading="eager"
|
||||
sizes="1200px"
|
||||
className="object-cover"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#050817]/65" />
|
||||
<Image
|
||||
src="/icon/Watermark.png"
|
||||
alt=""
|
||||
width={180}
|
||||
height={101}
|
||||
loading="eager"
|
||||
className="absolute top-4 right-4 z-30 h-auto w-[180px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
|
||||
/>
|
||||
</>
|
||||
<GuideExportBranding
|
||||
backgroundUrl={guide.character.splashImageUrl ?? guide.coverUrl}
|
||||
/>
|
||||
) : null}
|
||||
<header className="relative z-10 flex flex-col items-center gap-3 text-center">
|
||||
<p className="text-sm font-medium text-primary">คู่มือเสริม</p>
|
||||
@@ -1243,7 +1204,10 @@ function ExtraSectionContent({
|
||||
{extra.rows.map((row) => (
|
||||
<Card
|
||||
key={row.id}
|
||||
className="overflow-hidden py-0"
|
||||
className={cn(
|
||||
"overflow-hidden py-0",
|
||||
row.sheet && (exporting ? "col-span-2" : "md:col-span-2"),
|
||||
)}
|
||||
style={row.backgroundColor ? { backgroundColor: row.backgroundColor } : undefined}
|
||||
>
|
||||
{row.imageUrl ? (
|
||||
@@ -1297,6 +1261,40 @@ function ExtraSectionContent({
|
||||
</CardDescription>
|
||||
{row.percent ? <Badge>{metric(row.percent)}</Badge> : null}
|
||||
</CardHeader>
|
||||
{row.sheet ? (
|
||||
<CardContent
|
||||
className={cn(
|
||||
"pb-5",
|
||||
exporting && "[&_[data-slot=table-container]]:overflow-visible",
|
||||
)}
|
||||
>
|
||||
<Table
|
||||
className={cn(exporting && "table-fixed")}
|
||||
style={exporting ? undefined : { minWidth: `${row.sheet.columns.length * 140}px` }}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{row.sheet.columns.map((column) => (
|
||||
<TableHead key={column.id} className="whitespace-normal">
|
||||
{column.label}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{row.sheet.rows.map((sheetRow) => (
|
||||
<TableRow key={sheetRow.id}>
|
||||
{row.sheet!.columns.map((column) => (
|
||||
<TableCell key={column.id} className="whitespace-pre-wrap">
|
||||
{sheetRow.cells[column.id] ?? ""}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
) : null}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user