feat : update
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user