refactor: remove unused components and files related to date filtering and doctor search

- Deleted Date.js and FilterDate.js components as they are no longer needed.
- Removed SearchDoctor.js component which was responsible for searching doctors.
- Cleaned up the Head component by removing references to the deleted Date and SearchDoctor components.
- Removed TurnsPage component and its associated List component, which were not utilized.
- Deleted Cards and Table components from the list directory as they were not in use.
- Removed user account related components including Tab, Head, and Form components.
- Cleaned up bank account components including List, Item, and modal components.
- Removed representation adapters and related functions that were not in use.
This commit is contained in:
hamed
2026-06-25 18:14:27 +03:30
parent 537f3e47d5
commit f87110c277
70 changed files with 8 additions and 2911 deletions
+1 -4
View File
@@ -1,14 +1,11 @@
"use client";
import { ThemeProvider } from "next-themes";
import { usePathname } from "next/navigation";
import { useEffect } from "react";
import Cookies from "js-cookie";
import { setAccessToken } from "@/lib/tokenStore";
export function Providers({ children }) {
const router = usePathname();
useEffect(() => {
if (!Cookies.get("userInfo")) return;
fetch("/api/auth/refresh", { method: "POST" })
@@ -21,7 +18,7 @@ export function Providers({ children }) {
return (
<ThemeProvider
attribute={router.includes("/panel") ? "class" : "data-"}
attribute="data-"
defaultTheme="system"
enableSystem
>
+1 -29
View File
@@ -1,34 +1,18 @@
import { useState, useEffect } from "react";
import { useState } from "react";
import ProfilePA from "@/components/icons/ProfilePA";
import LogoutP from "@/components/icons/LogoutP";
import CardDA from "@/components/icons/CardDA";
import { Button, Popover } from "@mui/material";
import Link from "next/link";
import ModalLogout from "./ModalLogout";
import Cookies from "js-cookie";
function DetailProfile({ anchorEl, setAnchorEl }) {
const [modal, setModal] = useState(false);
const [hasRepresentationRole, setHasRepresentationRole] = useState(false);
const handleClose = () => setAnchorEl(null);
const closeModal = () => setModal(false);
const openModal = () => setModal(true);
useEffect(() => {
const userInfo = Cookies.get("userInfo");
if (userInfo) {
try {
const parsedUserInfo = JSON.parse(userInfo);
const roles = parsedUserInfo?.roles || {};
const hasRole = Object.values(roles).includes("representation");
setHasRepresentationRole(hasRole);
} catch (error) {
console.error("Error parsing userInfo:", error);
}
}
}, []);
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
@@ -74,18 +58,6 @@ function DetailProfile({ anchorEl, setAnchorEl }) {
تراکنش های من
</Button>
</Link>
{hasRepresentationRole && (
<Link href="/panel/dashboard" className="w-full">
<Button
fullWidth
variant="text"
className="!text-[#7E7E7E] !text-[14px] !font-medium !flex !justify-start !p-[8px] !gap-[5px]"
>
<ProfilePA />
داشبورد نماینده
</Button>
</Link>
)}
<Button
fullWidth
color="error"
-7
View File
@@ -1,7 +0,0 @@
import AddDoctorPage from "@/components/panel/addDoctor";
function AddDoctor() {
return <AddDoctorPage />;
}
export default AddDoctor;
-43
View File
@@ -1,43 +0,0 @@
import DashboardPage from "@/components/panel/dashboard";
import { cookies } from "next/headers";
import { fetchReq } from "@/lib/req";
import { safeJsonParse } from "@/lib/sanitize";
import { adaptRepresentationDashboard } from "@/lib/representationAdapters";
import { getServerAccessToken } from "@/lib/serverToken";
export const dynamic = "force-dynamic";
async function Dashboard() {
const API_URL = process.env.NEXT_PUBLIC_API_URL;
let dashboard = null;
try {
const cookieStore = await cookies();
const userInfo = cookieStore.get("userInfo");
if (userInfo) {
const parsedUserInfo = safeJsonParse(userInfo.value);
const representationUuid = parsedUserInfo?.representation_uuid;
if (representationUuid) {
const accessToken = await getServerAccessToken();
if (accessToken) {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
dashboard = await fetchReq(
`${API_URL}/api/v1/representation/${representationUuid}/dashboard/monthly?year=${year}&month=${month}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
}
}
}
} catch (error) {
console.error("Error fetching representation dashboard:", error);
}
return <DashboardPage data={adaptRepresentationDashboard(dashboard)} />;
}
export default Dashboard;
-51
View File
@@ -1,51 +0,0 @@
import Content from "@/components/panel/Content";
import { defineAbilitiesFor } from "@/lib/ability";
import { getUser } from "@/lib/auth";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
import { fetchReq } from "@/lib/req";
import { safeJsonParse } from "@/lib/sanitize";
import { getServerAccessToken } from "@/lib/serverToken";
async function LayoutPanel({ children }) {
const user = await getUser();
const ability = defineAbilitiesFor(user);
if (!ability.can("access", "Panel")) {
return redirect("/");
}
let representationInfo = null;
const API_URL = process.env.NEXT_PUBLIC_API_URL;
try {
const cookieStore = await cookies();
const userInfo = cookieStore.get("userInfo");
if (userInfo) {
const parsedUserInfo = safeJsonParse(userInfo.value);
if (!parsedUserInfo) return;
const representationUuid = parsedUserInfo?.representation_uuid;
if (representationUuid) {
const accessToken = await getServerAccessToken();
if (accessToken) {
representationInfo = await fetchReq(
`${API_URL}/api/v1/representation/${representationUuid}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
);
}
}
}
} catch (error) {
console.error("Error fetching representation info:", error);
}
return <Content children={children} representationInfo={representationInfo} />;
}
export default LayoutPanel;
-43
View File
@@ -1,43 +0,0 @@
import TurnsPage from "@/components/panel/turns";
import { cookies } from "next/headers";
import { fetchReq } from "@/lib/req";
import { safeJsonParse } from "@/lib/sanitize";
import { adaptRepresentationDashboard } from "@/lib/representationAdapters";
import { getServerAccessToken } from "@/lib/serverToken";
export const dynamic = "force-dynamic";
async function page() {
const API_URL = process.env.NEXT_PUBLIC_API_URL;
let dashboard = null;
try {
const cookieStore = await cookies();
const userInfo = cookieStore.get("userInfo");
if (userInfo) {
const parsedUserInfo = safeJsonParse(userInfo.value);
const representationUuid = parsedUserInfo?.representation_uuid;
if (representationUuid) {
const accessToken = await getServerAccessToken();
if (accessToken) {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
dashboard = await fetchReq(
`${API_URL}/api/v1/representation/${representationUuid}/dashboard/monthly?year=${year}&month=${month}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
}
}
}
} catch (error) {
console.error("Error fetching representation turns:", error);
}
return <TurnsPage data={adaptRepresentationDashboard(dashboard)} />;
}
export default page;
-41
View File
@@ -1,41 +0,0 @@
import UserAccountPage from "@/components/panel/userAccount";
import { cookies } from "next/headers";
import { fetchReq } from "@/lib/req";
import { safeJsonParse } from "@/lib/sanitize";
import { adaptBankAccount } from "@/lib/representationAdapters";
import { getServerAccessToken } from "@/lib/serverToken";
export const dynamic = "force-dynamic";
async function UserAccount() {
const API_URL = process.env.NEXT_PUBLIC_API_URL;
let representation = null;
try {
const cookieStore = await cookies();
const userInfo = cookieStore.get("userInfo");
if (userInfo) {
const parsedUserInfo = safeJsonParse(userInfo.value);
const representationUuid = parsedUserInfo?.representation_uuid;
if (representationUuid) {
const accessToken = await getServerAccessToken();
if (accessToken) {
representation = await fetchReq(
`${API_URL}/api/v1/representation/${representationUuid}`,
{ headers: { Authorization: `Bearer ${accessToken}` } }
);
}
}
}
} catch (error) {
console.error("Error fetching representation account:", error);
}
const bank = adaptBankAccount(representation?.data?.bank_account);
return <UserAccountPage data={{ bank }} />;
}
export default UserAccount;
+1 -1
View File
@@ -37,7 +37,7 @@ export default function robots() {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/dashboard', '/panel'],
disallow: ['/dashboard'],
},
sitemap: `${baseUrl}/sitemap.xml`,
};
+5 -40
View File
@@ -8,10 +8,8 @@ import ProfileD from "@/components/icons/ProfileD";
import NotificationD from "@/components/icons/NotificationD";
import { Button } from "@mui/material";
import ArrowLeftSideBar from "@/components/icons/ArrowLeftSideBar";
import { useState, useEffect } from "react";
import { useState } from "react";
import ModalLogout from "../../../app/component/ModalLogout";
import Cookies from "js-cookie";
import { useRouter } from "next/navigation";
function List({
value,
@@ -20,44 +18,13 @@ function List({
setIsTurnsDetails,
setIsOpenSide,
}) {
const router = useRouter();
const [hasRepresentationRole, setHasRepresentationRole] = useState(false);
useEffect(() => {
const userInfo = Cookies.get("userInfo");
if (userInfo) {
try {
const parsedUserInfo = JSON.parse(userInfo);
const roles = parsedUserInfo?.roles || {};
const hasRole = Object.values(roles).includes("representation");
setHasRepresentationRole(hasRole);
} catch (error) {
console.error("Error parsing userInfo:", error);
}
}
}, []);
const baseListMenu = [
const listMenu = [
{ name: "حساب کاربری", icon: <ProfileD active={value === 0} />, action: "tab" },
{ name: "نوبت های من", icon: <CalndarD active={value === 1} />, action: "tab" },
{ name: "تراکنش های من", icon: <CardD active={value === 2} />, action: "tab" },
];
// اگر نقش representation دارد، داشبورد نماینده را اضافه کن
if (hasRepresentationRole) {
baseListMenu.push({
name: "داشبورد نماینده",
icon: <ProfileD active={value === 3} />,
action: "navigate",
path: "/panel/dashboard"
});
}
const listMenu = [
...baseListMenu,
{ name: "نظرات من", icon: <MessageD active={value === hasRepresentationRole ? 4 : 3} />, action: "tab" },
{ name: "پیام ها", icon: <NotificationD active={value === hasRepresentationRole ? 5 : 4} />, action: "tab" },
{ name: "خروج", icon: <LogoutD active={value === hasRepresentationRole ? 6 : 5} />, action: "logout" },
{ name: "نظرات من", icon: <MessageD active={value === 3} />, action: "tab" },
{ name: "پیام ها", icon: <NotificationD active={value === 4} />, action: "tab" },
{ name: "خروج", icon: <LogoutD active={value === 5} />, action: "logout" },
];
const [open, setOpen] = useState(false);
@@ -70,8 +37,6 @@ function List({
if (item.action === "logout") {
handleOpen();
} else if (item.action === "navigate") {
router.push(item.path);
} else {
setValue(idx);
}
@@ -1,50 +0,0 @@
import { useState } from "react";
import { Button } from "@mui/material";
import ArrowBottomHD from "@/components/icons/ArrowBottomHD";
import Image from "next/image";
import DetailProfile from "@/app/component/DetailProfile";
function MenuProfile({ data }) {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => setAnchorEl(event.currentTarget);
return (
<>
<Image
className="flex md:hidden w-[32px] h-[32px] rounded-full"
src={data.src}
alt="profile"
height={32}
width={32}
/>
<Button
variant="text"
onClick={handleClick}
className="!hidden md:!flex !items-start !justify-center !gap-[8px] !p-1"
>
<Image
className="w-[48px] h-[48px] rounded-full"
src={data.src}
alt="profile"
height={48}
width={48}
/>
<div className="flex flex-col items-start justify-between gap-[3px]">
<p className="text-[#3B3B3B] dark:text-[#D7D8ED] text-[14px] font-semibold">
دکتر {data.name}
</p>
<p className="text-[#7E7E7E] dark:text-[#A1A1A1] text-[12px] font-normal">
{data.expertise}
</p>
</div>
<div className="mr-[4px]">
<ArrowBottomHD />
</div>
</Button>
<DetailProfile anchorEl={anchorEl} setAnchorEl={setAnchorEl} />
</>
);
}
export default MenuProfile;
-27
View File
@@ -1,27 +0,0 @@
import SearchHeaderP from "@/components/icons/SearchHeaderP";
import { InputAdornment, TextField } from "@mui/material";
function Search() {
return (
<TextField
fullWidth
placeholder="جستجو"
className="!hidden md:!flex"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchHeaderP />
</InputAdornment>
),
}}
sx={{
"& .MuiOutlinedInput-input": {
paddingTop: "9px !important",
paddingBottom: "9px !important",
},
}}
/>
);
}
export default Search;
@@ -1,68 +0,0 @@
import SearchHeaderP from "@/components/icons/SearchHeaderP";
import { Button, Popover, TextField } from "@mui/material";
import { useState } from "react";
function Search() {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<>
<Button
className="
!p-[8px] !border-[#EFEFEF] !rounded-[8px]
!w-[32px] md:!w-[36px] lg:!w-[40px] dark:!border-[#343645]
!h-[32px] md:!h-[36px] lg:!h-[40px] md:!hidden
!min-w-[32px] md:!min-w-[36px] lg:!min-w-[40px]
!min-h-[32px] md:!min-h-[36px] lg:!min-h-[40px]
"
variant="outlined"
onClick={handleClick}
>
<SearchHeaderP />
</Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<TextField
size="small"
placeholder="جستجو کنید.."
sx={{
"& .MuiFormLabel-root": {
top: "-4px !important",
},
"& .MuiInputBase-input": { padding: "8px" },
"& .MuiInputBase-root": { borderRadius: "6px !important" },
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #E9ECEF !important",
},
".Mui-focused": {
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #5559CE !important",
transition: "0.5s all",
},
},
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{
display: "none",
},
}}
/>
</Popover>
</>
);
}
export default Search;
@@ -1,36 +0,0 @@
import MoonP from "@/components/icons/MoonP";
import Sun from "@/components/icons/Sun";
import { Button } from "@mui/material";
import { useTheme } from "next-themes";
import { useState } from "react";
function Theme() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useState(() => setMounted(true), []);
return (
<Button
className="
!p-[8px] !border-[#EFEFEF] !rounded-[8px]
!w-[32px] md:!w-[36px] lg:!w-[40px] dark:!border-[#343645]
!h-[32px] md:!h-[36px] lg:!h-[40px]
!min-w-[32px] md:!min-w-[36px] lg:!min-w-[40px]
!min-h-[32px] md:!min-h-[36px] lg:!min-h-[40px]
"
// onClick={handleThemeSwitch}
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
variant="outlined"
>
<div className="block dark:hidden">
<MoonP />
</div>
<div className="hidden dark:block">
<Sun />
</div>
</Button>
);
}
export default Theme;
@@ -1,42 +0,0 @@
import { Button } from "@mui/material";
import NotificationP from "@/components/icons/NotificationP";
import SettingP from "@/components/icons/SettingP";
import Theme from "./Theme";
import Search from "./Search";
function Icons() {
return (
<div className="flex items-center justify-start gap-[6px]">
<Theme />
<Search />
<Button
className="
!p-[8px] !border-[#EFEFEF] !rounded-[8px]
!w-[32px] md:!w-[36px] lg:!w-[40px] dark:!border-[#343645]
!h-[32px] md:!h-[36px] lg:!h-[40px]
!min-w-[32px] md:!min-w-[36px] lg:!min-w-[40px]
!min-h-[32px] md:!min-h-[36px] lg:!min-h-[40px]
"
variant="outlined"
>
<SettingP />
</Button>
<Button
className="
!w-[32px] md:!w-[36px] lg:!w-[40px]
!h-[32px] md:!h-[36px] lg:!h-[40px] dark:!border-[#343645]
!min-w-[32px] md:!min-w-[36px] lg:!min-w-[40px]
!min-h-[32px] md:!min-h-[36px] lg:!min-h-[40px]
!p-[8px] !border-[#EFEFEF] !rounded-[8px] !relative before:!rounded-full
before:!absolute before:!right-[-2px] before:!top-[-2px] before:!w-[6px] before:!h-[6px]
before:!bg-[#D32F2F]
"
variant="outlined"
>
<NotificationP />
</Button>
</div>
);
}
export default Icons;
-25
View File
@@ -1,25 +0,0 @@
import Icons from "./icons";
import Search from "./Search";
import SideButton from "./sideButton";
import MenuProfile from "./MenuProfile";
function Header({ disableProfile, data, setActive }) {
return (
<div
className="
h-[56px] sm:h-[67px] md:h-[79px] lg:h-[90px] top-0 right-0 sticky border-0 border-b border-[#EFEFEF] bg-[#FFF]
dark:bg-[#222433] dark:border-transparent
flex items-center justify-between pr-[16px] pl-[18px] z-20 gap-[14px] sm:gap-[28px] md:gap-[42px] lg:gap-[56px]
"
>
<SideButton setActive={setActive} />
<Search />
<div className="flex items-center justify-end gap-[8px] md:gap-[40px] min-w-fit">
<Icons />
{!disableProfile && <MenuProfile data={data} />}
</div>
</div>
);
}
export default Header;
@@ -1,16 +0,0 @@
import { Button } from "@mui/material";
function ButtonMenu({ setActive }) {
return (
<Button
onClick={() => setActive(true)}
className="!py-[6px] !px-[2px] !flex !p-0.5 !w-[24px] !h-[24px] !flex-col !justify-between !items-center"
>
<span className="min-h-[2px] transition-all duration-500 ease-in-out rounded-[40px] bg-[#6C757D] rotate-0 w-full"></span>
<span className="w-full min-h-[2px] transition-all duration-500 ease-in-out rounded-[40px] bg-[#6C757D] rotate-180"></span>
<span className="min-h-[2px] transition-all duration-500 ease-in-out rounded-[40px] bg-[#6C757D] rotate-0 w-full"></span>
</Button>
);
}
export default ButtonMenu;
@@ -1,15 +0,0 @@
import ButtonMenu from "./ButtonMenu";
import LogoPanelHead from "@/components/icons/LogoPanelHead";
function SideButton({ setActive }) {
return (
<div className="flex md:hidden items-center justify-start gap-[4px]">
<ButtonMenu setActive={setActive} />
<div className="w-[100px] h-[24px]">
<LogoPanelHead />
</div>
</div>
);
}
export default SideButton;
-20
View File
@@ -1,20 +0,0 @@
import Header from "./header";
import Sidebar from "./sidebar";
import UserDetail from "./userDetail";
function LayoutPanel({ children, data }) {
return (
<div className="flex min-h-screen">
<Sidebar />
<main className="w-full">
<Header />
<section className="my-[24px] opacity-element mx-[16px] bg-[#FAFAFA]">
{children}
</section>
</main>
<UserDetail data={data} />
</div>
);
}
export default LayoutPanel;
-17
View File
@@ -1,17 +0,0 @@
function BgGray({ isActive, setIsActive }) {
return (
<div
onClick={() => setIsActive(false)}
className={`
md:hidden ${
isActive
? "bg-[#232323] opacity-40 flex"
: "bg-transparent opacity-40 hidden"
}
absolute z-[25] cursor-pointer transition-all duration-500 left-0 top-0 w-screen h-screen
`}
></div>
);
}
export default BgGray;
-32
View File
@@ -1,32 +0,0 @@
import DoubleArrowLeftP from "@/components/icons/DoubleArrowLeftP";
import LogoPanel from "@/components/icons/LogoPanel";
import { Button } from "@mui/material";
import Link from "next/link";
import React from "react";
function HeadMd({ open, setOpen }) {
return (
<div
className={`hidden md:flex w-full items-center transition-all duration-500 justify-between`}
>
<Link
href="/panel/dashboard"
className={`flex justify-center transition-all duration-500 logo-sidebar ${
open ? "max-w-full" : "max-w-0"
}`}
>
<LogoPanel />
</Link>
<Button
onClick={() => setOpen(!open)}
className={`!p-1 !grid !place-items-center !rounded-full !border !border-solid !border-[#EFEFEF]
!transition-all !duration-500 !bg-[#FFF] !w-[28px] !h-[28px] !z-[320] dark:!bg-[#222433] dark:!border-[#343645]
${open ? "!rotate-180" : "!rotate-0 !ml-[7px]"}`}
>
<DoubleArrowLeftP />
</Button>
</div>
);
}
export default HeadMd;
-22
View File
@@ -1,22 +0,0 @@
import { Button } from "@mui/material";
import ArrowRightP from "@/components/icons/ArrowRightP";
import LogoPanelHead from "@/components/icons/LogoPanelHead";
function HeadSm({ setActive }) {
return (
<div className="flex md:hidden items-center justify-start gap-[8px]">
<Button
variant="text"
className="!min-w-0 !p-1"
onClick={() => setActive(false)}
>
<ArrowRightP />
</Button>
<div className="w-[100px] h-[23px]">
<LogoPanelHead />
</div>
</div>
);
}
export default HeadSm;
-73
View File
@@ -1,73 +0,0 @@
import Link from "next/link";
import { Button } from "@mui/material";
import { usePathname } from "next/navigation";
// Icons
import CalendarP from "@/components/icons/CalendarP";
import CategoryP from "@/components/icons/CategoryP";
import ProfileAddP from "@/components/icons/ProfileAddP";
import ProfileCircleP from "@/components/icons/ProfileCircleP";
function Links({ open, setActive }) {
const list = [
{ name: "دشبورد", src: "/panel/dashboard", icon: <CategoryP /> },
{
name: "حساب کاربری",
src: "/panel/user-account",
icon: <ProfileCircleP />,
},
{
name: "اضافه کردن پزشک",
src: "/panel/add-doctor",
icon: <ProfileAddP />,
},
{ name: "نوبت ها", src: "/panel/turns", icon: <CalendarP /> },
];
const pathname = usePathname();
return (
<ul
className={`flex flex-col gap-[32px] mt-[36px] md:mt-[54px] transition-all duration-500`}
>
{list.map((item, idx) => (
<li key={idx}>
<Link href={item.src}>
<Button
fullWidth
variant="text"
onClick={() => setActive(false)}
className={`
!p-[8px] !flex !items-center !justify-start !gap-[8px] !text-[16px] !rounded-[8px] !transition-all !duration-500
${
item.src === pathname
? "!bg-[#5559CE] dark:!bg-[#1F1D2B] dark-active-icon"
: ""
}
`}
>
<div className="p-[3px] bg-[#E5E9FF] dark:bg-transparent rounded-[4px] w-fit">
<div className="w-[20px] h-[20px]">{item.icon}</div>
</div>
<p
className={`
transition-all duration-500 text-sidebar
${open ? "opacity-100" : "md:opacity-0"}
${
item.src === pathname
? "!text-[#FAFAFA] dark:!text-[#5559CE] !font-bold"
: "!text-[#525252] dark:!text-[#A1A1A1] !font-medium"
}
`}
>
{item.name}
</p>
</Button>
</Link>
</li>
))}
</ul>
);
}
export default Links;
-66
View File
@@ -1,66 +0,0 @@
import Links from "./Links";
import HeadMd from "./HeadMd";
import HeadSm from "./HeadSm";
import { Button } from "@mui/material";
import LogoutOrangeP from "@/components/icons/LogoutOrangeP";
import ModalLogout from "@/app/component/ModalLogout";
import { useState } from "react";
function Sidebar({ active, setActive, open, setOpen }) {
const [modal, setModal] = useState(false);
const handleOpen = () => setModal(true);
const handleClose = () => setModal(false);
return (
<div
className={`
transition-all duration-500 md:flex fixed md:relative z-30 w-[85%] hover-sidebar
${active ? " right-0" : "right-[-85%]"}
${open ? "min-w-[243px] md:w-[243px]" : "min-w-[92px] md:w-[92px]"}
`}
>
<aside
className={`
relative h-screen overflow-hidden
transition-all duration-500 w-[85%]
${active ? " right-0" : "right-[-85%]"}
${open ? "w-[243px]" : "w-[92px] hover:w-[243px]"}
`}
>
<div
className={`
z-30 md:top-0 md:right-0 w-[85%]
${active ? " right-0" : "right-[-85%]"}
overflow-hidden p-[24px] h-full flex flex-col justify-between items-start fixed
border-0 border-l border-l-[#EFEFEF] bg-[#FFF] dark:bg-[#222433]
transition-all duration-500 dark:border-transparent
${open ? "md:w-[243px]" : "md:w-[92px] hover:md:w-[243px]"}
`}
>
<div className="w-full">
<HeadMd open={open} setOpen={setOpen} />
<HeadSm setActive={setActive} />
<Links open={open} setActive={setActive} />
</div>
<div
className={`w-full flex logout-btn justify-center overflow-hidden transition-all duration-500 ${
open ? "!max-w-full" : "!max-w-[36px]"
}`}
>
<Button
variant="text"
onClick={handleOpen}
className="!flex !w-fit !items-center !justify-start !gap-[10px] !py-[8px] !px-[16px] !text-[#F17732] !text-[16px] !font-medium"
>
<LogoutOrangeP />
خروج از حساب
</Button>
<ModalLogout open={modal} handleClose={handleClose} />
</div>
</div>
</aside>
</div>
);
}
export default Sidebar;
@@ -1,40 +0,0 @@
import TextLoading from "@/app/component/loading/Text";
import WarnP from "@/components/icons/WarnP";
function Activities({ data, loading }) {
return (
<div className="w-full mt-[16px] sm:mt-[24px] md:mt-[32px] lg:mt-[40px]">
<p className="text-[#525252] text-[14px] md:text-[16px] font-bold dark:text-[#D7D8ED]">
فعالیت های امروز
</p>
<ul className="flex flex-col justify-start w-full mt-[24px]">
{data.todays_activities.map((item, idx) => (
<li key={idx} className="flex flex-col">
<div className="flex items-center justify-between">
<div className="flex items-center justify-start gap-[12px]">
<div className="bg-[rgba(255,192,81,0.20)] dark:bg-[rgba(255,192,81,0.10)] p-[8px] rounded-full">
<WarnP />
</div>
<TextLoading width={110} height={18} loading={loading}>
<p className="text-[#616161] dark:text-[#A1A1A1] text-[16px] font-medium">
{item.text}
</p>
</TextLoading>
</div>
<TextLoading width={55} height={18} loading={loading}>
<p className="text-[#7E7E7E] dark:text-[#A1A1A1] text-[12px] font-normal">
{item.time}
</p>
</TextLoading>
</div>
{data.todays_activities.length > idx + 1 && (
<span className="block h-px w-full bg-transparent md:bg-[#EFEFEF] dark:bg-[#343645] px-[13px] my-[16px] md:my-[20px] lg:my-[24px]"></span>
)}
</li>
))}
</ul>
</div>
);
}
export default Activities;
-41
View File
@@ -1,41 +0,0 @@
"use client";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
function Date() {
return (
<div className="flex justify-center sm-datepicker active-today jalali-dashboard">
<JalaliDatePicker
nextIcon={<></>}
prevIcon={<></>}
dayLabelRender={() => (
<div className="day-label-bar-inner-rtl">
<div className="day-label-item !text-[10px] font-semibold">
شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
۱ شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
۲ شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
۳ شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
۴ شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
۵ شنبه
</div>
<div className="day-label-item !text-[10px] font-semibold">
جمعه
</div>
</div>
)}
/>
</div>
);
}
export default Date;
-66
View File
@@ -1,66 +0,0 @@
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text";
import ArrowLeftBlueP from "@/components/icons/ArrowLeftBlueP";
import LocationP from "@/components/icons/LocationP";
import { Button } from "@mui/material";
import Image from "next/image";
import React from "react";
function Head({ data, loading, representationInfo }) {
const cityName = representationInfo?.city?.label || "اهواز";
return (
<div className="flex w-full flex-col items-center justify-start">
<CircularLoading width={80} height={80} loading={loading}>
<Image src={data.src} height={80} width={80} alt="profile" />
</CircularLoading>
<div className={loading ? "mt-[8px] mb-[4px]" : ""}>
<TextLoading width={60} height={18} loading={loading}>
<p className="text-[#3B3B3B] text-[14px] font-semibold mt-[8px] mb-[4px] dark:text-[#fafafa]">
{data.name}
</p>
</TextLoading>
</div>
<div className="flex items-center justify-center gap-[8px]">
<LocationP />
<TextLoading width={55} height={16} loading={loading}>
<p className="text-[#7E7E7E] text-[14px] font-normal dark:text-[#A1A1A1]">
نماینده شهر {cityName}
</p>
</TextLoading>
</div>
<div className="flex items-center justify-center my-[16px]">
<div className="flex flex-col items-center justify-center">
<p className="text-[#9B9B9B] text-[14px] font-normal dark:text-[#A1A1A1]">
کل بیماران
</p>
<TextLoading width={55} height={16} loading={loading}>
<p className="text-[#3B3B3B] text-[14px] font-medium dark:text-[#A1A1A1]">
{data.all_patients}
</p>
</TextLoading>
</div>
<span className="block h-[44px] w-px bg-[#EFEFEF] mx-[44px] dark:bg-[#343645]"></span>
<div className="flex flex-col items-center justify-center">
<p className="text-[#9B9B9B] text-[14px] font-normal dark:text-[#A1A1A1]">
کل نوبت ها
</p>
<TextLoading width={55} height={16} loading={loading}>
<p className="text-[#3B3B3B] text-[14px] font-medium dark:text-[#A1A1A1]">
{data.total_turns}
</p>
</TextLoading>
</div>
</div>
<Button
className="!flex !p-1 !items-center !justify-center !text-[#5559CE] !text-[14px] !font-medium"
variant="text"
>
مشاهده اطلاعات نماینده
<ArrowLeftBlueP />
</Button>
</div>
);
}
export default Head;
@@ -1,21 +0,0 @@
import Head from "./Head";
import Date from "./Date";
import Activities from "./Activities";
function UserDetail({ data, loading, representationInfo }) {
return (
<div className="hidden xl:flex min-w-[332px] h-full opacity-page">
<div
className="min-w-[332px] p-[24px] border-0 border-r border-r-[#EFEFEF] h-full items-start fixed top-0 left-0
g-[#FFF] py-[40px] px-[16px] bg-[#FFF] dark:bg-[#222433] dark:border-transparent"
>
<Head data={data} loading={loading} representationInfo={representationInfo} />
<span className="block w-full h-px bg-[#EFEFEF] px-[22px] mt-[13px] mb-[16px] dark:bg-[#343645]"></span>
<Date />
<Activities data={data} loading={loading} />
</div>
</div>
);
}
export default UserDetail;
-70
View File
@@ -1,70 +0,0 @@
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text";
import Image from "next/image";
function ColTable({ data, loading }) {
return (
<ul className="grid grid-cols-1 sm:grid-cols-2 md:hidden flex-col gap-[16px]">
{data.map((item, idx) => (
<li
key={idx}
className="bg-[#FFF] p-[12px] rounded-[8px] shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] dark:shadow-transparent dark:bg-[#222433]"
>
<div className="flex items-center justify-between">
<div className="flex items-center justify-start gap-[8px] !text-nowrap">
<CircularLoading width={32} height={32} loading={loading}>
<Image src={item.image} alt="doctor" height={32} width={32} />
</CircularLoading>
<TextLoading width={40} height={18} loading={loading}>
<p className="text-[#616161] dark:text-[#D7D8ED]">
{item.name}
</p>
</TextLoading>
</div>
<TextLoading width={105} height={18} loading={loading}>
<p className="px-[8px] py-[4px] text-[#F17732] dark:bg-[#4E2E39] dark:text-[#FF5450] text-[14px] font-medium bg-[rgba(255,216,197,0.50)] rounded-[4px]">
تعداد نوبت: {item.number_of_turns}
</p>
</TextLoading>
</div>
<div className="flex flex-col mt-[16px]">
<div className="flex items-center justify-between">
<p className="text-[#7E7E7E] dark:text-[#A1A1A1] text-[14px] font-normal">
تخصص:
</p>
<TextLoading width={90} height={18} loading={loading}>
<p className="text-[#616161] dark:text-[#A1A1A1] text-[14px] font-medium">
{item.expertise}
</p>
</TextLoading>
</div>
<span className="block h-px w-full bg-[#EFEFEF] dark:bg-[#343645] my-[12px]"></span>
<div className="flex items-center justify-between">
<p className="text-[#7E7E7E] dark:text-[#A1A1A1] text-[14px] font-normal">
تاریخ نوبت:
</p>
<TextLoading width={85} height={18} loading={loading}>
<p className="text-[#616161] dark:text-[#A1A1A1] text-[14px] font-medium">
{item.date}
</p>
</TextLoading>
</div>
<span className="block h-px w-full bg-[#EFEFEF] dark:bg-[#343645] my-[12px]"></span>
<div className="flex items-center justify-between">
<p className="text-[#7E7E7E] dark:text-[#A1A1A1] text-[14px] font-normal">
ساعت نوبت:
</p>
<TextLoading width={60} height={18} loading={loading}>
<p className="text-[#616161] dark:text-[#A1A1A1] text-[14px] font-medium">
{item.hour}
</p>
</TextLoading>
</div>
</div>
</li>
))}
</ul>
);
}
export default ColTable;
-66
View File
@@ -1,66 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import Header from "@/components/layoutPanel/header";
import Sidebar from "@/components/layoutPanel/sidebar";
import UserDetail from "@/components/layoutPanel/userDetail";
import { usePathname } from "next/navigation";
import BgGray from "@/components/layoutPanel/sidebar/BgGray";
import { adaptRepresentationInfo } from "@/lib/representationAdapters";
function Content({ children, representationInfo }) {
const pathname = usePathname();
const [active, setActive] = useState(false);
const [open, setOpen] = useState(true);
const [loading, setLoading] = useState(true);
const info = adaptRepresentationInfo(representationInfo);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);
return (
<div className="flex min-h-screen dark:bg-[#1F1D2B]">
<Sidebar
open={open}
active={active}
setOpen={setOpen}
setActive={setActive}
/>
<BgGray isActive={active} setIsActive={setActive} />
<main
className={`
w-full transition-all duration-500 dark:bg-[#1F1D2B]
${pathname.includes("dashboard")
? open
? "md:min-w-[calc(100%_-_243px)] md:w-[calc(100%_-_243px)] xl:min-w-[calc(100%_-_243px_-_332px)] xl:w-[calc(100%_-_243px_-_332px)]"
: "md:min-w-[calc(100%_-_96px)] md:w-[calc(100%_-_96px)] xl:min-w-[calc(100%_-_96px_-_332px)] xl:w-[calc(100%_-_96px_-_332px)]"
: open
? "md:w-[calc(100%-243px)]"
: "md:w-[calc(100%-96px)]"
}
`}
>
<Header
data={info}
setActive={setActive}
disableProfile={pathname.includes("dashboard")}
/>
<section className="my-[24px] opacity-element mx-[16px] bg-[#FAFAFA] dark:bg-[#1F1D2B]">
{children}
</section>
</main>
{pathname.includes("dashboard") && (
<UserDetail
data={info}
loading={loading}
representationInfo={representationInfo}
/>
)}
</div>
);
}
export default Content;
@@ -1,15 +0,0 @@
export const defaultState = {
name: { value: "", placeholder: "", isEdit: true },
medical_system_number: {
value: "",
placeholder: 1741025645,
isEdit: true,
},
expertise: { value: "", placeholder: "دندان پزشکی", isEdit: true },
description: { value: "", isEdit: true },
skills: { value: [], isEdit: true },
time_work: { value: "", isEdit: true },
phone: { value: "", placeholder: "", isEdit: true },
degree: { value: "", isEdit: true },
gender: { value: "woman", isEdit: true },
};
-97
View File
@@ -1,97 +0,0 @@
import Selector from "./fields/Selector";
import ContentText from "@/app/component/ContentText";
import EditField from "@/app/component/fields/EditField";
import GroupedSelect from "@/app/component/selectors/GroupedSelect";
import MultipleSelect from "@/app/component/selectors/MultipleSelect";
// Data
import specialties from "@/data/specialties.json";
import education from "@/data/education.json";
const validate_medical_number = {
min: 1,
max: 9999999999,
};
function Fields({ services, data, updateNum, changeData }) {
return (
<>
<ContentText text="نام و نام خانوادگی">
<EditField
isTransparent={true}
iconGray={true}
changeData={changeData}
data={data?.name}
placeholder={data?.name.placeholder}
name="name"
/>
</ContentText>
<ContentText text="شماره نظام پزشکی">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.medical_system_number}
noDisable={true}
type="number"
placeholder={data?.medical_system_number.placeholder}
name="medical_system_number"
props={{
inputProps: validate_medical_number,
}}
/>
</ContentText>
<ContentText text="تخصص">
<GroupedSelect
updateData={changeData}
data={data?.expertise.value}
name="expertise"
list={specialties}
nameKey="name"
/>
</ContentText>
<ContentText text="مدرک">
<Selector
updateData={changeData}
label="انتخاب کنید..."
name="degree"
data={data?.degree.value}
list={education}
nameKey="label"
/>
</ContentText>
<ContentText text="مهارت ها">
<MultipleSelect
name="skills"
list={services}
updateData={changeData}
value={data?.skills.value}
/>
</ContentText>
<ContentText text="شماره موبایل">
<EditField
isTransparent={true}
iconGray={true}
changeData={changeData}
data={data?.phone}
type="number"
placeholder={data?.phone.placeholder}
name="phone"
/>
</ContentText>
<ContentText text="توضیحات">
<EditField
multiline={5}
placeholder="درباره سوابق خود توضیحاتی بنویسید..."
isTransparent={true}
iconGray={true}
changeData={changeData}
data={data?.description}
name="description"
/>
</ContentText>
</>
);
}
export default Fields;
@@ -1,54 +0,0 @@
import { useState } from "react";
import { convertToJalali } from "@/helper";
import EditField from "@/app/component/fields/EditField";
import ButtonDate from "@/app/component/ButtonDate";
import CalendarPD from "@/components/icons/CalendarPD";
import ContentText from "@/app/component/ContentText";
function FieldDate({ changeListData, data }) {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const updateDate = (newDate) => {
const jalaliDate = newDate
? convertToJalali(newDate)
: "تاریخ مد نظر پیدا نشد...";
changeListData(jalaliDate, "value", "time_work");
};
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
return (
<ContentText text="زمان شروع به کار">
<ButtonDate
setAnchorEl={setAnchorEl}
updateDate={updateDate}
anchorEl={anchorEl}
open={open}
id={id}
>
<div className="relative w-full">
<EditField
placeholder="انتخاب کنید..."
changeData={changeListData}
data={data}
icon={<CalendarPD />}
isTransparent={true}
iconGray={true}
name="time_work"
/>
<span
className="absolute left-0 top-0 w-full h-full bg-transparent z-10 cursor-pointer"
onClick={handleClick}
></span>
</div>
</ButtonDate>
</ContentText>
);
}
export default FieldDate;
@@ -1,34 +0,0 @@
import { FormControlLabel, Radio, RadioGroup } from "@mui/material";
function RadioGender({ changeData, name, data }) {
return (
<RadioGroup
dir="ltr"
value={data}
className="!flex radio-mui !flex-row !gap-[36px] !items-center !justify-start"
onChange={(e) => changeData(e.target.value, "value", name)}
sx={{
"& .muirtl-j204z7-MuiFormControlLabel-root": {
marginLeft: "0 !important",
},
}}
>
<FormControlLabel
label="مرد"
value="man"
control={<Radio />}
dir="rtl"
className="!text-[#A1A1A1]"
/>
<FormControlLabel
label="زن"
value="woman"
control={<Radio />}
dir="rtl"
className="!text-[#A1A1A1]"
/>
</RadioGroup>
);
}
export default RadioGender;
@@ -1,85 +0,0 @@
import BottomSelect from "@/components/icons/BottomSelect";
import { Autocomplete, Button, TextField } from "@mui/material";
import { styleTextSelectRight } from "@/mui";
function Selector({ data, updateData, nameKey, label, name, list }) {
const selectedOption = list.find((item) => item.id === data) || null;
return (
<Autocomplete
value={selectedOption}
disablePortal
options={list}
getOptionLabel={(option) => option[nameKey] || ""}
isOptionEqualToValue={(option, value) => option.id === value.id}
className="!w-full !rounded-lg auto-complete-selector"
onChange={(e, newValue) => {
if (newValue) {
updateData && updateData(newValue.id, "value", name);
}
}}
sx={{
...styleTextSelectRight,
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
"& .MuiInputBase-input": { padding: "12.5px 14px !important" },
"& .MuiInputBase-root": {
borderRadius: 2,
padding: "0 !important",
height: {
xs: "43px !important",
sm: "43px !important",
md: "46px !important",
lg: "48px !important",
},
},
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #D7D7D7",
},
"& .MuiInput-underline:hover:not(.Mui-disabled):before, .MuiOutlinedInput-notchedOutline":
{
borderColor: "#D7D7D7 !important",
},
"& .muirtl-1kiro5a-MuiInputBase-root-MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":
{
border: "1px solid #5559CE !important",
},
}}
renderOption={(props, option) => (
<Button
fullWidth
{...props}
key={option.id || props.id}
className="!flex !justify-start !p-[8px] !text-start !text-[#A1A1A1] hover:dark:!bg-[#35343D] !bg-[#FFF] dark:!bg-[#1F1D2B]"
>
{option.name || option.label}
</Button>
)}
renderInput={(params) => (
<TextField
fullWidth
{...params}
InputProps={{
...params.InputProps,
endAdornment: (
<div className="absolute left-4">
<BottomSelect />
</div>
),
}}
placeholder={label}
sx={{
borderRadius: "8px",
}}
/>
)}
/>
);
}
export default Selector;
-73
View File
@@ -1,73 +0,0 @@
import { useEffect, useState } from "react";
import FieldDate from "./fields/FieldDate";
import RadioGender from "./fields/RadioGender";
import SendReq from "./sendReq";
import ContentText from "@/app/component/ContentText";
import { request } from "@/services/response";
import Fields from "./Fields";
const validate_medical_number = {
min: 1,
max: 9999999999,
};
function Form({ image, data, setData, resetData }) {
const [services, setServices] = useState([]);
const changeData = (value, type, name) => {
type !== "isEdit" &&
setData((prev) => {
return {
...prev,
[name]: {
...prev[name],
[type]: value,
},
};
});
};
const updateNum = (value, type, name) => {
const parseValue = +value;
const { max } = validate_medical_number;
const validateValue = parseValue > max ? data[name].value : value;
changeData(validateValue, type, name);
};
useEffect(() => {
request
.getDoctorServices()
.then((res) => {
setServices(res.data);
})
.catch(() => {
//
});
}, []);
return (
<>
<div className="mt-[24px] sm-modal scroll-mui md:mt-[26px] lg:mt-[28px] grid grid-cols-1 md:grid-cols-2 gap-x-[48px] gap-y-[24px]">
<Fields
data={data}
services={services}
updateNum={updateNum}
changeData={changeData}
/>
<div className="flex flex-col gap-y-[24px]">
<FieldDate changeListData={changeData} data={data?.time_work} />
<ContentText text="جنسیت">
<RadioGender
changeData={changeData}
data={data?.gender.value}
name="gender"
/>
</ContentText>
</div>
</div>
<SendReq img={image} data={data} resetData={resetData} />
</>
);
}
export default Form;
@@ -1,13 +0,0 @@
export const degree = [
{ label: "مدرک 1", id: 10 },
{ label: "مدرک 2", id: 20 },
{ label: "مدرک 3", id: 30 },
{ label: "مدرک 4", id: 40 },
];
export const expertise = [
{ label: "فوق تخصص", id: 10 },
{ label: "دکترای تخصصی", id: 20 },
{ label: "متخصص", id: 30 },
{ label: "دکتری", id: 40 },
];
@@ -1,21 +0,0 @@
import moment from "jalali-moment";
export const dateToTimestamp = (date) => {
const gregorianMoment = moment(date, "jYYYY/jM/jD").locale("fa");
const unixTimestamp = gregorianMoment.valueOf();
return unixTimestamp / 1000;
};
export const transformData = (data) => {
return {
name: data.name.value,
activity_time: dateToTimestamp(data.time_work.value),
doctor_mobile_number: data.phone.value,
degree: data.degree.value,
specialty: [data.expertise.value],
info: data.description.value,
doctor_id: data.medical_system_number.value,
doctor_services: data.skills.value,
gender: data.gender.value,
};
};
@@ -1,68 +0,0 @@
import ArrowLeftPA from "@/components/icons/ArrowLeftPA";
import { alert, checkAllValues } from "@/helper";
import { request } from "@/services/response";
import { Button } from "@mui/material";
import { useState } from "react";
import { transformData } from "./function";
function SendReq({ img, data, resetData }) {
const [loading, setLoading] = useState(false);
const saveData = (img) => {
let transformed = transformData(data);
if (img) {
transformed.image = [img];
}
request
.postDoctor(transformed)
.then((res) => {
if (res.id) {
alert("success", "پزشک جدید با موفقیت اضافه شد!");
resetData();
setLoading(false);
}
})
.catch(() => {
setLoading(false);
alert("error", "خطا در ثبت پزشک! لطفاً دوباره تلاش کنید.");
});
};
const saveImg = () => {
resetData();
setLoading(true);
if (img) {
request
.postImageUpload(img.file)
.then((res) => {
const imgId = res.fid[0].value;
saveData(imgId);
})
.catch(() => {
setLoading(false);
});
} else {
saveData();
}
};
return (
<div className="mt-[40px] w-full flex justify-end">
<Button
className="!w-full md:!w-fit !shadow-none !rounded-[4px] !gap-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
disabled={loading || !checkAllValues(data)}
variant="contained"
onClick={saveImg}
>
<p className={loading ? "opacity-0" : ""}>ثبت اطلاعات</p>
<div className={!checkAllValues(data) ? "opacity-40" : "opacity-100"}>
<ArrowLeftPA />
</div>
</Button>
</div>
);
}
export default SendReq;
-31
View File
@@ -1,31 +0,0 @@
"use client";
import Form from "./form";
import { useState } from "react";
import { defaultState } from "./defaultState";
import UploadFile from "@/app/component/uploadFile";
function AddDoctorPage() {
const [data, setData] = useState(defaultState);
const [image, setImage] = useState("");
const resetData = () => {
setData(defaultState);
setImage();
};
return (
<div className="opacity-page bg-transparent dark:shadow-[0px_4px_25px_10px_#1F1D2B] md:dark:bg-[#222433] md:bg-[#FFF] opacity-page rounded-[8px] shadow-none md:shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] pt-0 md:pt-[12px] lg:pt-[24px] px-0 md:px-[24px] p-[24px]">
<UploadFile image={image} setImage={setImage} />
<Form
data={data}
image={image}
parent={parent}
setData={setData}
resetData={resetData}
/>
</div>
);
}
export default AddDoctorPage;
-63
View File
@@ -1,63 +0,0 @@
import CalendarTickGreenP from "../../icons/CalendarTickGreenP";
import TextLoading from "@/app/component/loading/Text";
import CardOrangeP from "../../icons/CardOrangeP";
import PeopleBlueP from "../../icons/PeopleBlueP";
function Cards({ data, loading }) {
const list = [
{
text: "تعداد کل بیماران",
bg_color: "bg-[#E5E9FF] dark:bg-[#5559CE]",
value: data.number_of_patients,
label: "نفر",
icon: <PeopleBlueP />,
},
{
text: "نوبت های امروز",
bg_color: "bg-[rgba(20,204,38,0.14)] dark:bg-[#009D79]",
value: data.today_turns,
label: "نوبت",
icon: <CalendarTickGreenP />,
},
{
text: "پرداختی ها",
bg_color: "bg-[#FFD8C5] dark:bg-[#F17732]",
value: data.payments,
label: "تومان",
icon: <CardOrangeP />,
},
];
return (
<ul className="flex lg:grid lg:grid-cols-3 flex-wrap flex-col sm:flex-row items-center justify-center gap-[32px] mx-0 sm:mx-[16px]">
{list.map((item, idx) => (
<li
className="flex min-w-[194px] w-full sm:w-auto flex-col py-[16px] md:py-[18px] px-[12px] gap-[8px] rounded-[8px] border
border-solid border-[#EFEFEF] bg-[#FFF] dark:bg-[#222433] dark:border-transparent"
key={idx}
>
<div
className={`p-[12px] w-fit h-fit rounded-[2px] ${item.bg_color}`}
>
{item.icon}
</div>
<div className="flex items-center justify-between gap-[4px]">
<p className="text-[#525252] text-[14px] font-normal dark:text-[#D7D8ED]">
{item.text}
</p>
<TextLoading width={70} height={20} loading={loading}>
<p className="text-[#3B3B3B] text-[24px] font-medium dark:text-[#A1A1A1]">
{item.value}
<span className="text-[#616161] mr-[6px] text-[12px] font-normal dark:text-[#A1A1A1]">
{item.label}
</span>
</p>
</TextLoading>
</div>
</li>
))}
</ul>
);
}
export default Cards;
-90
View File
@@ -1,90 +0,0 @@
"use client";
import { LineChart, lineElementClasses } from "@mui/x-charts";
function Chart({ data, loading }) {
return (
<LineChart
loading={loading}
xAxis={[
{
data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
disableLine: true,
disableTicks: true,
},
]}
series={[
{
id: "income",
data: data.chart_amount_income,
area: true,
showMark: false,
},
]}
yAxis={[
{
disableLine: true,
disableTicks: true,
},
]}
margin={{
left: 60,
right: 50,
top: 10,
bottom: 60,
}}
sx={{
[`& .${lineElementClasses.root}`]: {
strokeWidth: 3,
stroke: loading ? "#E6E6E6" : "#5559CE",
},
"& .MuiAreaElement-series-income": {
fill: "url('#myGradient') !important",
},
"& .MuiChartsAxis-directionY .MuiChartsAxis-tickLabel": {
fontSize: "14px !important",
fontWeight: "400 !important",
fill: "#858D9D",
"@media (prefers-color-scheme: dark)": {
fill: "#A1A1A1",
},
},
"& .MuiChartsAxis-directionX .MuiChartsAxis-tickLabel": {
fontSize: "10px !important",
fontWeight: "400 !important",
fill: "#7E7E7E",
"@media (prefers-color-scheme: dark)": {
fill: "#A1A1A1",
},
},
"& .MuiChartsGrid-line": {
strokeDasharray: 2,
"@media (prefers-color-scheme: dark)": {
stroke: "#343645",
},
},
"& .MuiChartsAxis-directionY": {
// transform: 'translateX(-50px) !important'
},
}}
grid={{ horizontal: true }}
className="!w-full !mt-[20px] !h-[250px]"
>
<defs>
<linearGradient
id="myGradient"
x1="252.5"
y1="0"
x2="252.5"
y2="145"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#3A6FF8" stopOpacity="0.1" />
<stop offset="1" stopColor="#3A6FF8" stopOpacity="0.02" />
</linearGradient>
</defs>
</LineChart>
);
}
export default Chart;
@@ -1,65 +0,0 @@
"use client";
import ArrowDownP from "@/components/icons/ArrowDownP";
import { MenuItem, Select } from "@mui/material";
function Head() {
const list = [
{ id: 1, text: "سال 1402" },
{ id: 2, text: "سال 1403" },
{ id: 3, text: "سال 1404" },
{ id: 4, text: "سال 1405" },
{ id: 5, text: "سال 1406" },
{ id: 6, text: "سال 1407" },
];
return (
<div className="flex items-center justify-start gap-[12px] p-[16px]">
<p className="text-[#525252] text-[14px] md:text-[16px] font-bold dark:text-[#D7D8ED]">
میزان درآمد
</p>
<Select
defaultValue={1}
className="!text-[#7E7E7E] dark:!text-[#A1A1A1] !text-[12px] !font-normal !rounded-[4px] dark:!bg-[#222433]"
IconComponent={(e) => <ArrowDownP data={e} />}
MenuProps={{
PaperProps: {
className: "dark:!bg-[#222433]",
},
}}
sx={{
"& .MuiSelect-select": {
padding: "8px 6px 8px 8px !important",
},
"&:hover": {
"& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #D7D7D7",
"@media (prefers-color-scheme: dark)": {
borderColor: "#343645 !important",
},
},
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "#D7D7D7 !important",
border: "1px solid #D7D7D7",
"@media (prefers-color-scheme: dark)": {
borderColor: "#343645 !important",
},
},
}}
>
{list.map((item, idx) => (
<MenuItem
value={item.id}
key={idx}
className="dark:!bg-[#222433] dark:!text-[#D7D8ED]"
>
{item.text}
</MenuItem>
))}
</Select>
</div>
);
}
export default Head;
@@ -1,15 +0,0 @@
import Chart from "./Chart";
import Head from "./Head";
function AmountOfIncome({ data, loading }) {
return (
<div className="rounded-[8px] border border-solid border-[#EFEFEF] bg-[#FFF] dark:bg-[#222433] dark:!border-transparent">
<Head />
<div dir="ltr">
<Chart data={data} loading={loading} />
</div>
</div>
);
}
export default AmountOfIncome;
-31
View File
@@ -1,31 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import Activities from "@/components/layoutPanel/userDetail/Activities";
import AmountOfIncome from "./amountOfIncome";
import Cards from "./Cards";
import List from "./list";
function DashboardPage({ data }) {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);
return (
<div className="flex mt-[4px] flex-col gap-[20px] opacity-page">
<Cards data={data} loading={loading} />
<List data={data} loading={loading} />
<AmountOfIncome data={data} loading={loading} />
<div className="flex xl:hidden">
<Activities data={data} loading={loading} />
</div>
</div>
);
}
export default DashboardPage;
-84
View File
@@ -1,84 +0,0 @@
import CustomTable from "@/app/component/CustomTable";
import CircularLoading from "@/app/component/loading/Circular";
import TextLoading from "@/app/component/loading/Text";
import { TableCell, TableRow } from "@mui/material";
import Image from "next/image";
function Table({ data, loading }) {
const tableHead = ["ردیف", "نام پزشک", "تخصص", "تاریخ", "ساعت", "تعدد نوبت"];
const centerTable = [0, 4];
return (
<div className="hidden md:flex">
<CustomTable
zeroRadius={true}
tableHead={tableHead}
centerTable={centerTable}
>
{data.list_of_doctors_appointments.map((row, idx) => (
<TableRow
key={idx}
className="!border-0 !border-b !border-[#DBDBDB] dark:!border-transparent"
>
<TableCell
className="!pr-[18px] dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-nowrap"
align="center"
>
<TextLoading width={15} height={18} loading={loading}>
{idx + 1}
</TextLoading>
</TableCell>
<TableCell
className="!pr-[18px] dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-nowrap"
component="th"
scope="row"
>
<div className="flex items-center justify-start gap-[8px] !text-nowrap">
<CircularLoading width={32} height={32} loading={loading}>
<Image src={row.image} alt="doctor" height={32} width={32} />
</CircularLoading>
<TextLoading width={40} height={18} loading={loading}>
{row.name}
</TextLoading>
</div>
</TableCell>
<TableCell
className="!text-start dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={30} height={18} loading={loading}>
{row.expertise}
</TextLoading>
</TableCell>
<TableCell
className="!text-start dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.date}
</TextLoading>
</TableCell>
<TableCell
className="!text-center dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.hour}
</TextLoading>
</TableCell>
<TableCell
className="!text-right dark:!border-transparent !text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.number_of_turns}
</TextLoading>
</TableCell>
</TableRow>
))}
</CustomTable>
</div>
);
}
export default Table;
-29
View File
@@ -1,29 +0,0 @@
import { Button } from "@mui/material";
import ArrowLeftP from "../../../icons/ArrowLeftP";
import Table from "./Table";
import ColTable from "../../ColTable";
function List({ data, loading }) {
return (
<div className="rounded-[8px] border overflow-hidden border-solid w-full border-transparent md:border-[#EFEFEF] bg-transparent md:bg-[#FFF] dark:bg-transparent md:dark:bg-[#222433] dark:border-transparent">
<div className="items-center justify-between p-[16px] flex">
<p className="text-[#525252] text-[14px] md:text-[16px] font-bold dark:text-[#D7D8ED]">
لیست نوبت های پزشکان
</p>
<Button
variant="text"
className="!hidden md:!flex !items-center !justify-center !text-[#616161] !text-[14px] !font-normal !gap-[4px] !p-1 dark:!text-[#A1A1A1]"
>
مشاهده همه
<ArrowLeftP />
</Button>
</div>
<div className="w-full overflow-auto">
<Table data={data} loading={loading} />
<ColTable data={data.list_of_doctors_appointments} loading={loading} />
</div>
</div>
);
}
export default List;
-14
View File
@@ -1,14 +0,0 @@
import FilterDate from "./FilterDate";
function Date() {
return (
<div className="flex items-center justify-start gap-y-[20px] gap-[8px] md:gap-0">
<p className="text-[#616161] text-[14px] font-normal md:hidden dark:text-[#a1a1a1]">
انتخاب بازه زمانی
</p>
<FilterDate />
</div>
);
}
export default Date;
-37
View File
@@ -1,37 +0,0 @@
import { useState } from "react";
import { convertToJalali } from "@/helper";
import { ButtonGroup } from "@mui/material";
import PopoverDate from "@/app/component/PopoverDate";
function FilterDate() {
const [date, setDate] = useState({
start: null,
end: null,
});
const updateDate = (newDate, type) => {
setDate({
...date,
[type]: newDate,
});
};
return (
<ButtonGroup variant="outlined">
<PopoverDate
type="start"
date={date.start}
updateDate={updateDate}
text={date.start ? convertToJalali(date.start) : "از تاریخ"}
/>
<PopoverDate
type="end"
date={date.end}
updateDate={updateDate}
text={date.end ? convertToJalali(date.end) : "تا تاریخ"}
/>
</ButtonGroup>
);
}
export default FilterDate;
@@ -1,53 +0,0 @@
import ArrowBottomP from "@/components/icons/ArrowBottomP";
import ExportP from "@/components/icons/ExportP";
import SearchD from "@/components/icons/SearchD";
import { Button, ButtonGroup, TextField } from "@mui/material";
function SearchDoctor() {
return (
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-y-[20px] justify-between mt-[20px] md:mt-[22px] lg:mt-[24px] ">
<ButtonGroup
variant="contained"
className="lg:!w-[55%] !rounded-[4px] !overflow-hidden
flex items-center !shadow-none !h-fit !p-1 !border !border-[#EFEFEF] dark:!border-[#343645]"
>
<TextField
variant="standard"
InputProps={{
disableUnderline: true,
}}
sx={{
background: "transparent !important",
"& .MuiInputBase-root": { height: "100%" },
"& .MuiInput-root": { borderBottom: "none" },
"& .MuiInputBase-input": {
// background: "red !important",
color: "#7E7E7E",
padding: "0",
},
}}
placeholder="جستجو براساس نام پزشک..."
dir="rtl"
className={`!shadow-none !w-full !px-5 !h-full !text-[14px] md:!text-[16px] !min-w-[50%] !rounded-0 !font-normal !text-[#6C757D]`}
/>
<Button
className="
!rounded-[4px] !p-[8px] !w-[56px] !h-[56px] !max-w-[40px] !max-h-[40px]
"
>
<SearchD />
</Button>
</ButtonGroup>
<Button
variant="outlined"
className="!border-[#5559CE] dark:!bg-[#C7CEF4] dark:!text-[#5559CE] !flex !text-[14px] md:!text-[16px] !items-center !gap-[4px] md:!gap-[5px] lg:!gap-[6px] !rounded-[4px] !py-[8px] md:!py-[9px] !px-[12px] sm:!px-[14px] md:!px-[16px] lg:!px-[18px]"
>
<ExportP />
خروجی گرفتن
<ArrowBottomP />
</Button>
</div>
);
}
export default SearchDoctor;
-18
View File
@@ -1,18 +0,0 @@
import Date from "./Date";
import SearchDoctor from "./SearchDoctor";
function Head() {
return (
<div className="w-full">
<div className="flex flex-col sm:flex-row items-start md:items-center justify-start gap-[10px]">
<p className="text-[#525252] dark:text-[#D7D8ED] text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-bold">
لیست نوبت های پزشکان
</p>
<Date />
</div>
<SearchDoctor />
</div>
);
}
export default Head;
-25
View File
@@ -1,25 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import Head from "./head";
import List from "./list";
function TurnsPage({ data }) {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);
return (
<div className="bg-transparent dark:shadow-[0px_4px_25px_10px_#1F1D2B] md:dark:bg-[#222433] md:bg-[#FFF] opacity-page rounded-[8px] shadow-none md:shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] pt-0 md:pt-[12px] lg:pt-[24px] px-0 md:px-[24px] p-[24px]">
<Head />
<List data={data} loading={loading} />
</div>
);
}
export default TurnsPage;
-7
View File
@@ -1,7 +0,0 @@
import React from "react";
function Cards() {
return <div className="flex hidden">Cards</div>;
}
export default Cards;
-83
View File
@@ -1,83 +0,0 @@
import Image from "next/image";
import { TableCell, TableRow } from "@mui/material";
import Pagination from "@/app/component/Pagination";
import CustomTable from "@/app/component/CustomTable";
import TextLoading from "@/app/component/loading/Text";
import CustomLoading from "@/app/component/loading/Custom";
import CircularLoading from "@/app/component/loading/Circular";
function Table({ data, loading }) {
const tableHead = ["ردیف", "نام پزشک", "تخصص", "تاریخ", "ساعت", "تعدد نوبت"];
const centerTable = [0, 4];
return (
<div className="hidden md:flex flex-col">
<CustomTable tableHead={tableHead} centerTable={centerTable}>
{data.list_of_doctors_appointments.map((row, idx) => (
<TableRow key={idx} className="!border-0 !border-b !border-[#DBDBDB]">
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="center"
>
<TextLoading width={15} height={18} loading={loading}>
{idx + 1}
</TextLoading>
</TableCell>
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
component="th"
scope="row"
>
<div className="flex items-center justify-start gap-[8px] !text-nowrap">
<CircularLoading width={32} height={32} loading={loading}>
<Image src={row.image} alt="doctor" height={32} width={32} />
</CircularLoading>
<TextLoading width={40} height={18} loading={loading}>
{row.name}
</TextLoading>
</div>
</TableCell>
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-start !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.expertise}
</TextLoading>
</TableCell>
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-start !pr-[18px] !text-nowrap"
align="left"
>
<TextLoading width={50} height={18} loading={loading}>
{row.date}
</TextLoading>
</TableCell>
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-center !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.hour}
</TextLoading>
</TableCell>
<TableCell
className="!text-[#616161] dark:!text-[#A1A1A1] !text-[16px] !font-medium !pr-[18px] !text-nowrap"
align="left"
>
<CustomLoading width={80} height={22} loading={loading}>
{row.number_of_turns}
</CustomLoading>
</TableCell>
</TableRow>
))}
</CustomTable>
<div className="w-full flex justify-center mt-[48px]">
<Pagination />
</div>
</div>
);
}
export default Table;
-13
View File
@@ -1,13 +0,0 @@
import ColTable from "../../ColTable";
import Table from "./Table";
function List({ data, loading }) {
return (
<div className="mt-[32px] pb-[20px] table-rounded-8">
<Table data={data} loading={loading} />
<ColTable data={data.list_of_doctors_appointments} loading={loading} />
</div>
);
}
export default List;
-35
View File
@@ -1,35 +0,0 @@
import Tabs from "@/app/component/Tabs";
function Tab({ value, listTab, handleChange }) {
return (
<Tabs
isSm={false}
style={{
".MuiTabs-indicator": {
height: "2px",
borderRadius: "16px",
background: "#5559CE",
},
"& .MuiTabs-flexContainer": {
borderBottom: "2px solid #EFEFEF",
},
"& .mui-11pdt05-MuiButtonBase-root-MuiTab-root.Mui-selected": {
color: "#5559CE !important",
},
"& .MuiButtonBase-root": {
fontWeight: "400 !important",
},
"& .MuiButtonBase-root.Mui-selected": {
color: "#5559CE !important",
fontWeight: "700 !important",
transition: "0.3s all !important",
},
}}
value={value}
listTab={listTab}
handleChange={handleChange}
/>
);
}
export default Tab;
@@ -1,12 +0,0 @@
"use client";
import UploadFile from "@/app/component/uploadFile";
import { useState } from "react";
function Head() {
const [image, setImage] = useState("");
return <UploadFile image={image} setImage={setImage} />;
}
export default Head;
@@ -1,157 +0,0 @@
"use client";
import { useState } from "react";
import ContentText from "../../../../../app/component/ContentText";
import EditField from "@/app/component/fields/EditField";
import AutoCompleteSelect from "@/app/component/element/AutoCompleteSelect";
import { bank, city, state } from "./listSelector";
import AddLocationP from "@/components/icons/AddLocationP";
import { Button } from "@mui/material";
import ArrowLeftButtonP from "@/components/icons/ArrowLeftButtonP";
const validate_phone_number = {
min: 1,
max: 9999999999,
};
function Form() {
const [data, setData] = useState({
name: { value: "", isEdit: true },
national_code: { value: "", isEdit: true },
address: { value: "", isEdit: true },
phone: { value: "", isEdit: true },
account_number: { value: "", isEdit: true },
});
const [selected, setSelected] = useState({
state: "",
city: "",
bank: "",
});
const updateSelect = (event, name) =>
setSelected({ ...selected, [name]: event });
const changeData = (value, type, name) =>
setData({
...data,
[name]: {
...data[name],
[type]: value,
},
});
const updateNum = (value, type, name) => {
const parseValue = +value;
const { max } = validate_phone_number;
const validateValue = parseValue > max ? data[name].value : value;
changeData(validateValue, type, name);
};
return (
<div className="flex flex-col items-start justify-start mt-[24px] md:mt-[28px] lg:mt-[32px] gap-[32px] md:gap-[40px] lg:gap-[48px]">
<ul className="grid w-full grid-cols-1 md:grid-cols-2 gap-x-[48px] gap-y-[24px] md:gap-y-[28px] lg:gap-y-[32px]">
<ContentText text="نام و نام خانوادگی">
<EditField
isTransparent={true}
iconGray={true}
changeData={changeData}
data={data?.name}
name="name"
/>
</ContentText>
<ContentText text="کد ملی">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.national_code}
noDisable={true}
type="number"
placeholder="کد ملی"
name="national_code"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="استان">
<AutoCompleteSelect
updateData={updateSelect}
label="انتخاب کنید..."
name="state"
list={state}
/>
</ContentText>
<ContentText text="شهر">
<AutoCompleteSelect
updateData={updateSelect}
label="انتخاب کنید..."
name="city"
list={city}
/>
</ContentText>
<ContentText text="آدرس">
<EditField
icon={<AddLocationP />}
isTransparent={true}
placeholder="از روی نقشه انتخاب کنید..."
iconGray={true}
changeData={changeData}
data={data?.address}
name="address"
/>
</ContentText>
<ContentText text="شماره موبایل">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.phone}
noDisable={true}
type="number"
placeholder="شماره موبایل"
name="phone"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="شماره حساب">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data?.account_number}
noDisable={true}
type="number"
placeholder="شماره حساب"
name="account_number"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="بانک">
<AutoCompleteSelect
updateData={updateSelect}
label="انتخاب کنید..."
name="bank"
list={bank}
/>
</ContentText>
</ul>
<div className="w-full flex justify-end">
<Button
variant="contained"
className="!gap-[4px] !py-[8px] !px-[24px] !rounded-[4px] !w-full md:!w-fit
!shadow-none !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
>
ثبت اطلاعات
<ArrowLeftButtonP />
</Button>
</div>
</div>
);
}
export default Form;
@@ -1,20 +0,0 @@
export const state = [
{ label: "استان 1", id: 10 },
{ label: "استان 2", id: 20 },
{ label: "استان 3", id: 30 },
{ label: "استان 4", id: 40 },
];
export const city = [
{ label: "شهر 1", id: 10 },
{ label: "شهر 2", id: 20 },
{ label: "شهر 3", id: 30 },
{ label: "شهر 4", id: 40 },
];
export const bank = [
{ label: "بانک 1", id: 10 },
{ label: "بانک 2", id: 20 },
{ label: "بانک 3", id: 30 },
{ label: "بانک 4", id: 40 },
];
@@ -1,13 +0,0 @@
import Form from "./form";
import Head from "./Head";
function Account() {
return (
<div className="opacity-page">
<Head />
<Form />
</div>
);
}
export default Account;
-91
View File
@@ -1,91 +0,0 @@
import CustomTable from "@/app/component/CustomTable";
import { Button, TableCell, TableRow } from "@mui/material";
import TextLoading from "@/app/component/loading/Text";
import TrashB from "@/components/icons/TrashB";
import EditB from "@/components/icons/EditB";
import SwitchTable from "@/app/component/SwitchTable";
function List({ data, loading, changeStatus }) {
const tableHead = ["ردیف", "شماره کارت", "شماره شبا", "بانک", "وضعیت", ""];
const centerTable = [0, 4, 5];
return (
<div className="!mt-[40px] hidden md:block">
<div className="hidden md:block w-full">
<CustomTable tableHead={tableHead} centerTable={centerTable}>
{data.map((row, idx) => (
<TableRow
key={idx}
className="!border-0 !border-b !border-[#DBDBDB]"
>
<TableCell
className="!pr-[18px] !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-[#616161] !text-nowrap"
align="center"
>
<TextLoading width={15} height={18} loading={loading}>
{idx + 1}
</TextLoading>
</TableCell>
<TableCell
className="!text-start !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-[#616161] !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
{row.card_number}
</TextLoading>
</TableCell>
<TableCell
className="!text-start !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-[#616161] !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={80} height={18} loading={loading}>
{row.shaba_number}
</TextLoading>
</TableCell>
<TableCell
className="!text-start !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !text-[16px] !font-medium !text-[#616161] !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={30} height={18} loading={loading}>
{row.bank}
</TextLoading>
</TableCell>
<TableCell
className="!text-start !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !pr-[18px] !text-nowrap"
align="right"
>
<TextLoading width={50} height={18} loading={loading}>
<div className="flex items-center justify-start gap-[16px]">
<p className="text-[16px] min-w-[60px] dark:text-[#A1A1A1] font-medium text-[#616161]">
{row.status ? "فعال" : "غیر فعال"}
</p>
<SwitchTable
id={row.id}
value={row.status}
changeStatus={changeStatus}
/>
</div>
</TextLoading>
</TableCell>
<TableCell
className="!text-center !bg-[#F8F8F8] dark:!bg-transparent dark:!text-[#A1A1A1] !pr-[18px] !text-nowrap"
align="right"
>
<div className="flex items-center justify-center gap-[4px]">
<Button className="!min-w-fit !p-1" variant="text">
<TrashB />
</Button>
<Button className="!min-w-fit !p-1" variant="text">
<EditB />
</Button>
</div>
</TableCell>
</TableRow>
))}
</CustomTable>
</div>
</div>
);
}
export default List;
@@ -1,78 +0,0 @@
import TextLoading from "@/app/component/loading/Text";
import SwitchTable from "@/app/component/SwitchTable";
import EditB from "@/components/icons/EditB";
import TrashB from "@/components/icons/TrashB";
import { Button } from "@mui/material";
function Item({ data, loading, changeStatus }) {
return (
<li className="rounded-[8px] bg-[#FFF] p-[12px] dark:shadow-transparent dark:bg-[#222433] shadow-[0px_1px_24.8px_0px_rgba(204,_204,_204,_0.18)]">
<div className="w-full flex items-center justify-between">
<div className="flex items-center justify-start gap-[4px]">
<TextLoading width={50} height={18} loading={loading}>
<div className="flex items-center justify-start gap-[16px]">
<p className="text-[16px] min-w-[60px] font-medium text-[#616161] dark:text-[#A1A1A1]">
{data.status ? "فعال" : "غیر فعال"}
</p>
<SwitchTable
id={data.id}
value={data.status}
changeStatus={changeStatus}
/>
</div>
</TextLoading>
</div>
<div className="flex items-center justify-center gap-[4px]">
<Button className="!min-w-fit !p-1" variant="text">
<TrashB />
</Button>
<Button className="!min-w-fit !p-1" variant="text">
<EditB />
</Button>
</div>
</div>
<ul className="flex flex-col mt-[16px]">
<li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={60} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal dark:text-[#A1A1A1]">
شماره کارت:
</p>
</TextLoading>
<TextLoading loading={loading} width={90} height={18}>
<p className="text-[#616161] text-[12px] font-medium dark:text-[#A1A1A1]">
{data.card_number}
</p>
</TextLoading>
</li>
<span className="block h-px w-[calc(100%-20px)] dark:bg-[#343645] mx-auto bg-[#EFEFEF] my-[8px]"></span>
<li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={60} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal dark:text-[#A1A1A1]">
شماره شبا:
</p>
</TextLoading>
<TextLoading loading={loading} width={90} height={18}>
<p className="text-[#616161] text-[12px] font-medium dark:text-[#A1A1A1]">
{data.shaba_number}
</p>
</TextLoading>
</li>
<span className="block h-px w-[calc(100%-20px)] mx-auto bg-[#EFEFEF] my-[8px] dark:bg-[#343645]"></span>
<li className="flex items-center justify-between gap-[12px]">
<TextLoading loading={loading} width={60} height={18}>
<p className="text-[#7E7E7E] text-[14px] font-normal dark:text-[#A1A1A1]">
بانک:
</p>
</TextLoading>
<TextLoading loading={loading} width={90} height={18}>
<p className="text-[#616161] text-[12px] font-medium dark:text-[#A1A1A1]">
{data.bank}
</p>
</TextLoading>
</li>
</ul>
</li>
);
}
export default Item;
@@ -1,13 +0,0 @@
import Item from "./Item";
function Cards({ data, loading, changeStatus }) {
return (
<ul className="md:hidden mt-[24px] grid grid-cols-1 sm:grid-cols-2 gap-[16px]">
{data.map((item, idx) => (
<Item data={item} key={idx} changeStatus={changeStatus} />
))}
</ul>
);
}
export default Cards;
@@ -1,14 +0,0 @@
import ModalAddCard from "./modal";
function Head() {
return (
<div className="w-full flex items-center justify-between">
<p className="text-[#525252] dark:text-[#A1A1A1] text-[20px] font-bold">
کارت های بانکی شما
</p>
<ModalAddCard />
</div>
);
}
export default Head;
@@ -1,58 +0,0 @@
import EditField from "@/app/component/fields/EditField";
import ContentText from "@/app/component/ContentText";
import AutoCompleteSelect from "@/app/component/element/AutoCompleteSelect";
import { validate_phone_number } from "./validate";
const list = [
{ label: "بانک 1", id: 10 },
{ label: "بانک 2", id: 20 },
{ label: "بانک 3", id: 30 },
{ label: "بانک 4", id: 40 },
];
function Form({ data, changeData, updateNum }) {
return (
<div className="mt-[32px] md:mt-[36px] lg:mt-[40px] flex flex-col gap-[24px] md:gap-[28px] lg:gap-[32px] px-0 sm:px-[52px]">
<ContentText text="شماره کارت">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data.card}
noDisable={true}
type="number"
placeholder="شماره کارت خود را بنویسید..."
name="card"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="شماره شبا">
<EditField
isTransparent={true}
iconGray={true}
changeData={updateNum}
data={data.shaba}
noDisable={true}
type="number"
placeholder="شماره شبا خود را بنویسید..."
name="shaba"
props={{
inputProps: validate_phone_number,
}}
/>
</ContentText>
<ContentText text="بانک">
<AutoCompleteSelect
updateData={changeData}
label="انتخاب بانک...."
name="bank"
list={list}
/>
</ContentText>
</div>
);
}
export default Form;
@@ -1,82 +0,0 @@
import { useState } from "react";
import Form from "./Form";
import { styleDefault } from "@/mui";
import { Box, Button, Modal } from "@mui/material";
// Icons
import PlusB from "@/components/icons/PlusB";
import CloseModalD from "@/components/icons/CloseModalD";
function ModalAddCard() {
const [open, setOpen] = useState(false);
const [data, setData] = useState({
card: { value: "", is_edit: true },
shaba: { value: "", is_edit: true },
bank: { value: "", is_edit: true },
});
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const changeData = (value, name) =>
setData({ ...data, [name]: { ...data[name], value } });
const updateNum = (value, type, name) => {
const parseValue = +value;
changeData(parseValue, name);
};
const isValidData = data.card && data.shaba && data.value && data.value.value;
return (
<>
{/* Button Modal */}
<Button
className="!bg-[#5559CE] !w-fit !shadow-none !rounded-[4px] !gap-[4px] !text-[#EFEFEF] !text-[14px] md:!text-[16px] !font-medium"
onClick={handleOpen}
variant="contained"
>
<PlusB />
اضافه کردن کارت
</Button>
{/* Content Modal */}
<Modal open={open} onClose={handleClose}>
<Box
sx={styleDefault}
className="!w-full dark:!bg-[#222433] dark:!shadow-none !h-full md:!h-fit sm:!w-fit !rounded-none sm:!rounded-[8px] sm:!min-w-[524px] !py-[20px] !pt-[24px] md:!pt-[22px] lg:!pt-[20px] !pb-[36px]"
>
<div className="flex items-center justify-between w-full">
<p className="text-[#3B3B3B] dark:text-[#D7D8ED] text-[16px] md:text-[18px] lg:text-[20px] font-bold">
اضافه کردن شماره کارت
</p>
<Button onClick={handleClose} className="!p-1" variant="text">
<CloseModalD />
</Button>
</div>
<Form data={data} changeData={changeData} updateNum={updateNum} />
<div className="flex items-center justify-center gap-[12px] md:gap-[14px] lg:gap-[16px] mt-[32px] md:mt-[36px] lg:mt-[40px]">
<Button
variant="outlined"
onClick={handleClose}
className="!min-w-[134px] !rounded-[4px] !h-[40px] md:!h-[44px] lg:!h-[48px] !border-[#828DE0] dark:!bg-[#C7CEF4]
dark:!border-transparent dark:!text-[#5559CE] !text-[#828DE0] !text-[16px] !font-medium"
>
انصراف
</Button>
<Button
variant="contained"
onClick={handleClose}
disabled={!isValidData}
className="!min-w-[134px] !rounded-[4px] !h-[40px] md:!h-[44px] lg:!h-[48px] !text-[#EFEFEF] !text-[16px] !font-medium"
>
ذخیره
</Button>
</div>
</Box>
</Modal>
</>
);
}
export default ModalAddCard;
@@ -1,6 +0,0 @@
const validate_phone_number = {
min: 1,
max: 9999999999,
};
export { validate_phone_number };
@@ -1,31 +0,0 @@
import { useState } from "react";
import Cards from "./cards";
import Head from "./head";
import List from "./List";
function Bank({ data, loading }) {
const [list, setList] = useState(data);
const changeStatus = (value, id) => {
let newList = list;
if (value) {
newList = newList.map((item, idx) => {
return {
...item,
status: item.id === id ? value : false,
};
});
}
setList(newList);
};
return (
<div className="opacity-page">
<Head />
<List data={list} loading={loading} changeStatus={changeStatus} />
<Cards data={list} loading={loading} changeStatus={changeStatus} />
</div>
);
}
export default Bank;
-38
View File
@@ -1,38 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import Account from "./account";
import Bank from "./bank";
import Tab from "./Tab";
const listTab = ["حساب کاربری", "حساب بانکی"];
function UserAccountPage({ data }) {
const [value, setValue] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);
const handleChange = (event, newValue) => setValue(newValue);
const components = [<Account />, <Bank loading={loading} data={data.bank} />];
return (
<div
className="
opacity-page rounded-[8px] bg-transparent md:bg-[#FFF] min-h-[80vh]
shadow-none md:shadow-[0px_1px_24.8px_0px_rgba(204,204,204,0.18)] py-0 md:py-[7px] lg:py-[14px] lg:!pb-[24px]
px-0 sm:px-[18px] md:px-[36px] lg:px-[44px] xl:px-[56px] dark:shadow-[0px_4px_25px_10px_#1F1D2B] dark:bg-transparent md:dark:bg-[#222433]
"
>
<Tab listTab={listTab} handleChange={handleChange} value={value} />
<div className="mt-[22px]">{components[value]}</div>
</div>
);
}
export default UserAccountPage;
-6
View File
@@ -1,5 +1,4 @@
import { AbilityBuilder, createMongoAbility } from "@casl/ability";
import { safeJsonParse } from "./sanitize";
export function defineAbilitiesFor(user) {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
@@ -7,11 +6,6 @@ export function defineAbilitiesFor(user) {
if (user) {
can("access", "Dashboard");
cannot("access", "Login");
const parsedData = safeJsonParse(user.value);
const roles = parsedData?.roles;
if (roles && Array.isArray(Object.values(roles)) && Object.values(roles).includes("representation")) {
can("access", "Panel");
}
} else {
can("access", "Login");
cannot("access", "Dashboard");
-67
View File
@@ -1,56 +1,3 @@
import moment from "moment-jalaali";
const DEFAULT_AVATAR = "/assets/images/doctor.png";
function toJalali(date) {
if (!date) return "--";
return moment(date).format("jYYYY/jM/jD");
}
export function adaptRepresentationInfo(representationInfo) {
const data = representationInfo?.data ?? representationInfo ?? {};
return {
name: data.full_name ?? "",
src: data.avatar ?? DEFAULT_AVATAR,
detail: data.city_name ? `نماینده شهر ${data.city_name}` : "نماینده",
mobile_number: data.mobile_number ?? "",
commission_percent: data.commission_percent ?? null,
bank_account: data.bank_account ?? null,
city: { label: data.city_name ?? "" },
all_patients: null,
total_turns: null,
number_of_patients: null,
today_turns: null,
payments: null,
todays_activities: [],
list_of_doctors_appointments: [],
chart_amount_income: [],
};
}
export function adaptRepresentationDashboard(dashboard) {
const stats = dashboard?.data?.stats ?? dashboard?.stats ?? {};
const daily = Array.isArray(stats.daily) ? stats.daily : [];
return {
number_of_patients: stats.total_appointments ?? 0,
today_turns: daily.length ? daily[daily.length - 1].appointments ?? 0 : 0,
payments: stats.total_revenue_rials ?? 0,
commission_rials: stats.commission_rials ?? 0,
chart_amount_income: daily.map((d) => d.revenue ?? 0),
list_of_doctors_appointments: daily.map((d) => ({
name: "",
expertise: "",
image: DEFAULT_AVATAR,
date: toJalali(d.date),
hour: "",
number_of_turns: d.appointments ?? 0,
})),
todays_activities: [],
};
}
export function buildPatientUser(profile, extras = {}) {
// GET /user-profile is double-nested: { data: { data: {...} } } (after fetchReq → response.data)
const data = profile?.data?.data ?? profile?.data ?? profile ?? {};
@@ -81,17 +28,3 @@ export function buildPatientUser(profile, extras = {}) {
messages: [],
};
}
export function adaptBankAccount(bankAccount) {
if (!bankAccount) return [];
return [
{
id: 1,
card_number: bankAccount.account_number ?? "",
shaba_number: bankAccount.iban ?? "",
bank: bankAccount.bank_name ?? "",
owner_name: bankAccount.owner_name ?? "",
status: true,
},
];
}