|
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
|
|
Textarea,
|
|
|
|
|
} from "@/components/ui";
|
|
|
|
|
import { EditIcon, PlusIcon, TrashIcon } from "@/components/icons";
|
|
|
|
|
import { toFa } from "@/lib/utils";
|
|
|
|
|
import { MediaPreview } from "@/components/MediaPreview";
|
|
|
|
|
import { ImagePicker } from "@/components/ImagePicker";
|
|
|
|
|
import { MediaPreview } from "@/components/MediaPreview";
|
|
|
|
|
import { pickUrl, IMAGE_KEYS } from "@/lib/media";
|
|
|
|
|
import { toFa } from "@/lib/utils";
|
|
|
|
|
|
|
|
|
|
interface SliderAction {
|
|
|
|
|
path?: string;
|
|
|
|
@@ -37,7 +37,7 @@ interface Slider {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ACTION_TYPES = [
|
|
|
|
|
{ value: "screen", label: "صفحه (screen)" },
|
|
|
|
|
{ value: "navigation", label: "صفحه (navigation)" },
|
|
|
|
|
{ value: "link", label: "لینک (link)" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
@@ -51,7 +51,7 @@ export default function SlidersPage() {
|
|
|
|
|
const [description, setDescription] = useState("");
|
|
|
|
|
const [url, setUrl] = useState("");
|
|
|
|
|
const [actionPath, setActionPath] = useState("");
|
|
|
|
|
const [actionType, setActionType] = useState("screen");
|
|
|
|
|
const [actionType, setActionType] = useState("navigation");
|
|
|
|
|
const [imageId, setImageId] = useState<number | null>(null);
|
|
|
|
|
const [imageFile, setImageFile] = useState<File | null>(null);
|
|
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
@@ -65,7 +65,7 @@ export default function SlidersPage() {
|
|
|
|
|
setDescription("");
|
|
|
|
|
setUrl("");
|
|
|
|
|
setActionPath("");
|
|
|
|
|
setActionType("screen");
|
|
|
|
|
setActionType("navigation");
|
|
|
|
|
setImageId(null);
|
|
|
|
|
setImageFile(null);
|
|
|
|
|
setOpen(true);
|
|
|
|
@@ -76,7 +76,7 @@ export default function SlidersPage() {
|
|
|
|
|
setDescription(row.description ?? "");
|
|
|
|
|
setUrl(row.url ?? "");
|
|
|
|
|
setActionPath(row.action?.path ?? "");
|
|
|
|
|
setActionType(row.action?.type ?? "screen");
|
|
|
|
|
setActionType(row.action?.type ?? "navigation");
|
|
|
|
|
setImageId(row.image_id ?? null);
|
|
|
|
|
setImageFile(null);
|
|
|
|
|
setOpen(true);
|
|
|
|
@@ -86,9 +86,8 @@ export default function SlidersPage() {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setSaving(true);
|
|
|
|
|
try {
|
|
|
|
|
// Both create and edit go through multipart (the image is a file). The
|
|
|
|
|
// nested action uses literal bracket keys; edit posts to /slider/:id,
|
|
|
|
|
// which the backend also accepts as a multipart update.
|
|
|
|
|
// Multipart for both (so an image file can be uploaded). Bracket keys
|
|
|
|
|
// build the nested action object; update uses POST /slider/:id.
|
|
|
|
|
const body = toFormData({
|
|
|
|
|
title,
|
|
|
|
|
description,
|
|
|
|
@@ -102,6 +101,7 @@ export default function SlidersPage() {
|
|
|
|
|
await apiFetch(`/slider/${editing.id}`, { method: "POST", body });
|
|
|
|
|
toast.success("اسلایدر ویرایش شد.");
|
|
|
|
|
} else {
|
|
|
|
|
await apiFetch("/slider", { method: "POST", body });
|
|
|
|
|
await apiFetch("/slider", { method: "POST", body });
|
|
|
|
|
toast.success("اسلایدر افزوده شد.");
|
|
|
|
|
}
|
|
|
|
@@ -132,7 +132,7 @@ export default function SlidersPage() {
|
|
|
|
|
const columns: Column<Slider>[] = [
|
|
|
|
|
{ key: "id", header: "شناسه", render: (r) => toFa(r.id), className: "w-24" },
|
|
|
|
|
{
|
|
|
|
|
key: "thumbnail",
|
|
|
|
|
key: "image",
|
|
|
|
|
header: "تصویر",
|
|
|
|
|
className: "w-20",
|
|
|
|
|
render: (r) => (
|
|
|
|
@@ -236,6 +236,16 @@ export default function SlidersPage() {
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
<Field label="تصویر">
|
|
|
|
|
<ImagePicker
|
|
|
|
|
imageId={imageId}
|
|
|
|
|
onPickId={setImageId}
|
|
|
|
|
file={imageFile}
|
|
|
|
|
onPickFile={setImageFile}
|
|
|
|
|
existingUrl={editing ? pickUrl(editing, IMAGE_KEYS) : null}
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
|
|
|
|
|
<Field label="توضیحات">
|
|
|
|
|
<Textarea
|
|
|
|
|
value={description}
|
|
|
|
|