feat : update

This commit is contained in:
2026-09-04 23:30:42 +07:00 Unverified
parent 890b88d4f1
commit b4f9fb0abb
12 changed files with 689 additions and 196 deletions
+59 -184
View File
@@ -1,5 +1,6 @@
"use client";
import dynamic from "next/dynamic";
import Image from "next/image";
import {
ChartNoAxesCombinedIcon,
@@ -96,6 +97,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Skeleton } from "@/components/ui/skeleton";
import { Toggle } from "@/components/ui/toggle";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import {
@@ -161,6 +163,7 @@ import {
normalizePercentInput,
} from "@/lib/guides/percent-input";
import { formatGuideValidationIssues } from "@/lib/guides/validation-messages";
import { normalizeExtraRichText } from "@/lib/rich-text";
import { cn } from "@/lib/utils";
type CatalogItem = {
@@ -260,7 +263,13 @@ type TeamValue = {
let clientIdCounter = 0;
const DEFAULT_EXTRA_BACKGROUND_COLOR = "#24232C";
const DEFAULT_EXTRA_HIGHLIGHT_COLOR = "#FF3B30";
const RichTextEditor = dynamic(
() => import("@/components/rich-text-editor").then((module) => module.RichTextEditor),
{
ssr: false,
loading: () => <Skeleton className="h-36 w-full" />,
},
);
function createClientId(prefix: string): string {
const browserCrypto = globalThis.crypto;
@@ -3308,10 +3317,15 @@ function ExtraEditor({ guide, initial }: EditorBase) {
clientId: section.id,
rows: section.rows.map((row) => ({
...row,
body: normalizeExtraRichText(row.body, row.highlights),
highlights: [],
clientId: row.id,
})),
})),
);
const [openSectionIds, setOpenSectionIds] = useState(() =>
initial.extras.map((section) => section.id),
);
const value = {
sections: sections.map((section) => ({
title: section.title,
@@ -3324,7 +3338,7 @@ function ExtraEditor({ guide, initial }: EditorBase) {
? null
: Number(row.percent),
backgroundColor: row.backgroundColor,
highlights: row.highlights.map(({ text, color }) => ({ text, color })),
highlights: [],
})),
})),
};
@@ -3345,20 +3359,23 @@ function ExtraEditor({ guide, initial }: EditorBase) {
localDraft,
parseLocalDraft: (candidate) =>
parseEditorDraft<typeof localDraft>(candidate, ["sections"]),
restoreLocalDraft: (draft) => setSections(
draft.sections.map((section) => ({
restoreLocalDraft: (draft) => {
const restoredSections = draft.sections.map((section) => ({
...section,
id: section.id || createClientId("extra-section-record"),
clientId: section.clientId || createClientId("extra-section"),
rows: section.rows.map((row) => ({
...row,
id: row.id || createClientId("extra-row-record"),
body: normalizeExtraRichText(row.body, row.highlights ?? []),
backgroundColor: row.backgroundColor ?? null,
highlights: row.highlights ?? [],
highlights: [],
clientId: row.clientId || createClientId("extra-row"),
})),
})),
),
}));
setSections(restoredSections);
setOpenSectionIds(restoredSections.map((section) => section.clientId));
},
});
const handleSectionDragEnd = ({ active, over }: DragEndEvent) => {
if (!over || active.id === over.id) return;
@@ -3384,7 +3401,7 @@ function ExtraEditor({ guide, initial }: EditorBase) {
<CardHeader>
<CardTitle>Extra sections</CardTitle>
<CardDescription>
เพิ่มส่วนเสริมแบบภาพ หัวข้อ ข้อความสั้น และเปอร์เซ็นต์
เพิ่มส่วนเสริมแบบภาพ หัวข้อ ข้อความที่จัดรูปแบบได้ และเปอร์เซ็นต์
ระบบจะบันทึกอัตโนมัติ
</CardDescription>
</CardHeader>
@@ -3394,6 +3411,12 @@ function ExtraEditor({ guide, initial }: EditorBase) {
collisionDetection={closestCenter}
onDragEnd={handleSectionDragEnd}
>
<Accordion
multiple
value={openSectionIds}
onValueChange={setOpenSectionIds}
className="gap-5"
>
<SortableContext
items={sections.map((section) => section.clientId)}
strategy={verticalListSortingStrategy}
@@ -3405,6 +3428,7 @@ function ExtraEditor({ guide, initial }: EditorBase) {
label={`ลากเพื่อจัดลำดับ ${section.title || `Extra section ${sectionIndex + 1}`}`}
>
{(sectionDragHandle) => (
<AccordionItem value={section.clientId} className="border-0">
<Card
size="sm"
className={cn(
@@ -3445,8 +3469,15 @@ function ExtraEditor({ guide, initial }: EditorBase) {
<Trash2Icon />
<span className="sr-only">ลบส่วน</span>
</DeleteConfirmationDialog>
<AccordionTrigger
className="size-8 flex-none items-center justify-center p-0 hover:no-underline"
headerClassName="flex-none"
aria-label={`${openSectionIds.includes(section.clientId) ? "ซ่อน" : "แสดง"} ${section.title || `Extra section ${sectionIndex + 1}`}`}
title={openSectionIds.includes(section.clientId) ? "ซ่อนส่วนนี้" : "แสดงส่วนนี้"}
/>
</CardAction>
</CardHeader>
<AccordionContent className="pb-0">
<CardContent className="flex flex-col gap-4">
<DndContext
sensors={extraSensors}
@@ -3624,9 +3655,16 @@ function ExtraEditor({ guide, initial }: EditorBase) {
</Field>
<Field>
<FieldLabel>ข้อความ</FieldLabel>
<Textarea
<RichTextEditor
value={row.body}
onChange={(event) =>
invalid={extraIssue(
"sections",
sectionIndex,
"rows",
rowIndex,
"body",
)}
onChange={(body) =>
setSections(
sections.map((item, index) =>
index === sectionIndex
@@ -3636,7 +3674,8 @@ function ExtraEditor({ guide, initial }: EditorBase) {
inner === rowIndex
? {
...candidate,
body: event.target.value,
body,
highlights: [],
}
: candidate,
),
@@ -3769,175 +3808,6 @@ function ExtraEditor({ guide, initial }: EditorBase) {
ใช้สีตามธีม
</Button>
</FieldSet>
<FieldSet>
<FieldLegend>ไฮไลต์คำหรือวลี</FieldLegend>
<FieldDescription>
ระบุข้อความให้ตรงกับเนื้อหา ระบบจะเปลี่ยนสีทุกตำแหน่งที่พบ
</FieldDescription>
{row.highlights.map((highlight, highlightIndex) => (
<FieldGroup
key={highlightIndex}
className="sm:grid sm:grid-cols-[1fr_auto_auto] sm:items-end"
>
<Field
data-invalid={extraIssue(
"sections",
sectionIndex,
"rows",
rowIndex,
"highlights",
highlightIndex,
"text",
) || undefined}
>
<FieldLabel>คำหรือวลี</FieldLabel>
<Input
value={highlight.text}
placeholder="MASSIVE"
aria-invalid={extraIssue(
"sections",
sectionIndex,
"rows",
rowIndex,
"highlights",
highlightIndex,
"text",
)}
onChange={(event) =>
setSections(
sections.map((item, index) =>
index === sectionIndex
? {
...item,
rows: item.rows.map((candidate, inner) =>
inner === rowIndex
? {
...candidate,
highlights: candidate.highlights.map((rule, ruleIndex) =>
ruleIndex === highlightIndex
? { ...rule, text: event.target.value }
: rule,
),
}
: candidate,
),
}
: item,
),
)
}
/>
</Field>
<Field
data-invalid={extraIssue(
"sections",
sectionIndex,
"rows",
rowIndex,
"highlights",
highlightIndex,
"color",
) || undefined}
>
<FieldLabel>สี</FieldLabel>
<Input
type="color"
value={highlight.color}
aria-invalid={extraIssue(
"sections",
sectionIndex,
"rows",
rowIndex,
"highlights",
highlightIndex,
"color",
)}
onChange={(event) =>
setSections(
sections.map((item, index) =>
index === sectionIndex
? {
...item,
rows: item.rows.map((candidate, inner) =>
inner === rowIndex
? {
...candidate,
highlights: candidate.highlights.map((rule, ruleIndex) =>
ruleIndex === highlightIndex
? { ...rule, color: event.target.value }
: rule,
),
}
: candidate,
),
}
: item,
),
)
}
/>
</Field>
<Button
type="button"
variant="ghost"
size="icon-sm"
aria-label={`ลบไฮไลต์ ${highlight.text || highlightIndex + 1}`}
onClick={() =>
setSections(
sections.map((item, index) =>
index === sectionIndex
? {
...item,
rows: item.rows.map((candidate, inner) =>
inner === rowIndex
? {
...candidate,
highlights: candidate.highlights.filter((_, ruleIndex) => ruleIndex !== highlightIndex),
}
: candidate,
),
}
: item,
),
)
}
>
<Trash2Icon />
</Button>
</FieldGroup>
))}
<Button
type="button"
variant="outline"
size="sm"
disabled={row.highlights.length >= 20}
onClick={() =>
setSections(
sections.map((item, index) =>
index === sectionIndex
? {
...item,
rows: item.rows.map((candidate, inner) =>
inner === rowIndex
? {
...candidate,
highlights: [
...candidate.highlights,
{ text: "", color: DEFAULT_EXTRA_HIGHLIGHT_COLOR },
],
}
: candidate,
),
}
: item,
),
)
}
>
<PlusIcon data-icon="inline-start" />
เพิ่มคำไฮไลต์
</Button>
</FieldSet>
</FieldGroup>
</CardContent>
</Card>
@@ -3981,27 +3851,32 @@ function ExtraEditor({ guide, initial }: EditorBase) {
เพิ่มแถว
</Button>
</CardContent>
</AccordionContent>
</Card>
</AccordionItem>
)}
</SortableExtraItem>
))}
</SortableContext>
</Accordion>
</DndContext>
</CardContent>
<CardFooter className="flex-wrap justify-between gap-3">
<Button
variant="outline"
onClick={() =>
onClick={() => {
const clientId = createClientId("extra-section");
setSections([
...sections,
{
id: createClientId("extra-section-record"),
title: "Extra",
rows: [],
clientId: createClientId("extra-section"),
clientId,
},
])
}
]);
setOpenSectionIds([...openSectionIds, clientId]);
}}
>
<PlusIcon data-icon="inline-start" />
เพิ่ม Extra section