slider
This commit is contained in:
@@ -83,6 +83,11 @@ export const Pages = {
|
||||
notifications: {
|
||||
list: "/notifications/list",
|
||||
},
|
||||
sliders: {
|
||||
list: "/sliders/list",
|
||||
add: "/sliders/create",
|
||||
update: "/sliders/update/",
|
||||
},
|
||||
statistics: {
|
||||
list: "/statistics",
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ export const MENU_PREVIEW_RESERVED_SEGMENTS = new Set([
|
||||
"reviews",
|
||||
"pagers",
|
||||
"notifications",
|
||||
"sliders",
|
||||
"assets",
|
||||
"menu-preview",
|
||||
]);
|
||||
|
||||
+2
-1
@@ -68,7 +68,8 @@
|
||||
"reviews": "نظرات",
|
||||
"pagers": "پیجرها",
|
||||
"notifications": "تنظیمات اعلانات",
|
||||
"statistics": "گزارشات"
|
||||
"statistics": "گزارشات",
|
||||
"sliders": "اسلایدرها"
|
||||
},
|
||||
"shipment": {
|
||||
"dineIn": "سرو در رستوران",
|
||||
|
||||
@@ -208,9 +208,9 @@ export const useCreateOrderForm = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const registerNewCustomer = async (): Promise<string | null> => {
|
||||
const registerNewCustomer = async (): Promise<string | undefined> => {
|
||||
if (!isNewCustomer) {
|
||||
return customerUserId
|
||||
return customerUserId ?? undefined
|
||||
}
|
||||
|
||||
const response = await createUser({
|
||||
|
||||
@@ -26,4 +26,5 @@ export const enum PermissionEnum {
|
||||
MANAGE_SCHEDULES = "manage_schedules",
|
||||
MANAGE_REPORTS = "manage_reports",
|
||||
MANAGE_CONTACTS = "manage_contacts",
|
||||
MANAGE_SLIDERS = "manage_sliders",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { type FC, useState } from "react";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { TickCircle } from "iconsax-react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Button from "@/components/Button";
|
||||
import Input from "@/components/Input";
|
||||
import { Pages } from "@/config/Pages";
|
||||
import type { ErrorType } from "@/helpers/types";
|
||||
import { extractErrorMessage } from "@/config/func";
|
||||
import { useCreateSlider } from "./hooks/useSliderData";
|
||||
import { useSingleUpload } from "../uploader/hooks/useUploaderData";
|
||||
import type { CreateSliderType } from "./types/Types";
|
||||
import SliderSidebar from "./components/SliderSidebar";
|
||||
|
||||
const CreateSlider: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { mutate: createSlider, isPending } = useCreateSlider();
|
||||
const { mutate: singleUpload, isPending: isUploading } = useSingleUpload();
|
||||
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
||||
|
||||
const formik = useFormik<CreateSliderType>({
|
||||
initialValues: {
|
||||
imageUrl: "",
|
||||
title: "",
|
||||
link: "",
|
||||
order: 0,
|
||||
isActive: true,
|
||||
},
|
||||
validationSchema: Yup.object().shape({
|
||||
title: Yup.string().required("عنوان الزامی است"),
|
||||
link: Yup.string()
|
||||
.transform((value) => (value?.trim() === "" ? undefined : value))
|
||||
.url("لینک معتبر نیست")
|
||||
.notRequired(),
|
||||
order: Yup.number().required("ترتیب الزامی است").min(0, "ترتیب باید مثبت باشد"),
|
||||
isActive: Yup.boolean(),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
if (imageFiles.length === 0) {
|
||||
toast.error("تصویر اسلایدر الزامی است");
|
||||
return;
|
||||
}
|
||||
|
||||
const submitSlider = (imageUrl: string) => {
|
||||
createSlider(
|
||||
{
|
||||
...values,
|
||||
imageUrl,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success("اسلایدر با موفقیت ایجاد شد");
|
||||
navigate(Pages.sliders.list);
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error));
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
singleUpload(imageFiles[0], {
|
||||
onSuccess: (response) => {
|
||||
const uploadedImageUrl = response?.data?.url || "";
|
||||
if (!uploadedImageUrl) {
|
||||
toast.error("آدرس تصویر آپلود شده دریافت نشد");
|
||||
return;
|
||||
}
|
||||
submitSlider(uploadedImageUrl);
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error, "خطا در آپلود تصویر"));
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4">
|
||||
<div className="text-lg font-light">افزودن اسلایدر</div>
|
||||
<Button
|
||||
className="px-5 w-full sm:w-auto"
|
||||
onClick={async () => {
|
||||
const errors = await formik.validateForm();
|
||||
formik.setTouched({
|
||||
title: true,
|
||||
link: true,
|
||||
order: true,
|
||||
});
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
const firstError = Object.values(errors).find((error) => typeof error === "string");
|
||||
if (firstError) toast.error(firstError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageFiles.length === 0) {
|
||||
toast.error("تصویر اسلایدر الزامی است");
|
||||
return;
|
||||
}
|
||||
|
||||
formik.handleSubmit();
|
||||
}}
|
||||
isloading={isPending || isUploading}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<TickCircle className="size-5" color="white" />
|
||||
<div>ثبت اسلایدر</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 mt-6">
|
||||
<div className="flex-1">
|
||||
<div className="bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl">
|
||||
<Input
|
||||
label="عنوان"
|
||||
name="title"
|
||||
value={formik.values.title}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ""}
|
||||
/>
|
||||
<div className="mt-5">
|
||||
<Input
|
||||
label="لینک"
|
||||
name="link"
|
||||
value={formik.values.link}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.link && formik.errors.link ? formik.errors.link : ""}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Input
|
||||
label="ترتیب"
|
||||
name="order"
|
||||
type="number"
|
||||
value={formik.values.order ?? ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value === "" ? 0 : Number(e.target.value);
|
||||
formik.setFieldValue("order", value);
|
||||
}}
|
||||
error_text={formik.touched.order && formik.errors.order ? formik.errors.order : ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-auto">
|
||||
<SliderSidebar
|
||||
isActive={formik.values.isActive}
|
||||
onChangeActive={(value) => formik.setFieldValue("isActive", value)}
|
||||
onChangeImage={setImageFiles}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateSlider;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { type FC } from "react";
|
||||
import Table from "@/components/Table";
|
||||
import Filters from "@/components/Filters";
|
||||
import { Add } from "iconsax-react";
|
||||
import Button from "@/components/Button";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Pages } from "@/config/Pages";
|
||||
import { useGetSliders, useDeleteSlider } from "./hooks/useSliderData";
|
||||
import { useSliderFilters } from "./hooks/useSliderFilters";
|
||||
import { getSliderTableColumns } from "./components/SliderTableColumns";
|
||||
import { useSliderFiltersFields } from "./components/SliderFiltersFields";
|
||||
|
||||
const SlidersList: FC = () => {
|
||||
const {
|
||||
filters,
|
||||
currentPage,
|
||||
apiParams,
|
||||
handleFiltersChange,
|
||||
handlePageChange,
|
||||
limit,
|
||||
} = useSliderFilters();
|
||||
|
||||
const { data: slidersData, isLoading } = useGetSliders(apiParams);
|
||||
const { mutate: deleteSlider, isPending: isDeleting } = useDeleteSlider();
|
||||
|
||||
const sliders = slidersData?.data || [];
|
||||
const columns = getSliderTableColumns({
|
||||
onDelete: (id: string) => deleteSlider(id),
|
||||
isDeleting,
|
||||
});
|
||||
const filterFields = useSliderFiltersFields();
|
||||
const totalPages = Math.ceil(sliders.length / limit) || 1;
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-lg font-light">لیست اسلایدرها</h1>
|
||||
<Link to={Pages.sliders.add}>
|
||||
<Button className="w-fit px-6">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Add color="#fff" size={20} />
|
||||
<span>افزودن اسلایدر</span>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<Filters
|
||||
fields={filterFields}
|
||||
onChange={handleFiltersChange}
|
||||
initialValues={filters}
|
||||
searchField="search"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
data={sliders}
|
||||
isloading={isLoading}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange: handlePageChange,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SlidersList;
|
||||
@@ -0,0 +1,199 @@
|
||||
import { type FC, useMemo, useState } from "react";
|
||||
import { useFormik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { TickCircle } from "iconsax-react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import Button from "@/components/Button";
|
||||
import Input from "@/components/Input";
|
||||
import { Pages } from "@/config/Pages";
|
||||
import type { ErrorType } from "@/helpers/types";
|
||||
import { extractErrorMessage } from "@/config/func";
|
||||
import { useGetSlider, useUpdateSlider } from "./hooks/useSliderData";
|
||||
import { useSingleUpload } from "../uploader/hooks/useUploaderData";
|
||||
import type { CreateSliderType } from "./types/Types";
|
||||
import SliderSidebar from "./components/SliderSidebar";
|
||||
|
||||
const UpdateSlider: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { mutate: updateSlider, isPending } = useUpdateSlider();
|
||||
const { mutate: singleUpload, isPending: isUploading } = useSingleUpload();
|
||||
const { data: sliderData, isLoading } = useGetSlider(id || "");
|
||||
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
||||
|
||||
const initialValues = useMemo<CreateSliderType>(() => {
|
||||
if (sliderData?.data) {
|
||||
return {
|
||||
imageUrl: sliderData.data.imageUrl || "",
|
||||
title: sliderData.data.title || "",
|
||||
link: sliderData.data.link || "",
|
||||
order: sliderData.data.order ?? 0,
|
||||
isActive: sliderData.data.isActive ?? true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
imageUrl: "",
|
||||
title: "",
|
||||
link: "",
|
||||
order: 0,
|
||||
isActive: true,
|
||||
};
|
||||
}, [sliderData]);
|
||||
|
||||
const formik = useFormik<CreateSliderType>({
|
||||
initialValues,
|
||||
enableReinitialize: true,
|
||||
validationSchema: Yup.object().shape({
|
||||
title: Yup.string().required("عنوان الزامی است"),
|
||||
link: Yup.string()
|
||||
.transform((value) => (value?.trim() === "" ? undefined : value))
|
||||
.url("لینک معتبر نیست")
|
||||
.notRequired(),
|
||||
order: Yup.number().required("ترتیب الزامی است").min(0, "ترتیب باید مثبت باشد"),
|
||||
isActive: Yup.boolean(),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
if (!id) return;
|
||||
|
||||
if (imageFiles.length === 0 && !values.imageUrl) {
|
||||
toast.error("تصویر اسلایدر الزامی است");
|
||||
return;
|
||||
}
|
||||
|
||||
const submitSlider = (imageUrl: string) => {
|
||||
updateSlider(
|
||||
{
|
||||
id,
|
||||
params: {
|
||||
...values,
|
||||
imageUrl,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success("اسلایدر با موفقیت ویرایش شد");
|
||||
navigate(Pages.sliders.list);
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error));
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (imageFiles.length > 0) {
|
||||
singleUpload(imageFiles[0], {
|
||||
onSuccess: (response) => {
|
||||
const uploadedImageUrl = response?.data?.url || "";
|
||||
if (!uploadedImageUrl) {
|
||||
toast.error("آدرس تصویر آپلود شده دریافت نشد");
|
||||
return;
|
||||
}
|
||||
submitSlider(uploadedImageUrl);
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error, "خطا در آپلود تصویر"));
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
submitSlider(values.imageUrl);
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="text-lg font-light">در حال بارگذاری...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-5">
|
||||
<div className="flex flex-col sm:flex-row w-full justify-between items-start sm:items-center gap-4">
|
||||
<div className="text-lg font-light">ویرایش اسلایدر</div>
|
||||
<Button
|
||||
className="px-5 w-full sm:w-auto"
|
||||
onClick={async () => {
|
||||
const errors = await formik.validateForm();
|
||||
formik.setTouched({
|
||||
title: true,
|
||||
link: true,
|
||||
order: true,
|
||||
});
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
const firstError = Object.values(errors).find((error) => typeof error === "string");
|
||||
if (firstError) toast.error(firstError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageFiles.length === 0 && !formik.values.imageUrl) {
|
||||
toast.error("تصویر اسلایدر الزامی است");
|
||||
return;
|
||||
}
|
||||
|
||||
formik.handleSubmit();
|
||||
}}
|
||||
isloading={isPending || isUploading}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<TickCircle className="size-5" color="white" />
|
||||
<div>ثبت تغییرات</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:flex-row gap-6 mt-6">
|
||||
<div className="flex-1">
|
||||
<div className="bg-white py-6 sm:py-8 xl:px-10 px-4 rounded-3xl">
|
||||
<Input
|
||||
label="عنوان"
|
||||
name="title"
|
||||
value={formik.values.title}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ""}
|
||||
/>
|
||||
<div className="mt-5">
|
||||
<Input
|
||||
label="لینک"
|
||||
name="link"
|
||||
value={formik.values.link}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.link && formik.errors.link ? formik.errors.link : ""}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<Input
|
||||
label="ترتیب"
|
||||
name="order"
|
||||
type="number"
|
||||
value={formik.values.order ?? ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value === "" ? 0 : Number(e.target.value);
|
||||
formik.setFieldValue("order", value);
|
||||
}}
|
||||
error_text={formik.touched.order && formik.errors.order ? formik.errors.order : ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-auto">
|
||||
<SliderSidebar
|
||||
isActive={formik.values.isActive}
|
||||
onChangeActive={(value) => formik.setFieldValue("isActive", value)}
|
||||
onChangeImage={setImageFiles}
|
||||
preview={formik.values.imageUrl ? [formik.values.imageUrl] : []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateSlider;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { useMemo } from "react";
|
||||
import type { FieldType } from "@/components/Filters";
|
||||
|
||||
export const useSliderFiltersFields = (): FieldType[] => {
|
||||
return useMemo(
|
||||
() => [
|
||||
{
|
||||
type: "input",
|
||||
name: "search",
|
||||
placeholder: "جستجو",
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { type FC } from "react";
|
||||
import UploadBoxDraggble from "@/components/UploadBoxDraggble";
|
||||
import Switch from "@/components/Switch";
|
||||
|
||||
type Props = {
|
||||
isActive: boolean;
|
||||
onChangeActive: (value: boolean) => void;
|
||||
onChangeImage: (files: File[]) => void;
|
||||
preview?: string[];
|
||||
};
|
||||
|
||||
const SliderSidebar: FC<Props> = (props) => {
|
||||
return (
|
||||
<div className="bg-white w-full lg:w-[300px] text-xs h-fit px-5 py-7 rounded-3xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>وضعیت اسلایدر</div>
|
||||
<Switch
|
||||
active={props.isActive}
|
||||
onChange={(value) => props.onChangeActive(value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<div className="text-sm mb-2">تصویر اسلایدر</div>
|
||||
<UploadBoxDraggble
|
||||
label="تصویر اسلایدر را آپلود کنید"
|
||||
onChange={(files) => props.onChangeImage(files)}
|
||||
preview={props.preview}
|
||||
imageSize="large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SliderSidebar;
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { ColumnType } from "@/components/types/TableTypes";
|
||||
import type { Slider } from "../types/Types";
|
||||
import TrashWithConfrim from "@/components/TrashWithConfrim";
|
||||
import { Eye } from "iconsax-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Pages } from "@/config/Pages";
|
||||
import Status from "@/components/Status";
|
||||
|
||||
interface GetSliderTableColumnsParams {
|
||||
onDelete?: (id: string) => void;
|
||||
isDeleting?: boolean;
|
||||
}
|
||||
|
||||
export const getSliderTableColumns = ({
|
||||
onDelete,
|
||||
isDeleting,
|
||||
}: GetSliderTableColumnsParams = {}): ColumnType<Slider>[] => {
|
||||
return [
|
||||
{
|
||||
key: "title",
|
||||
title: "عنوان",
|
||||
render: (item: Slider) => item.title || "-",
|
||||
},
|
||||
{
|
||||
key: "imageUrl",
|
||||
title: "تصویر",
|
||||
render: (item: Slider) => (
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
className="w-16 h-10 rounded object-cover"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "link",
|
||||
title: "لینک",
|
||||
render: (item: Slider) => item.link || "-",
|
||||
},
|
||||
{
|
||||
key: "order",
|
||||
title: "ترتیب",
|
||||
render: (item: Slider) => item.order,
|
||||
},
|
||||
{
|
||||
key: "isActive",
|
||||
title: "وضعیت",
|
||||
render: (item: Slider) => (
|
||||
<Status
|
||||
variant={item.isActive ? "success" : "error"}
|
||||
label={item.isActive ? "فعال" : "غیرفعال"}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "actions",
|
||||
title: "",
|
||||
render: (item: Slider) => (
|
||||
<div className="flex gap-2 items-center">
|
||||
<Link to={Pages.sliders.update + item.id}>
|
||||
<Eye size={20} color="#8C90A3" className="cursor-pointer" />
|
||||
</Link>
|
||||
{onDelete && (
|
||||
<TrashWithConfrim
|
||||
onDelete={() => onDelete(item.id)}
|
||||
isloading={isDeleting}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/SliderService";
|
||||
import type { CreateSliderType, GetSlidersParams } from "../types/Types";
|
||||
|
||||
export const useGetSliders = (params?: GetSlidersParams) => {
|
||||
return useQuery({
|
||||
queryKey: ["sliders", params],
|
||||
queryFn: () => api.getSliders(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateSlider = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.createSlider,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["sliders"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteSlider = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.deleteSlider,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["sliders"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetSlider = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["slider", id],
|
||||
queryFn: () => api.getSlider(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateSlider = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, params }: { id: string; params: CreateSliderType }) =>
|
||||
api.updateSlider(id, params),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["sliders"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import type { FilterValues } from "@/components/Filters";
|
||||
import type { GetSlidersParams } from "../types/Types";
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const DEFAULT_LIMIT = 10;
|
||||
|
||||
export const useSliderFilters = () => {
|
||||
const [filters, setFilters] = useState<FilterValues>({});
|
||||
const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE);
|
||||
const [limit] = useState(DEFAULT_LIMIT);
|
||||
|
||||
const apiParams = useMemo<GetSlidersParams>(() => {
|
||||
const params: GetSlidersParams = {
|
||||
page: currentPage,
|
||||
limit,
|
||||
};
|
||||
|
||||
if (filters.search) {
|
||||
params.search = filters.search as string;
|
||||
}
|
||||
|
||||
return params;
|
||||
}, [filters, currentPage, limit]);
|
||||
|
||||
const handleFiltersChange = (newFilters: FilterValues) => {
|
||||
setFilters(newFilters);
|
||||
setCurrentPage(DEFAULT_PAGE);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
return {
|
||||
filters,
|
||||
currentPage,
|
||||
apiParams,
|
||||
handleFiltersChange,
|
||||
handlePageChange,
|
||||
limit,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import axios from "@/config/axios";
|
||||
import type {
|
||||
CreateSliderType,
|
||||
GetSlidersParams,
|
||||
SliderResponse,
|
||||
SlidersResponse,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getSliders = async (
|
||||
params?: GetSlidersParams
|
||||
): Promise<SlidersResponse> => {
|
||||
const { data } = await axios.get<SlidersResponse>("/admin/sliders", { params });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createSlider = async (params: CreateSliderType) => {
|
||||
const { data } = await axios.post("/admin/sliders", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteSlider = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/sliders/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getSlider = async (id: string): Promise<SliderResponse> => {
|
||||
const { data } = await axios.get<SliderResponse>(`/admin/sliders/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateSlider = async (id: string, params: CreateSliderType) => {
|
||||
const { data } = await axios.patch(`/admin/sliders/${id}`, params);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { IResponse } from "@/types/response.types";
|
||||
|
||||
export type Slider = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
imageUrl: string;
|
||||
title: string;
|
||||
link: string;
|
||||
order: number;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
export type SlidersResponse = IResponse<Slider[]>;
|
||||
export type SliderResponse = IResponse<Slider>;
|
||||
|
||||
export type CreateSliderType = {
|
||||
imageUrl: string;
|
||||
title: string;
|
||||
link?: string;
|
||||
order: number;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
export type GetSlidersParams = {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
};
|
||||
@@ -47,6 +47,9 @@ import NotificationsList from '@/pages/notifications/List'
|
||||
import Notification from '@/components/Notification'
|
||||
import ReportsList from '@/pages/report/List'
|
||||
import Statistics from '@/pages/statistics/Statistics'
|
||||
import SlidersList from '@/pages/sliders/List'
|
||||
import CreateSlider from '@/pages/sliders/Create'
|
||||
import UpdateSlider from '@/pages/sliders/Update'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
@@ -112,6 +115,9 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.pagers.list} element={<PagersList />} />
|
||||
|
||||
<Route path={Pages.notifications.list} element={<NotificationsList />} />
|
||||
<Route path={Pages.sliders.list} element={<SlidersList />} />
|
||||
<Route path={Pages.sliders.add} element={<CreateSlider />} />
|
||||
<Route path={Pages.sliders.update + ':id'} element={<UpdateSlider />} />
|
||||
|
||||
<Route path={Pages.reports.list} element={<ReportsList />} />
|
||||
|
||||
|
||||
+24
-15
@@ -1,21 +1,21 @@
|
||||
import { type FC, useEffect } from 'react'
|
||||
import LogoImage from '../assets/images/logo.png'
|
||||
import LogoSmall from '../assets/images/logo-small.png'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DocumentText, Home2, Logout, People, Setting2, TicketDiscount, Calendar, Security, Card, SecurityUser, TruckFast, Element3, Star1, NotificationBing, SmsEdit } from 'iconsax-react'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { Pages } from '../config/Pages'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
import { clx } from '../helpers/utils'
|
||||
import FoodsSubMenu from './components/FoodsSubMenu'
|
||||
import OrdersSubMenu from './components/OrdersSubMenu'
|
||||
import CustomersSubMenu from './components/CustomersSubMenu'
|
||||
import DiscountSubMenu from './components/DiscountSubMenu'
|
||||
import { SideBarItemHasSubMenu } from '../config/SideBarSubMenu'
|
||||
import { usePermissions } from '@/hooks/usePermissions'
|
||||
import { PermissionEnum } from '@/pages/roles/enum/Enum'
|
||||
import { Calendar, Card, DocumentText, Element3, Gallery, Home2, Logout, NotificationBing, People, Security, SecurityUser, Setting2, SmsEdit, Star1, TicketDiscount, TruckFast } from 'iconsax-react'
|
||||
import { type FC, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Skeleton from 'react-loading-skeleton'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import LogoSmall from '../assets/images/logo-small.png'
|
||||
import LogoImage from '../assets/images/logo.png'
|
||||
import { Pages } from '../config/Pages'
|
||||
import { SideBarItemHasSubMenu } from '../config/SideBarSubMenu'
|
||||
import { clx } from '../helpers/utils'
|
||||
import CustomersSubMenu from './components/CustomersSubMenu'
|
||||
import DiscountSubMenu from './components/DiscountSubMenu'
|
||||
import FoodsSubMenu from './components/FoodsSubMenu'
|
||||
import OrdersSubMenu from './components/OrdersSubMenu'
|
||||
import SideBarItem from './SideBarItem'
|
||||
import { useSharedStore } from './store/sharedStore'
|
||||
|
||||
const SideBar: FC = () => {
|
||||
|
||||
@@ -289,6 +289,15 @@ const SideBar: FC = () => {
|
||||
activeName='reports'
|
||||
/>
|
||||
)}
|
||||
{checkPermission(PermissionEnum.MANAGE_SLIDERS) && (
|
||||
<SideBarItem
|
||||
icon={<Gallery variant={isActive('sliders') ? 'Bold' : 'Outline'} color={isActive('sliders') ? 'black' : '#8C90A3'} size={iconSizeSideBar} />}
|
||||
title={t('sidebar.sliders')}
|
||||
isActive={isActive('sliders')}
|
||||
link={Pages.sliders.list}
|
||||
activeName='sliders'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user