feat: domains management

This commit is contained in:
2026-02-23 16:33:34 +03:30
parent b8ae9757d4
commit d7d4848f2a
13 changed files with 223 additions and 4 deletions

View File

@@ -0,0 +1,133 @@
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { AddAccess } from "./AddAccess";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { CheckIcon, XMarkIcon } from "@heroicons/react/24/solid";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
export default function Access() {
const { openModal } = useModalStore();
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
const [pagesTableData, setPagesTableData] = useState([]);
const [selectedModifyKeys, setSelectedModifyKeys] = useState<
(string | number)[]
>([]);
const { data: pagesData, refetch } = useApiRequest({
api: "/auth/api/v1/permission/",
method: "get",
params: {
...pagesInfo,
modify_state: selectedModifyKeys[0],
},
queryKey: ["permissions", pagesInfo, selectedModifyKeys],
});
useEffect(() => {
if (pagesData?.results) {
const formattedData = pagesData.results.map((item: any, i: number) => {
return [
pagesInfo.page === 1
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.name,
item?.description,
`${getFaPermissions(item?.page)} (${item?.page})`,
<Grid
key={i}
className="flex justify-center items-center bg-gray-300/10 rounded-full"
>
{item?.modify_state ? (
<CheckIcon className="w-5 h-5 text-green-500" />
) : (
<XMarkIcon className="w-5 h-5 text-red-500" />
)}
</Grid>,
<Popover key={i}>
<Tooltip title="ویرایش" position="right">
<Button
variant="edit"
page="permission_control"
access="Post-access"
onClick={() => {
openModal({
title: "ویرایش دسترسی",
content: <AddAccess item={item} getData={refetch} />,
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
page="permission_control"
access="Post-access"
api={`auth/api/v1/permission/${item?.id}/`}
getData={refetch}
/>
</Popover>,
];
});
setPagesTableData(formattedData);
}
}, [pagesData, pagesInfo]);
return (
<Grid container column>
<Grid container className="items-center gap-2">
<Button
size="small"
variant="submit"
page="permission_control"
access="Post-access"
onClick={() => {
openModal({
title: "ایجاد دسترسی",
content: <AddAccess getData={refetch} />,
});
}}
>
ایجاد دسترسی
</Button>
<AutoComplete
inPage
size="small"
data={[
{ key: "", value: "کل موارد" },
{ key: "true", value: "داخلی" },
{ key: "false", value: "خارجی" },
]}
selectedKeys={selectedModifyKeys}
onChange={(keys) => {
setSelectedModifyKeys(keys);
setPagesInfo((prev) => ({ ...prev, page: 1 }));
}}
title="فیلتر نوع دسترسی"
/>
</Grid>
<Grid className="w-full">
<Table
className="mt-2"
onChange={setPagesInfo}
count={pagesData?.count || 10}
isPaginated
title="مدیریت دسترسی ها"
columns={[
"ردیف",
"دسترسی",
"توضیحات",
"صفحه",
"دسترسی داخلی",
"عملیات",
]}
rows={pagesTableData}
/>
</Grid>
</Grid>
);
}

View File

@@ -0,0 +1,196 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateString } from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import Checkbox from "../../components/CheckBox/CheckBox";
type FormValues = z.infer<typeof schema>;
type AddAccessProps = {
getData: () => void;
item?: any;
};
const schema = z.object({
access: zValidateString("نام صفحه"),
description: zValidateString("توضیحات"),
selectedPageId: z
.array(z.union([z.string(), z.number()]))
.min(1, { message: "لطفاً یک صفحه انتخاب کنید." }),
modify_state: z.boolean(),
});
export const AddAccess = ({ getData, item }: AddAccessProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([]);
const [data, setData] = useState<any>([]);
const [pagesData, setPagesData] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
access: item?.name || "",
description: item?.description || "",
selectedPageId: [],
modify_state: item?.modify_state || false,
},
});
useEffect(() => {
if (selectedKeys.length > 0) {
setValue("selectedPageId", selectedKeys);
}
}, [selectedKeys, setValue]);
useEffect(() => {
if (pagesData?.results && item?.page) {
const matchingPage = pagesData.results.find(
(page: any) => page.name === item.page,
);
if (matchingPage) {
const keys = [matchingPage.id];
setSelectedKeys(keys);
setValue("selectedPageId", keys);
}
}
}, [pagesData, item, setValue]);
const handleChangeComplete = (newSelectedKeys: (number | string)[]) => {
setSelectedKeys(newSelectedKeys);
};
const mutationPages = useApiMutation({
api: "/auth/api/v1/page/",
method: "get",
});
const mutation = useApiMutation({
api: `/auth/api/v1/permission/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const getPages = async () => {
const data = await mutationPages.mutateAsync({
page: 1,
page_size: 1000,
});
setPagesData(data);
};
useEffect(() => {
getPages();
}, []);
useEffect(() => {
if (pagesData?.results) {
const d = pagesData.results.map((page: any) => ({
key: page.id,
value: `${getFaPermissions(page.name)} (${page.name})`,
}));
setData(d);
}
}, [pagesData]);
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
name: data.access,
description: data.description,
category: "api",
meta: {},
page: selectedKeys[0],
modify_state: data?.modify_state,
});
showToast("عملیات با موفقیت انجام شد", "success");
closeModal();
getData();
} catch (error: any) {
if (error.status === 400) {
showToast("این دسترسی تکراری است!", "error");
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="selectedPageId"
control={control}
render={({ field }) => (
<AutoComplete
data={data}
selectedKeys={selectedKeys}
onChange={(newSelectedKeys) => {
handleChangeComplete(newSelectedKeys);
field.onChange(newSelectedKeys);
}}
title="انتخاب صفحه"
error={!!errors.selectedPageId}
helperText={errors.selectedPageId?.message}
/>
)}
/>
<Controller
name="access"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام دسترسی"
value={field.value}
onChange={field.onChange}
error={!!errors.access}
helperText={errors.access?.message}
/>
)}
/>
<Controller
name="description"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="توضیحات"
value={field.value}
onChange={field.onChange}
error={!!errors.description}
helperText={errors.description?.message}
/>
)}
/>
<Controller
name="modify_state"
control={control}
render={({ field }) => (
<Checkbox
checked={field.value}
onChange={field.onChange}
label="دسترسی داخلی"
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -0,0 +1,125 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import {
zValidateString,
zValidateEnglishString,
} from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import { getToastResponse } from "../../data/getToastResponse";
const schema = z.object({
code: zValidateString("کد حوزه"),
name: zValidateEnglishString("نام انگلیسی حوزه"),
fa_name: zValidateString("نام حوزه"),
});
type AddDomainProps = {
getData: () => void;
item?: any;
};
type FormValues = z.infer<typeof schema>;
export const AddDomain = ({ getData, item }: AddDomainProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
code: item?.code || "",
name: item?.name || "",
fa_name: item?.fa_name || "",
},
});
const mutation = useApiMutation({
api: `/core/domain/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
code: data.code,
name: data.name,
fa_name: data.fa_name,
});
showToast(getToastResponse(item, "حوزه"), "success");
closeModal();
getData();
} catch (error: any) {
if (error?.status === 403) {
showToast(
error?.response?.data?.message || "این مورد تکراری است!",
"error",
);
} else {
showToast(
error?.response?.data?.message || "خطا در ثبت اطلاعات!",
"error",
);
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="code"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="کد حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.code}
helperText={errors.code?.message}
/>
)}
/>
<Controller
name="name"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام انگلیسی حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
<Controller
name="fa_name"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام حوزه"
value={field.value}
onChange={field.onChange}
error={!!errors.fa_name}
helperText={errors.fa_name?.message}
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -0,0 +1,90 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import Textfield from "../../components/Textfeild/Textfeild";
import { useForm, Controller } from "react-hook-form";
import { zValidateEnglishString } from "../../data/getFormTypeErrors";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
const schema = z.object({
page: zValidateEnglishString("نام صفحه"),
});
type AddPageProps = {
getData: () => void;
item?: any;
};
type FormValues = z.infer<typeof schema>;
export const AddPage = ({ getData, item }: AddPageProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
page: item?.name || "",
},
});
const mutation = useApiMutation({
api: `/auth/api/v1/page/${item ? item?.id + "/" : ""}`,
method: item ? "put" : "post",
});
const onSubmit = async (data: FormValues) => {
try {
const payload = {
name: data.page,
code: data.page + ".view",
};
if (item) {
await mutation.mutateAsync({
...payload,
id: item.id,
});
} else {
await mutation.mutateAsync(payload);
}
showToast("عملیات با موفقیت انجام شد", "success");
closeModal();
getData();
} catch (error: any) {
if (error.status === 400) {
showToast("این صفحه تکراری است!", "error");
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2">
<Controller
name="page"
control={control}
render={({ field }) => (
<Textfield
fullWidth
placeholder="نام صفحه"
value={field.value}
onChange={field.onChange}
error={!!errors.page}
helperText={errors.page?.message}
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -0,0 +1,161 @@
import { zodResolver } from "@hookform/resolvers/zod";
import Button from "../../components/Button/Button";
import { Grid } from "../../components/Grid/Grid";
import { useForm, Controller } from "react-hook-form";
import { z } from "zod";
import { useApiMutation } from "../../utils/useApiRequest";
import { useToast } from "../../hooks/useToast";
import { useModalStore } from "../../context/zustand-store/appStore";
import AutoComplete from "../../components/AutoComplete/AutoComplete";
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { useFetchProfile } from "../../hooks/useFetchProfile";
type FormValues = z.infer<typeof schema>;
type AddAccessProps = {
getData: () => void;
item?: any;
};
const schema = z.object({
selectedAccessId: z
.array(z.union([z.string(), z.number()]))
.min(1, { message: "لطفاً یک دسترسی را انتخاب کنید." }),
});
export const EditAccess = ({ getData, item }: AddAccessProps) => {
const showToast = useToast();
const { closeModal } = useModalStore();
const { getProfile } = useFetchProfile();
const [selectedKeys, setSelectedKeys] = useState<(string | number)[]>([]);
const [data, setData] = useState<any>([]);
const [accessData, setAccessData] = useState<any>(null);
const {
control,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
selectedAccessId: [],
},
});
useEffect(() => {
if (selectedKeys.length > 0) {
setValue("selectedAccessId", selectedKeys);
}
}, [selectedKeys, setValue]);
useEffect(() => {
if (accessData?.results && item?.permissions) {
const permissionPageAccesses = item.permissions.flatMap(
(option: any) => option.page_access || [],
);
const matchingPages = accessData.results.filter((page: any) =>
permissionPageAccesses.includes(page.name),
);
const matchingIds = matchingPages.map((page: any) => page.id);
if (matchingIds.length > 0) {
setSelectedKeys(matchingIds);
setValue("selectedAccessId", matchingIds);
} else {
setSelectedKeys([]);
setValue("selectedAccessId", []);
}
}
}, [accessData, item, setValue]);
const handleChangeComplete = (newSelectedKeys: (number | string)[]) => {
setSelectedKeys(newSelectedKeys);
};
const mutationUserAccess = useApiMutation({
api: "/auth/api/v1/permission/",
method: "get",
});
const mutation = useApiMutation({
api: `/auth/api/v1/user-relations/${item ? item?.id + "/" : ""}`,
method: "put",
});
const getPages = async () => {
const data = await mutationUserAccess.mutateAsync({
page: 1,
page_size: 10000,
});
setAccessData(data);
};
useEffect(() => {
getPages();
}, []);
useEffect(() => {
if (accessData?.results) {
const sortedResults = [...accessData.results].sort((a, b) => {
if (a.page < b.page) return -1;
if (a.page > b.page) return 1;
return 0;
});
const d = sortedResults.map((access: any) => ({
key: access.id,
value: `${getFaPermissions(access.page)} - ${access?.description} (${
access?.name
}) `,
}));
setData(d);
}
}, [accessData]);
const onSubmit = async (data: FormValues) => {
try {
await mutation.mutateAsync({
user: item?.user?.id,
permissions: data.selectedAccessId,
});
showToast("عملیات با موفقیت انجام شد", "success");
closeModal();
getData();
getProfile();
} catch (error: any) {
if (error.status === 400) {
showToast("مشکلی پیش آمده است!", "error");
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container column className="gap-2 overflow-hidden">
<Controller
name="selectedAccessId"
control={control}
render={({ field }) => (
<AutoComplete
multiselect
data={data}
selectedKeys={selectedKeys}
onChange={(newSelectedKeys) => {
handleChangeComplete(newSelectedKeys);
field.onChange(newSelectedKeys);
}}
title="انتخاب دسترسی"
error={!!errors.selectedAccessId}
helperText={errors.selectedAccessId?.message}
/>
)}
/>
<Button type="submit">ثبت</Button>
</Grid>
</form>
);
};

View File

@@ -0,0 +1,118 @@
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { AddPage } from "./AddPage";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
import { ArrowPathIcon } from "@heroicons/react/24/outline";
import { BooleanQuestion } from "../../components/BooleanQuestion/BooleanQuestion";
export default function Pages() {
const { openModal } = useModalStore();
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
const [pagesTableData, setPagesTableData] = useState([]);
const { data: pagesData, refetch } = useApiRequest({
api: "/auth/api/v1/page/",
method: "get",
params: pagesInfo,
queryKey: ["pages", pagesInfo],
});
useEffect(() => {
if (pagesData?.results) {
const formattedData = pagesData.results.map((item: any, i: number) => {
return [
pagesInfo.page === 1
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
getFaPermissions(item?.name),
item?.name,
item?.permissions?.map((option: any) => option.name)?.join(" - "),
<Popover key={i}>
<Tooltip title="ویرایش" position="right">
<Button
page="permission_control"
access="Post-Page"
variant="edit"
onClick={() => {
openModal({
title: "ویرایش صفحه",
content: <AddPage item={item} getData={refetch} />,
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
page="permission_control"
access="Post-Page"
api={`auth/api/v1/page/${item?.id}/`}
getData={refetch}
/>
</Popover>,
];
});
setPagesTableData(formattedData);
}
}, [pagesData, pagesInfo]);
return (
<Grid container column>
<Grid container className="items-center gap-2">
<Button
size="small"
page="permission_control"
access="Post-Page"
variant="submit"
onClick={() => {
openModal({
title: "ایجاد صفحه",
content: <AddPage getData={refetch} />,
});
}}
>
ایجاد صفحه
</Button>
{(window.location.origin.includes("localhost") ||
window.location.origin.includes("tdam.rasadyar")) && (
<Tooltip title="ارسال لیست به سامانه" position="top">
<Button
size="small"
page="permission_control"
access="Post-Page"
icon={<ArrowPathIcon className="w-5 h-5" />}
onClick={() => {
openModal({
title: "آیا از ارسال لیست به سامانه مطمئنید؟",
content: (
<BooleanQuestion
getData={refetch}
api={`auth/api/v1/update_access/`}
method="post"
/>
),
});
}}
>
{" "}
</Button>
</Tooltip>
)}
</Grid>
<Table
className="mt-2"
onChange={setPagesInfo}
count={pagesData?.count || 10}
isPaginated
title="صفحات سامانه"
columns={["ردیف", "صفحه", "کلید", "دسترسی ها", "عملیات"]}
rows={pagesTableData}
/>
</Grid>
);
}

View File

@@ -0,0 +1,71 @@
import { useEffect, useState } from "react";
import { getFaPermissions } from "../../utils/getFaPermissions";
import { useApiRequest } from "../../utils/useApiRequest";
import { useModalStore } from "../../context/zustand-store/appStore";
import Table from "../../components/Table/Table";
import { Grid } from "../../components/Grid/Grid";
import Button from "../../components/Button/Button";
import { AddAccess } from "./AddAccess";
import { Popover } from "../../components/PopOver/PopOver";
import { Tooltip } from "../../components/Tooltip/Tooltip";
import { DeleteButtonForPopOver } from "../../components/PopOverButtons/PopOverButtons";
export default function UnusedAccess() {
const { openModal } = useModalStore();
const [pagesInfo, setPagesInfo] = useState({ page: 1, page_size: 10 });
const [pagesTableData, setPagesTableData] = useState([]);
const { data: pagesData, refetch } = useApiRequest({
api: "/auth/api/v1/permission/connectionless_permissions/",
method: "get",
params: pagesInfo,
queryKey: ["connectionless_permissions", pagesInfo],
});
useEffect(() => {
if (pagesData?.results) {
const formattedData = pagesData.results.map((item: any, i: number) => {
return [
pagesInfo.page === 1
? i + 1
: i + pagesInfo.page_size * (pagesInfo.page - 1) + 1,
item?.name,
item?.description,
`${getFaPermissions(item?.page)} (${item?.page})`,
<Popover key={i}>
<Tooltip title="ویرایش" position="right">
<Button
variant="edit"
onClick={() => {
openModal({
title: "ویرایش دسترسی",
content: <AddAccess item={item} getData={refetch} />,
});
}}
/>
</Tooltip>
<DeleteButtonForPopOver
api={`auth/api/v1/permission/${item?.id}/`}
getData={refetch}
/>
</Popover>,
];
});
setPagesTableData(formattedData);
}
}, [pagesData, pagesInfo]);
return (
<Grid container column>
<Table
className="mt-2"
onChange={setPagesInfo}
count={pagesData?.count || 10}
isPaginated
title="دسترسی های غیر فعال"
columns={["ردیف", "دسترسی", "توضیحات", "صفحه", "عملیات"]}
rows={pagesTableData}
/>
</Grid>
);
}