Compare commits

..

2 Commits

Author SHA1 Message Date
karimalden
1055417dc0 formate 2024-08-07 16:37:27 +03:00
karimalden
0f471f4483 pnpm 2024-08-07 16:36:09 +03:00
22 changed files with 2909 additions and 4474 deletions

View File

@ -1,66 +1,29 @@
{ {
"name": "my-app", "name": "my-app",
"version": "0.1.0", "version": "0.1.0",
"type": "module",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@types/draft-js": "^0.11.18",
"@types/katex": "^0.16.7",
"@types/mathjax": "^0.0.40",
"@types/node": "^20.14.0",
"@types/react-draft-wysiwyg": "^1.13.8",
"@types/react-helmet": "^6.1.11",
"algebra.js": "^0.2.6",
"antd": "^5.17.4", "antd": "^5.17.4",
"apexcharts": "^3.49.1",
"axios": "^1.7.2", "axios": "^1.7.2",
"better-react-mathjax": "^2.0.3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"draft-js": "^0.11.7",
"draft-js-latex-plugin": "^0.1.2",
"equation-resolver": "^1.0.0",
"formik": "^2.4.6", "formik": "^2.4.6",
"html2canvas": "^1.4.1", "html-to-image": "^1.11.11",
"i18next": "^23.11.5", "i18next": "^23.11.5",
"jspdf": "^2.5.1",
"katex": "^0.16.11",
"lodash.debounce": "^4.0.8",
"mammoth": "^1.8.0",
"mathjax": "^3.2.2",
"mathjax-react": "^2.0.1",
"mathjax3-react": "^1.2.0",
"mathquill": "0.10.1-a",
"nmath": "^1.0.0",
"path-to-regexp": "^6.2.2", "path-to-regexp": "^6.2.2",
"quill-image-resize-module": "^3.0.0", "pdf-lib": "^1.17.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-apexcharts": "^1.4.1",
"react-contenteditable": "^3.3.7",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-draft-wysiwyg": "^1.15.0",
"react-equation": "^1.0.0",
"react-google-docs-viewer": "^1.0.1",
"react-helmet": "^6.1.0",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-katex": "^3.0.1",
"react-latex-next": "^3.0.0",
"react-math": "^0.0.1",
"react-math-keyboard": "^1.5.17",
"react-mathjax": "^1.0.1",
"react-mathjax2": "^0.0.2",
"react-mathquill": "^1.0.3",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-quill": "^2.0.0",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-textarea-autosize": "^8.5.3",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"reactstrap": "^9.2.2", "reactstrap": "^9.2.2",
"sass": "^1.77.4", "sass": "^1.77.4",
"source-map-explorer": "^2.5.3", "ts-node": "^10.9.2",
"typescript": "^4.9.5", "vite-plugin-env-compatible": "^2.0.1",
"yup": "^1.4.0", "yup": "^1.4.0",
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },
@ -91,16 +54,25 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/node": "^20.14.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@vitejs/plugin-legacy": "^5.4.1",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"prettier": "^3.3.0", "prettier": "^3.3.0",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"ts-jest": "^29.1.4", "ts-jest": "^29.1.4",
"vite": "^5.2.12" "ts-loader": "^9.5.1",
"typescript": "^4.9.5",
"vite": "^5.2.12",
"vite-plugin-compression": "^0.5.1",
"webpack": "^5.93.0",
"webpack-cli": "^5.1.4"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ const App = () => {
key={"auth"} key={"auth"}
path={"/auth"} path={"/auth"}
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<Auth /> <Auth />
</Suspense> </Suspense>
} }
@ -25,7 +25,7 @@ const App = () => {
key={"Page404"} key={"Page404"}
path={"/*"} path={"/*"}
element={ element={
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<Page404 /> <Page404 />
</Suspense> </Suspense>
} }

View File

@ -13,4 +13,4 @@ const DropdownToggle: React.FC<DropdownToggleProps> = ({ isOpen, onClick }) => {
); );
}; };
export default DropdownToggle; export default DropdownToggle;

View File

@ -25,7 +25,10 @@ export const MenuItem = ({ item, location, index }: any) => {
<i>{item.icon}</i> <i>{item.icon}</i>
<Link to={item.path || "/"}>{t(item.text)}</Link> <Link to={item.path || "/"}>{t(item.text)}</Link>
{item?.children && ( {item?.children && (
<DropdownToggle isOpen={isDropdownOpen} onClick={() => handleDropdown(index)} /> <DropdownToggle
isOpen={isDropdownOpen}
onClick={() => handleDropdown(index)}
/>
)} )}
</div> </div>
@ -34,4 +37,4 @@ export const MenuItem = ({ item, location, index }: any) => {
)} )}
</> </>
); );
}; };

View File

@ -1,54 +1,58 @@
import React from 'react' import React from "react";
import { getLocalStorage } from '../../../utils/LocalStorage'; import { getLocalStorage } from "../../../utils/LocalStorage";
import { USER_KEY } from '../../../config/AppKey'; import { USER_KEY } from "../../../config/AppKey";
import { translateOptions } from '../../../utils/translatedOptions'; import { translateOptions } from "../../../utils/translatedOptions";
import { search_array } from '../../../Routes'; import { search_array } from "../../../Routes";
import { useTranslation } from 'react-i18next'; import { useTranslation } from "react-i18next";
import SearchFieldWithSelect from '../../DataTable/SearchFieldWithSelect'; import SearchFieldWithSelect from "../../DataTable/SearchFieldWithSelect";
import { Tooltip } from 'antd'; import { Tooltip } from "antd";
import useModalHandler from '../../../utils/useModalHandler'; import useModalHandler from "../../../utils/useModalHandler";
import { ModalEnum } from '../../../enums/Model'; import { ModalEnum } from "../../../enums/Model";
import Image from '../../Ui/Image'; import Image from "../../Ui/Image";
const NavBarRightSide = () => { const NavBarRightSide = () => {
const userData = getLocalStorage(USER_KEY); const userData = getLocalStorage(USER_KEY);
const [t] = useTranslation() const [t] = useTranslation();
const translateArray = translateOptions(search_array, t); const translateArray = translateOptions(search_array, t);
const { handel_open_model } = useModalHandler(); const { handel_open_model } = useModalHandler();
const handleEdit = () => { const handleEdit = () => {
handel_open_model(ModalEnum.CHANGE_PASSWORD); handel_open_model(ModalEnum.CHANGE_PASSWORD);
}; };
return ( return (
<article> <article>
<div className="header_search"> <div className="header_search">
<SearchFieldWithSelect <SearchFieldWithSelect
options={translateArray} options={translateArray}
placeholder={t("practical.search_here")} placeholder={t("practical.search_here")}
/> />
</div>
<span className="header_icons">
<div>
<Image src="/Icon/bell.png" alt="Notifications" />
</div> </div>
<Tooltip <span className="header_icons">
placement="top" <div>
title={<div onClick={handleEdit}>{t("header.change_your_current_password")}</div>} <Image src="/Icon/bell.png" alt="Notifications" />
color="#E0E0E0"
>
<div className="gear">
<Image src="/Icon/gear.png" alt="Settings" />
</div> </div>
</Tooltip> <Tooltip
</span> placement="top"
<div className="header_profile"> title={
<span> <div onClick={handleEdit}>
<h6>{userData?.username}</h6> {t("header.change_your_current_password")}
<p>{userData?.type}</p> </div>
}
color="#E0E0E0"
>
<div className="gear">
<Image src="/Icon/gear.png" alt="Settings" />
</div>
</Tooltip>
</span> </span>
<Image src="/Layout/DefaultStudentImage.png" alt="Profile" /> <div className="header_profile">
</div> <span>
</article> <h6>{userData?.username}</h6>
) <p>{userData?.type}</p>
} </span>
<Image src="/Layout/DefaultStudentImage.png" alt="Profile" />
</div>
</article>
);
};
export default NavBarRightSide export default NavBarRightSide;

View File

@ -10,10 +10,15 @@ const SubMenu: React.FC<SubMenuProps> = ({ items, location }) => {
return ( return (
<div className="sub-menu"> <div className="sub-menu">
{items.map((childItem, index) => ( {items.map((childItem, index) => (
<MenuItem key={index} item={childItem} location={location} index={index} /> <MenuItem
key={index}
item={childItem}
location={location}
index={index}
/>
))} ))}
</div> </div>
); );
}; };
export default SubMenu; export default SubMenu;

View File

@ -4,60 +4,60 @@ import { MdExpandLess, MdExpandMore } from "react-icons/md";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
export const MenuItem = ({ item, location, index }: any) => { export const MenuItem = ({ item, location, index }: any) => {
const isActive = location.pathname.split("/")[1] === item.path?.slice(1); const isActive = location.pathname.split("/")[1] === item.path?.slice(1);
// console.log(location.pathname.split("/")[1]); // console.log(location.pathname.split("/")[1]);
const [openDropdown, setOpenDropdown] = useState<number | null>(null); const [openDropdown, setOpenDropdown] = useState<number | null>(null);
const handleDropdown = (index: number) => { const handleDropdown = (index: number) => {
setOpenDropdown((prev) => (prev === index ? null : index)); setOpenDropdown((prev) => (prev === index ? null : index));
}; };
const isDropdownOpen = openDropdown === index; const isDropdownOpen = openDropdown === index;
const [t] = useTranslation(); const [t] = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<> <>
<div <div
className={`link ${isActive ? "active" : ""} ${item?.children && "DropDownLink"} `} className={`link ${isActive ? "active" : ""} ${item?.children && "DropDownLink"} `}
onClick={() => navigate(item.path || "/")} onClick={() => navigate(item.path || "/")}
> >
<i>{item.icon}</i> <i>{item.icon}</i>
<Link to={item.path || "/"}>{t(item.text)}</Link> <Link to={item.path || "/"}>{t(item.text)}</Link>
{item?.children && ( {item?.children && (
<> <>
{isDropdownOpen ? ( {isDropdownOpen ? (
<div <div
className="DropDownIcon" className="DropDownIcon"
onClick={() => handleDropdown(index)} onClick={() => handleDropdown(index)}
> >
<MdExpandLess /> <MdExpandLess />
</div> </div>
) : ( ) : (
<div <div
className="DropDownIcon" className="DropDownIcon"
onClick={() => handleDropdown(index)} onClick={() => handleDropdown(index)}
> >
<MdExpandMore /> <MdExpandMore />
</div> </div>
)} )}
</> </>
)}
</div>
{item?.children && isDropdownOpen && (
<div className="sub-menu">
{item.children.map((childItem: any, index: any) => (
<MenuItem
key={index}
item={childItem}
location={location}
index={index}
/>
))}
</div>
)} )}
</> </div>
);
}; {item?.children && isDropdownOpen && (
<div className="sub-menu">
{item.children.map((childItem: any, index: any) => (
<MenuItem
key={index}
item={childItem}
location={location}
index={index}
/>
))}
</div>
)}
</>
);
};

View File

@ -3,17 +3,13 @@ import SpinContainer from "../Layout/SpinContainer";
import Layout from "../../Layout/Ui/Layout"; import Layout from "../../Layout/Ui/Layout";
export const RenderRouteElement = (route: any) => ( export const RenderRouteElement = (route: any) => (
<Suspense <Suspense
fallback={ fallback={
<Layout> <Layout>
<SpinContainer /> <SpinContainer />
</Layout> </Layout>
} }
> >
{route.header ? ( {route.header ? <Layout>{route.element}</Layout> : route.element || <></>}
<Layout>{route.element}</Layout> </Suspense>
) : ( );
route.element || <></>
)}
</Suspense>
);

View File

@ -5,16 +5,15 @@ import { Route } from "react-router-dom";
import { RenderRouteElement } from "./RenderRouteElement"; import { RenderRouteElement } from "./RenderRouteElement";
export const renderRoutesRecursively = (routes: TMenuItem[]) => export const renderRoutesRecursively = (routes: TMenuItem[]) =>
routes.map((route: TMenuItem) => { routes.map((route: TMenuItem) => {
const useAbility = hasAbility(route.abilities, route.abilities_value); const useAbility = hasAbility(route.abilities, route.abilities_value);
if (!useAbility) { if (!useAbility) {
return false; return false;
} }
return ( return (
<React.Fragment key={route.path}> <React.Fragment key={route.path}>
<Route path={route.path} element={RenderRouteElement(route)} /> <Route path={route.path} element={RenderRouteElement(route)} />
{route.children && renderRoutesRecursively(route.children)} {route.children && renderRoutesRecursively(route.children)}
</React.Fragment> </React.Fragment>
); );
});
});

View File

@ -10,17 +10,14 @@ const Layout = ({
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
}) => { }) => {
return ( return (
<ProtectedRouteProvider className="Layout">
<ProtectedRouteProvider className="Layout"> <main className={`${className} Layout_Body`}>
<main className={`${className} Layout_Body`}> <NavBar />
<NavBar /> <div className="Layout_Children">{children}</div>
<div className="Layout_Children">{children}</div> </main>
</main> <SideBar />
<SideBar /> </ProtectedRouteProvider>
</ProtectedRouteProvider>
); );
}; };

View File

@ -17,7 +17,6 @@ const NavBar = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const PrevPath = getPrevPathRoute(location.pathname); const PrevPath = getPrevPathRoute(location.pathname);
const handelNavigate = () => { const handelNavigate = () => {
if (PrevPath === 0) { if (PrevPath === 0) {
return; return;
@ -25,18 +24,17 @@ const NavBar = () => {
navigate(deletePathSegments(location.pathname, PrevPath)); navigate(deletePathSegments(location.pathname, PrevPath));
}; };
return ( return (
<div className="NavBar"> <div className="NavBar">
<span className="navbar_link" onClick={handelNavigate}> <span className="navbar_link" onClick={handelNavigate}>
<MdOutlineArrowForwardIos /> {PageTitle} <MdOutlineArrowForwardIos /> {PageTitle}
</span> </span>
<NavBarRightSide/> <NavBarRightSide />
<Suspense fallback={<SpinContainer/>}> <Suspense fallback={<SpinContainer />}>
<ChangePasswordModel /> <ChangePasswordModel />
</Suspense> </Suspense>
</div> </div>
); );
}; };
export default NavBar; export default NavBar;

View File

@ -10,8 +10,6 @@ import { getLocalStorage } from "../../utils/LocalStorage";
import { BRANCH_OBJECT_KEY } from "../../config/AppKey"; import { BRANCH_OBJECT_KEY } from "../../config/AppKey";
import { MenuItem } from "../../Components/Layout/SideBar/MenuItem"; import { MenuItem } from "../../Components/Layout/SideBar/MenuItem";
const SideBar = () => { const SideBar = () => {
const location = useLocation(); const location = useLocation();

View File

@ -9,17 +9,12 @@ function ProviderContainer({ children }: ChildrenType) {
return ( return (
<BrowserRouter basename="/"> <BrowserRouter basename="/">
<I18nProvider> <I18nProvider>
<QueryProvider> <QueryProvider>
<ToastProvider> <ToastProvider>
<AntdProvider> <AntdProvider>{children}</AntdProvider>
</ToastProvider>
{children} </QueryProvider>
</AntdProvider> </I18nProvider>
</ToastProvider>
</QueryProvider>
</I18nProvider>
</BrowserRouter> </BrowserRouter>
); );
} }

View File

@ -70,7 +70,6 @@
color: black; color: black;
} }
} }
} }
.ModelBodyForm { .ModelBodyForm {
padding: 2vw; padding: 2vw;

View File

@ -1,4 +1,3 @@
import useAuthState from "../../zustand/AuthState"; import useAuthState from "../../zustand/AuthState";
import { BaseURL, HEADER_KEY } from "../config"; import { BaseURL, HEADER_KEY } from "../config";
import AxiosBuilder from "./AxiosBuilder"; import AxiosBuilder from "./AxiosBuilder";

View File

@ -1,7 +1,6 @@
export enum LocalStorageEnum { export enum LocalStorageEnum {
PROJECT_NAME = 'SCHOOL_DASHBOARD_EXERCISE', PROJECT_NAME = "SCHOOL_DASHBOARD_EXERCISE",
LANGUAGE_KEY = LocalStorageEnum.PROJECT_NAME + '_LANGUAGE', LANGUAGE_KEY = LocalStorageEnum.PROJECT_NAME + "_LANGUAGE",
TOKEN_KEY = LocalStorageEnum.PROJECT_NAME + '_TOKEN_KEY', TOKEN_KEY = LocalStorageEnum.PROJECT_NAME + "_TOKEN_KEY",
USER_KEY = LocalStorageEnum.PROJECT_NAME + '_USER_KEY', USER_KEY = LocalStorageEnum.PROJECT_NAME + "_USER_KEY",
} }

View File

@ -1,23 +1,23 @@
import { ConfigProvider } from 'antd'; import { ConfigProvider } from "antd";
function AntdProvider({ children }: { children: React.ReactNode }) { function AntdProvider({ children }: { children: React.ReactNode }) {
const primaryColor = "#3182ce"; const primaryColor = "#3182ce";
const bgColor = "rgb(255, 255, 255)"; const bgColor = "rgb(255, 255, 255)";
return ( return (
<ConfigProvider <ConfigProvider
theme={{ theme={{
token: { token: {
colorPrimary: primaryColor, colorPrimary: primaryColor,
},
components: {
Table: {
headerBg: bgColor,
headerColor: primaryColor,
}, },
}, components: {
}} Table: {
> headerBg: bgColor,
headerColor: primaryColor,
},
},
}}
>
{children} {children}
</ConfigProvider> </ConfigProvider>
); );

View File

@ -1,6 +1,6 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from "react";
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from "react-i18next";
import i18n from './i18nConfig'; // Import the configured i18n instance import i18n from "./i18nConfig"; // Import the configured i18n instance
interface I18nProviderProps { interface I18nProviderProps {
children: ReactNode; children: ReactNode;
@ -10,4 +10,4 @@ const I18nProvider: React.FC<I18nProviderProps> = ({ children }) => {
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>; return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
}; };
export default I18nProvider; export default I18nProvider;

View File

@ -6,7 +6,10 @@ interface ProtectedRouteProviderProps extends React.HTMLProps<HTMLDivElement> {
children: React.ReactNode; children: React.ReactNode;
} }
function ProtectedRouteProvider({ children, ...props }: ProtectedRouteProviderProps) { function ProtectedRouteProvider({
children,
...props
}: ProtectedRouteProviderProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const { isAuthenticated } = useAuthState(); const { isAuthenticated } = useAuthState();
@ -16,11 +19,7 @@ function ProtectedRouteProvider({ children, ...props }: ProtectedRouteProviderPr
} }
}, [isAuthenticated, navigate]); }, [isAuthenticated, navigate]);
return ( return <div {...props}>{children}</div>;
<div {...props}>
{children}
</div>
);
} }
export default ProtectedRouteProvider; export default ProtectedRouteProvider;

View File

@ -1,6 +1,6 @@
import i18n from 'i18next'; import i18n from "i18next";
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from "react-i18next";
import translationAR from '../translate/ar.json'; import translationAR from "../translate/ar.json";
const resources = { const resources = {
ar: { ar: {
@ -10,10 +10,10 @@ const resources = {
i18n.use(initReactI18next).init({ i18n.use(initReactI18next).init({
resources, resources,
lng: 'ar', // Set the default language lng: "ar", // Set the default language
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
}); });
export default i18n; export default i18n;

View File

@ -14,7 +14,7 @@ interface AuthStore {
const useAuthState = create<AuthStore>((set) => { const useAuthState = create<AuthStore>((set) => {
const storedToken = localStorage.getItem(TOKEN_KEY); const storedToken = localStorage.getItem(TOKEN_KEY);
const storedAbilities = localStorage.getItem(ABILITIES_KEY); const storedAbilities = localStorage.getItem(ABILITIES_KEY);
return { return {
isAuthenticated: !!storedToken, isAuthenticated: !!storedToken,
token: storedToken, token: storedToken,
@ -36,7 +36,7 @@ const useAuthState = create<AuthStore>((set) => {
localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY); localStorage.removeItem(USER_KEY);
localStorage.removeItem(ABILITIES_KEY); localStorage.removeItem(ABILITIES_KEY);
set(() => ({ set(() => ({
isAuthenticated: false, isAuthenticated: false,
token: null, token: null,