first_Push
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
25
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"aldeen",
|
||||||
|
"antd",
|
||||||
|
"Attendence",
|
||||||
|
"Checkboxhandle",
|
||||||
|
"Createstudent",
|
||||||
|
"Datepicker",
|
||||||
|
"formik",
|
||||||
|
"Groupbutton",
|
||||||
|
"handelnavigate",
|
||||||
|
"Karim",
|
||||||
|
"queryqlent",
|
||||||
|
"registraion",
|
||||||
|
"SENDNOTIFICATION",
|
||||||
|
"setdateparams",
|
||||||
|
"szhsin",
|
||||||
|
"tagcontainer",
|
||||||
|
"toastify",
|
||||||
|
"Viewelement",
|
||||||
|
"zustand",
|
||||||
|
"مطلوب"
|
||||||
|
],
|
||||||
|
"vite.https": true
|
||||||
|
}
|
||||||
34
index.html
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ar" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/App/Logo.png" />
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/App/Logo.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/App/Logo.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="social networking platform with automated content moderation and context-based authentication system"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
|
|
||||||
|
<title>SCHOOL_DASHBOARD_EXERCISE</title>
|
||||||
|
</head>
|
||||||
|
<body dir="rtl">
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
76
package.json
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.3.7",
|
||||||
|
"@types/node": "^20.14.0",
|
||||||
|
"@types/react-helmet": "^6.1.11",
|
||||||
|
"antd": "^5.17.4",
|
||||||
|
"apexcharts": "^3.49.1",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
|
"formik": "^2.4.6",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"i18next": "^23.11.5",
|
||||||
|
"jspdf": "^2.5.1",
|
||||||
|
"path-to-regexp": "^6.2.2",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-apexcharts": "^1.4.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-i18next": "^13.5.0",
|
||||||
|
"react-icons": "^4.12.0",
|
||||||
|
"react-query": "^3.39.3",
|
||||||
|
"react-router-dom": "^6.23.1",
|
||||||
|
"react-toastify": "^9.1.3",
|
||||||
|
"reactstrap": "^9.2.2",
|
||||||
|
"sass": "^1.77.4",
|
||||||
|
"source-map-explorer": "^2.5.3",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"yup": "^1.4.0",
|
||||||
|
"zustand": "^4.5.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite --port=3000",
|
||||||
|
"build": "vite build",
|
||||||
|
"test": "vite jest",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/testing-library__jest-dom": "^6.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jsdom": "^24.1.0",
|
||||||
|
"prettier": "^3.3.0",
|
||||||
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
|
"ts-jest": "^29.1.4",
|
||||||
|
"vite": "^5.2.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
6205
pnpm-lock.yaml
Normal file
BIN
public/App/Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/ArrowType/20.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/ArrowType/40.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/ArrowType/60.png
Normal file
|
After Width: | Height: | Size: 962 B |
BIN
public/ArrowType/80.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/ArrowType/90.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/Cards/Graph.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/DataState/EmptyData.gif
Normal file
|
After Width: | Height: | Size: 698 KiB |
BIN
public/DataState/loading.gif
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/DataState/successfully.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/Home/HomeCounter.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
187
public/Home/welcome.svg
Normal file
|
After Width: | Height: | Size: 37 KiB |
17
public/Icon/Add.svg
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<svg viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>add_circle</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Rounded" transform="translate(-102.000000, -1484.000000)">
|
||||||
|
<g id="Content" transform="translate(100.000000, 1428.000000)">
|
||||||
|
<g id="-Round-/-Content-/-add_circle" transform="translate(0.000000, 54.000000)">
|
||||||
|
<g>
|
||||||
|
<polygon id="Path" points="0 0 24 0 24 24 0 24"></polygon>
|
||||||
|
<path d="M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M16,13 L13,13 L13,16 C13,16.55 12.55,17 12,17 C11.45,17 11,16.55 11,16 L11,13 L8,13 C7.45,13 7,12.55 7,12 C7,11.45 7.45,11 8,11 L11,11 L11,8 C11,7.45 11.45,7 12,7 C12.55,7 13,7.45 13,8 L13,11 L16,11 C16.55,11 17,11.45 17,12 C17,12.55 16.55,13 16,13 Z" id="🔹Icon-Color" fill="#3182ce"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/Icon/Animation.gif
Normal file
|
After Width: | Height: | Size: 619 KiB |
BIN
public/Icon/Empty_Image.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/Icon/Error.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/Icon/Flash.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
public/Icon/add_students.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/Icon/bell.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
public/Icon/cash.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
5
public/Icon/cercile.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg viewBox="0 0 377 446" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<ellipse opacity="0.1" cx="293" cy="326.206" rx="293" ry="326.206" fill="white" fill-opacity="0.66"/>
|
||||||
|
<ellipse opacity="0.1" cx="293" cy="326.207" rx="181.81" ry="202.415" fill="white" fill-opacity="0.66"/>
|
||||||
|
<ellipse opacity="0.1" cx="292.999" cy="326.206" rx="94.6615" ry="105.39" fill="white" fill-opacity="0.66"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 399 B |
BIN
public/Icon/chart.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
public/Icon/gear.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/Icon/medal.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/Icon/warning.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/Image/1.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/Image/user_fake.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
15
public/Layout/Background_Header.svg
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
public/Layout/DefultStudentImage.png
Normal file
|
After Width: | Height: | Size: 861 B |
BIN
public/Layout/ErrorUserImage.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/Layout/Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/Layout/OLogo.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
public/Layout/auth_bg.png
Normal file
|
After Width: | Height: | Size: 665 KiB |
BIN
public/Subject/algebra.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/Subject/flask.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/Subject/koran.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
3
public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
27
public/site.webmanifest
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "SCHOOL_DASHBOARD_EXERCISE",
|
||||||
|
"short_name": "RS",
|
||||||
|
"description": "A social networking platform with automated content moderation and context-based authentication system.",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/App/Logo.png",
|
||||||
|
"sizes": "81x113",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/App/Logo.png",
|
||||||
|
"sizes": "81x113",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/App/Logo.png",
|
||||||
|
"sizes": "81x113",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
public/student/student_bg.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1082" height="138" viewBox="0 0 1082 138">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip-path">
|
||||||
|
<rect id="Rectangle_1" data-name="Rectangle 1" width="1082" height="138" transform="translate(419 362)" fill="#fff"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(-419 -362)" clip-path="url(#clip-path)">
|
||||||
|
<g id="cover" transform="translate(369 332)">
|
||||||
|
<g id="Group_1" data-name="Group 1">
|
||||||
|
<path id="Path_1" data-name="Path 1" d="M50,50A20,20,0,0,1,70,30H1112a20,20,0,0,1,20,20V170H50Z" fill="#3182ce"/>
|
||||||
|
</g>
|
||||||
|
<g id="Group_2" data-name="Group 2">
|
||||||
|
<rect id="Rectangle_1-2" data-name="Rectangle 1" width="280.439" height="275.13" rx="20" transform="translate(230.408 111.564)" fill="#fb7d5b"/>
|
||||||
|
<rect id="Rectangle_2" data-name="Rectangle 2" width="280.439" height="275.13" rx="20" transform="translate(92 61)" fill="#fcc43e"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
101
src/App.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React, { Suspense, lazy, useEffect } from "react";
|
||||||
|
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||||
|
import Layout from "./Layout/Ui/Layout";
|
||||||
|
import { CrudRoute, menuItems } from "./Routes";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
import { hasAbility } from "./utils/hasAbility";
|
||||||
|
import { useChangeLanguage } from "./Hooks/useChangeLanguage";
|
||||||
|
import useAuthState from "./zustand/AuthState";
|
||||||
|
import { TMenuItem } from "./types/App";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import useSetPage_title from "./Hooks/useSetPageTitle";
|
||||||
|
import SpinContainer from "./Components/Layout/SpinContainer";
|
||||||
|
|
||||||
|
const Page404 = lazy(() => import("./Layout/Ui/NotFoundPage"));
|
||||||
|
const Auth = lazy(() => import("./Pages/Auth/Page"));
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const { changeLanguage } = useChangeLanguage();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { isAuthenticated } = useAuthState();
|
||||||
|
const [t] = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
navigate("/auth");
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, navigate]);
|
||||||
|
|
||||||
|
const renderRouteElement = (route: any) => (
|
||||||
|
<Suspense fallback={<Layout> <SpinContainer/> </Layout>}>
|
||||||
|
{route.header ? (
|
||||||
|
<Layout>{route.element}</Layout>
|
||||||
|
) : (
|
||||||
|
route.element || <h1>please Create the Page</h1>
|
||||||
|
)}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderRoutesRecursively = (routes:TMenuItem[]) =>
|
||||||
|
routes.map((route: TMenuItem) => {
|
||||||
|
const useAbility = hasAbility(route.abilities, route.abilities_value);
|
||||||
|
const tableHeader = t(`${route?.header}`);
|
||||||
|
useSetPage_title(tableHeader,route?.path);
|
||||||
|
|
||||||
|
if (useAbility) {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={route.path}>
|
||||||
|
<Route path={route.path} element={renderRouteElement(route)} />
|
||||||
|
{route.children && renderRoutesRecursively(route.children)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
key={"auth"}
|
||||||
|
path={"/auth"}
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Spin />}>
|
||||||
|
{" "}
|
||||||
|
<Auth />{" "}
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
key={"Page404"}
|
||||||
|
path={"/*"}
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Spin />}>
|
||||||
|
{" "}
|
||||||
|
<Page404 />{" "}
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{renderRoutesRecursively(menuItems)}
|
||||||
|
|
||||||
|
{CrudRoute.map((route) => {
|
||||||
|
const useAbility = hasAbility(route.abilities, route.abilities_value);
|
||||||
|
const tableHeader = t(`${route?.header}`);
|
||||||
|
useSetPage_title(tableHeader,route?.path);
|
||||||
|
|
||||||
|
if (!useAbility) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={route.path ?? ""}
|
||||||
|
path={route.path ?? ""}
|
||||||
|
element={renderRouteElement(route)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
58
src/Components/Chart/AreaChart.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import ReactApexChart from "react-apexcharts";
|
||||||
|
|
||||||
|
const AreaChart: React.FC = () => {
|
||||||
|
const chartRef = useRef<any>(null); // Ref for the chart component
|
||||||
|
const [options, setOptions] = useState<any>({
|
||||||
|
chart: {
|
||||||
|
type: "area",
|
||||||
|
toolbar: {
|
||||||
|
show: false, // Disable the toolbar
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "smooth",
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
type: "datetime",
|
||||||
|
categories: [
|
||||||
|
"2018-09-19T00:00:00.000Z",
|
||||||
|
"2018-09-19T01:30:00.000Z",
|
||||||
|
"2018-09-19T02:30:00.000Z",
|
||||||
|
"2018-09-19T03:30:00.000Z",
|
||||||
|
"2018-09-19T04:30:00.000Z",
|
||||||
|
"2018-09-19T05:30:00.000Z",
|
||||||
|
"2018-09-19T06:30:00.000Z",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
x: {
|
||||||
|
format: "dd/MM/yy HH:mm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [series, setSeries] = useState<any>([
|
||||||
|
{ name: "series1", data: [31, 40, 28, 51, 42, 109, 100], color: "#FCC43E" },
|
||||||
|
{ name: "series2", data: [11, 32, 45, 32, 34, 52, 41], color: "#FB7D5B" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="AreaChart">
|
||||||
|
<div id="chart">
|
||||||
|
<ReactApexChart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="area"
|
||||||
|
className="ReactApexChartArea"
|
||||||
|
height={400}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="html-dist"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AreaChart;
|
||||||
95
src/Components/Chart/ColumnChart.tsx
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { Radio, Select } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import ReactApexChart from "react-apexcharts";
|
||||||
|
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
|
||||||
|
const ColumnChart: React.FC = () => {
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "غياب",
|
||||||
|
data: [44, 55, 57, 56, 61, 58],
|
||||||
|
color: "#FB7D5B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "حضور",
|
||||||
|
data: [76, 85, 70, 98, 87, 80],
|
||||||
|
color: "#FCC43E",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
type: "bar",
|
||||||
|
},
|
||||||
|
plotOptions: {
|
||||||
|
bar: {
|
||||||
|
horizontal: false,
|
||||||
|
columnWidth: "35%",
|
||||||
|
endingShape: "rounded",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: ["Feb", "Mar", "Apr", "May", "Jun", "Jul"],
|
||||||
|
},
|
||||||
|
// yaxis: {
|
||||||
|
// title: {
|
||||||
|
// text: '$ (thousands)'
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
fill: {
|
||||||
|
opacity: 1,
|
||||||
|
colors: ["#FB7D5B", "#FCC43E"], // Red and Green colors
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
y: {
|
||||||
|
formatter: function (val: number) {
|
||||||
|
return val + " طالب";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false, // Hide the legend
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const [value, setValue] = useState(1);
|
||||||
|
|
||||||
|
const handleChangeSelect = (value: string) => {};
|
||||||
|
return (
|
||||||
|
<div className="ColumnChart">
|
||||||
|
<div className="ColumnChart_header">
|
||||||
|
<span>الغياب المتكرر</span>
|
||||||
|
|
||||||
|
<span className="Legend">
|
||||||
|
<div data-attr="red">غياب</div>
|
||||||
|
<div>حضور</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
defaultValue="هذا الأسبوع"
|
||||||
|
onChange={handleChangeSelect}
|
||||||
|
suffixIcon={<IoMdArrowDropdown size={30} />}
|
||||||
|
options={[
|
||||||
|
{ value: "هذا الأسبوع", label: "هذا الأسبوع" },
|
||||||
|
{ value: "هذا الأسبوع", label: "هذا الأسبوع" },
|
||||||
|
{ value: "هذا الأسبوع", label: "هذا الأسبوع" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ColumnChartWrapper" id="chart">
|
||||||
|
<ReactApexChart
|
||||||
|
options={options}
|
||||||
|
series={series}
|
||||||
|
type="bar"
|
||||||
|
height={"100%"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="html-dist"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnChart;
|
||||||
61
src/Components/Chart/LineChart.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import React from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
|
||||||
|
const LineChart: React.FC = () => {
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: "Desktops",
|
||||||
|
data: [10, 41, 35, 51, 49, 62, 69, 91, 148],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
chart: {
|
||||||
|
height: 350,
|
||||||
|
type: "line",
|
||||||
|
zoom: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: "straight",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: "Product Trends by Month",
|
||||||
|
align: "left",
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
row: {
|
||||||
|
colors: ["#f3f3f3", "transparent"], // takes an array which will be repeated on columns
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
categories: [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div id="chart">
|
||||||
|
<Chart options={options} series={series} type="line" height={350} />
|
||||||
|
</div>
|
||||||
|
<div id="html-dist"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineChart;
|
||||||
38
src/Components/Chart/PieChart.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from "react";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
|
||||||
|
const PieChart = () => {
|
||||||
|
const series = [44, 55, 13] as any;
|
||||||
|
const options: any = {
|
||||||
|
series: [44, 55, 13],
|
||||||
|
chart: {
|
||||||
|
width: 380,
|
||||||
|
type: "pie",
|
||||||
|
},
|
||||||
|
labels: ["Team A", "Team B", "Team C", "Team D", "Team E"],
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 480,
|
||||||
|
options: {
|
||||||
|
chart: {
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: "bottom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="chart">
|
||||||
|
<Chart options={options} series={series} type="pie" width={380} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PieChart;
|
||||||
14
src/Components/Classes/ClassesSegmented.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Segmented } from "antd";
|
||||||
|
|
||||||
|
const ClassesSegmented: React.FC = () => (
|
||||||
|
<Segmented<string>
|
||||||
|
options={["الأحد", "اللإثنين", "الثلاثاء", "الأربعاء", "الخميس"]}
|
||||||
|
onChange={(value) => {}}
|
||||||
|
className="Segmented"
|
||||||
|
block
|
||||||
|
defaultChecked
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ClassesSegmented;
|
||||||
83
src/Components/Columns/ColumnsImage.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import {
|
||||||
|
DownloadOutlined,
|
||||||
|
RotateLeftOutlined,
|
||||||
|
RotateRightOutlined,
|
||||||
|
SwapOutlined,
|
||||||
|
ZoomInOutlined,
|
||||||
|
ZoomOutOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import React from "react";
|
||||||
|
import { Image, Space } from "antd";
|
||||||
|
import { ImageBaseURL } from "../../api/config";
|
||||||
|
import useImageError from "../../Hooks/useImageError";
|
||||||
|
import { ErrorImage } from "../../Layout/app/Const";
|
||||||
|
|
||||||
|
const ColumnsImage = ({ src }: any) => {
|
||||||
|
const imageUrl = src || ErrorImage;
|
||||||
|
|
||||||
|
const handleError = useImageError;
|
||||||
|
// or you can download flipped and rotated image
|
||||||
|
// https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp
|
||||||
|
const onDownload = () => {
|
||||||
|
fetch(src)
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
const url = URL.createObjectURL(new Blob([blob]));
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = "image.png";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
link.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
width={45}
|
||||||
|
height={45}
|
||||||
|
src={imageUrl}
|
||||||
|
className="p-1 mb-1 columnImage "
|
||||||
|
preview={{
|
||||||
|
toolbarRender: (
|
||||||
|
_,
|
||||||
|
{
|
||||||
|
transform: { scale },
|
||||||
|
actions: {
|
||||||
|
onFlipY,
|
||||||
|
onFlipX,
|
||||||
|
onRotateLeft,
|
||||||
|
onRotateRight,
|
||||||
|
onZoomOut,
|
||||||
|
onZoomIn,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) => (
|
||||||
|
<Space size={12} className="toolbar-wrapper">
|
||||||
|
{/* <DownloadOutlined onClick={onDownload} /> */}
|
||||||
|
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||||
|
<SwapOutlined onClick={onFlipX} />
|
||||||
|
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||||
|
<RotateRightOutlined onClick={onRotateRight} />
|
||||||
|
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||||
|
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onError={handleError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnsImage;
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: t("image"),
|
||||||
|
// center: "true",
|
||||||
|
// cell: (row: any) => {
|
||||||
|
// return (
|
||||||
|
// <ColumnsImage src={row?.image} />
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// },
|
||||||
40
src/Components/Columns/ColumnsSwitch.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Switch } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { CheckOutlined, CloseOutlined } from "@ant-design/icons";
|
||||||
|
export interface ColumnsSwitchProps {
|
||||||
|
Front?: string;
|
||||||
|
Back?: string;
|
||||||
|
onChange?: (checked: any, event: any) => any;
|
||||||
|
icon?: boolean;
|
||||||
|
Null?: boolean;
|
||||||
|
|
||||||
|
Checked?: boolean;
|
||||||
|
}
|
||||||
|
const ColumnsSwitch = (props: ColumnsSwitchProps) => {
|
||||||
|
const { Front, Back, icon, onChange, Checked, Null } = props;
|
||||||
|
|
||||||
|
const onSwitchChange = (checked: boolean, event: Event) => {
|
||||||
|
// formik.setFieldValue("status", checked)
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checkedChildren={icon ? <CheckOutlined /> : Null ? null : Front}
|
||||||
|
unCheckedChildren={icon ? <CloseOutlined /> : Null ? null : Back}
|
||||||
|
onChange={(checked: any, event: any) =>
|
||||||
|
onChange ? onChange(checked, event) : onSwitchChange(checked, event)
|
||||||
|
}
|
||||||
|
checked={Checked}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnsSwitch;
|
||||||
|
|
||||||
|
ColumnsSwitch.defaultProps = {
|
||||||
|
Front: "Front",
|
||||||
|
Back: "Back",
|
||||||
|
onChange: undefined,
|
||||||
|
icon: false,
|
||||||
|
Checked: false,
|
||||||
|
Null: false,
|
||||||
|
};
|
||||||
105
src/Components/Course/CourseHeaderSection.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { FaPlus, FaUser } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown, IoMdArrowDropup } from "react-icons/io";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Popover, Spin } from "antd";
|
||||||
|
import { CourseItem } from "../../types/Item";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import useModalHandler from "../../utils/useModalHandler";
|
||||||
|
import { FaEllipsis } from "react-icons/fa6";
|
||||||
|
import { ABILITIES_ENUM, ABILITIES_VALUES_ENUM } from "../../enums/abilities";
|
||||||
|
import { hasAbility } from "../../utils/hasAbility";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const HeaderCardsSection = ({ data, isLoading }: any) => {
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(false);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const toggleShowAll = () => {
|
||||||
|
setShowAll(!showAll);
|
||||||
|
};
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const index_eduClass_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.EDUCATION_CLASS,
|
||||||
|
ABILITIES_VALUES_ENUM?.INDEX,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handelnavigate = (item: CourseItem) => {
|
||||||
|
if (index_eduClass_ability) {
|
||||||
|
navigate(
|
||||||
|
`/${ABILITIES_ENUM?.COURSE}/${item?.id}/${ABILITIES_ENUM?.EDUCATION_CLASS}`,
|
||||||
|
);
|
||||||
|
// set_title(item?.name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { handel_open_model } = useModalHandler();
|
||||||
|
const Courses = data?.data || [];
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopoverContent = (
|
||||||
|
<div className="PopoverContent">
|
||||||
|
<p onClick={() => handel_open_model(ModalEnum?.COURSE_ADD)}>
|
||||||
|
{" "}
|
||||||
|
{t("practical.add")} {t("models.course")} <FaPlus />{" "}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const store_course_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.COURSE,
|
||||||
|
ABILITIES_VALUES_ENUM?.STORE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="header_cards_section">
|
||||||
|
<div className="CountCards">
|
||||||
|
{Courses?.length > 0 &&
|
||||||
|
Courses?.map((item: CourseItem, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => handelnavigate(item)}
|
||||||
|
key={item?.id}
|
||||||
|
className={`CountCard ${!showAll && index >= 6 ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
<i>
|
||||||
|
<FaUser />
|
||||||
|
</i>
|
||||||
|
<h4>{item?.name}</h4>
|
||||||
|
<h5>
|
||||||
|
{item?.student_count} {t("models.student")}{" "}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{Courses?.length > 4 &&
|
||||||
|
(showAll ? (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropup />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropdown />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{store_course_ability && (
|
||||||
|
<Popover
|
||||||
|
content={PopoverContent}
|
||||||
|
overlayClassName="Popover"
|
||||||
|
arrow={false}
|
||||||
|
placement="bottomRight"
|
||||||
|
trigger={"click"}
|
||||||
|
>
|
||||||
|
<FaEllipsis />
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderCardsSection;
|
||||||
71
src/Components/CustomFields/File.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { LoadingOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
|
import { message, Upload } from "antd";
|
||||||
|
import type { GetProp, UploadProps } from "antd";
|
||||||
|
import { FaImage } from "react-icons/fa";
|
||||||
|
import { TbCameraPlus } from "react-icons/tb";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
|
||||||
|
type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
|
||||||
|
|
||||||
|
const getBase64 = (img: FileType, callback: (url: string) => void) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => callback(reader.result as string));
|
||||||
|
reader.readAsDataURL(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomFile: React.FC = () => {
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
const FormikName = formik.values["image"] ?? "";
|
||||||
|
|
||||||
|
const [imageUrl, setImageUrl] = useState<string>(FormikName);
|
||||||
|
useEffect(() => {
|
||||||
|
setImageUrl(FormikName);
|
||||||
|
}, [FormikName]);
|
||||||
|
|
||||||
|
const handleChange: UploadProps["onChange"] = (info) => {
|
||||||
|
if (info.file.status === "done") {
|
||||||
|
getBase64(info.file.originFileObj as FileType, (url) => {
|
||||||
|
setImageUrl(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const customRequest = async ({ onSuccess }: any) => {
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
const uploadButton = (
|
||||||
|
<button style={{ border: 0, background: "none" }} type="button">
|
||||||
|
<div className="CustomFile">السحب والإفلات أو انقر هنا لتحديد الملف</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
const uploadIcon = (
|
||||||
|
<div className="uploadIcon">
|
||||||
|
<TbCameraPlus />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`ValidationField ${imageUrl && `have_value`} w-100`}>
|
||||||
|
<label className="text">الصورة * </label>
|
||||||
|
|
||||||
|
<Upload
|
||||||
|
name="avatar"
|
||||||
|
listType="picture-card"
|
||||||
|
className="avatar-uploader"
|
||||||
|
showUploadList={false}
|
||||||
|
customRequest={customRequest}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{imageUrl ? (
|
||||||
|
<img src={imageUrl} alt="avatar" style={{ width: "100%" }} />
|
||||||
|
) : (
|
||||||
|
uploadButton
|
||||||
|
)}
|
||||||
|
{imageUrl && uploadIcon}
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomFile;
|
||||||
65
src/Components/CustomFields/PdfUploader.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Button, Image, Upload, UploadFile } from "antd";
|
||||||
|
import useFormField from "../../Hooks/useFormField";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import { ImageBaseURL } from "../../api/config";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FaRegFilePdf } from "react-icons/fa";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const PdfUploader = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
isDisabled,
|
||||||
|
placholder,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, t, isError } = useFormField(name, props);
|
||||||
|
let FormikName = formik.values[name];
|
||||||
|
const imageUrl = formik.values[name] ? ImageBaseURL + FormikName : "";
|
||||||
|
const [Imageurl, setImageurl] = useState(null);
|
||||||
|
|
||||||
|
const FilehandleChange = (value: any) => {
|
||||||
|
// console.log(value.file.originFileObj, "value.file.originFileObj");
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value.file.originFileObj);
|
||||||
|
setImageurl(value.file.originFileObj);
|
||||||
|
};
|
||||||
|
const customRequest = async ({ onSuccess }: any) => {
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
const UploadProps = {
|
||||||
|
name: "file",
|
||||||
|
customRequest: customRequest,
|
||||||
|
onChange: FilehandleChange,
|
||||||
|
headers: {
|
||||||
|
authorization: "authorization-text",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="PdfUploader">
|
||||||
|
<div>ملف الواجب</div>
|
||||||
|
<main>
|
||||||
|
{Imageurl === null ? (
|
||||||
|
<div className="ImagePdfUploader">
|
||||||
|
<Image src={"/Icon/Empty_Image.png"} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="ImagePdfUploaderWrapper">
|
||||||
|
<Image src={URL.createObjectURL(Imageurl)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Upload {...UploadProps}>
|
||||||
|
<Button>
|
||||||
|
{" "}
|
||||||
|
إضافة ورقة <FaRegFilePdf />
|
||||||
|
</Button>
|
||||||
|
</Upload>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PdfUploader;
|
||||||
119
src/Components/CustomFields/Select/CustomSelect.scss
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
.custom-select {
|
||||||
|
position: relative;
|
||||||
|
z-index: 9;
|
||||||
|
color: var(--primary);
|
||||||
|
max-height: 3vw;
|
||||||
|
input {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--primary) !important;
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.custom_select_icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom_select_icon.open {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-list {
|
||||||
|
max-height: 120px;
|
||||||
|
overflow: auto;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
scroll-padding: 10rem;
|
||||||
|
direction: ltr;
|
||||||
|
color: var(--primary);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #999999;
|
||||||
|
border-radius: 5px; /* Adjust border-radius as needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
border-radius: 5px; /* Adjust border-radius as needed */
|
||||||
|
background-color: transparent; /* Set to desired background color */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg2);
|
||||||
|
border: 0.2vw solid var(--primary);
|
||||||
|
border-top: none;
|
||||||
|
padding: 0.1vw 0.3vw;
|
||||||
|
direction: rtl;
|
||||||
|
|
||||||
|
border-bottom-right-radius: 15px !important;
|
||||||
|
border-bottom-left-radius: 15px !important;
|
||||||
|
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
padding: 0.6vw 1vw;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.4vw;
|
||||||
|
padding: 0.3vw 1vw;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--primary);
|
||||||
|
border: 0.2vw solid var(--primary);
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 70px;
|
||||||
|
text-align: center;
|
||||||
|
width: 9vw;
|
||||||
|
height: 2.5vw;
|
||||||
|
transition: 0.5s ease-in-out;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
font-size: 1.6vw;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
.search-input {
|
||||||
|
font-size: 0.8vw;
|
||||||
|
width: 70%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent !important;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select.open .select-header {
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
border-top-right-radius: 30px !important;
|
||||||
|
border-top-left-radius: 30px !important;
|
||||||
|
|
||||||
|
transition: 0.5s ease-in-out;
|
||||||
|
// background-color: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CustomDatePicker {
|
||||||
|
.ant-picker-outlined {
|
||||||
|
max-height: 2.5vw !important;
|
||||||
|
min-height: 2vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/Components/CustomFields/Select/CustomSelect.tsx
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
options: Option[];
|
||||||
|
placeholder: string;
|
||||||
|
onChange?: (option: Option) => void;
|
||||||
|
searchBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomSelect: React.FC<Props> = ({
|
||||||
|
options,
|
||||||
|
placeholder,
|
||||||
|
onChange,
|
||||||
|
searchBy = "name",
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const node = useRef<HTMLDivElement>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOptionClick = (option: Option) => {
|
||||||
|
setSelectedOption(option);
|
||||||
|
// console.log(option);
|
||||||
|
|
||||||
|
navigate(`${location.pathname}?${searchBy}=${option?.value}`);
|
||||||
|
|
||||||
|
setIsOpen(false);
|
||||||
|
setSearchTerm("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredOptions = options.filter((option) =>
|
||||||
|
option.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Add event listener when component mounts
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
// Remove event listener when component unmounts
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
// Close the dropdown if clicked outside the component
|
||||||
|
if (node.current && !node.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={`custom-select ${isOpen ? "open" : ""}`} ref={node}>
|
||||||
|
<div className="select-header" onClick={toggleDropdown}>
|
||||||
|
<IoMdArrowDropdown
|
||||||
|
className={`custom_select_icon ${isOpen ? "open" : ""}`}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={selectedOption ? selectedOption.label : placeholder}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
onFocus={() => setIsOpen(true)}
|
||||||
|
className="search-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{(isOpen || (searchTerm !== "" && filteredOptions.length > 0)) && (
|
||||||
|
<div className="options">
|
||||||
|
<div className="options-list">
|
||||||
|
{filteredOptions.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="option"
|
||||||
|
onClick={() => handleOptionClick(option)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomSelect;
|
||||||
55
src/Components/CustomFields/SelectAndTime.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { DatePicker, Select } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const SelectAndTime = () => {
|
||||||
|
const onChange: any["onChange"] = (date: any, dateString: any) => {
|
||||||
|
// console.log(date, dateString);
|
||||||
|
};
|
||||||
|
const onChangeSearch = (value: string) => {
|
||||||
|
// console.log(`selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSearch = (value: string) => {
|
||||||
|
// console.log('search:', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter `option.label` match the user type `input`
|
||||||
|
const filterOption = (
|
||||||
|
input: string,
|
||||||
|
option?: { label: string; value: string },
|
||||||
|
) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SelectAndTime w-100">
|
||||||
|
<label className="text">تاريخ & مكان الولادة *</label>
|
||||||
|
<div className="TowItemField">
|
||||||
|
<DatePicker size="large" onChange={onChange} picker="month" />
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder="Select a person"
|
||||||
|
optionFilterProp="children"
|
||||||
|
onChange={onChange}
|
||||||
|
onSearch={onChangeSearch}
|
||||||
|
filterOption={filterOption}
|
||||||
|
size="large"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: "jack",
|
||||||
|
label: "Jack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "lucy",
|
||||||
|
label: "Lucy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "tom",
|
||||||
|
label: "Tom",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectAndTime;
|
||||||
17
src/Components/CustomFields/TowFiled.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Input } from "antd";
|
||||||
|
import { Field } from "formik";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const TowFiled = () => {
|
||||||
|
return (
|
||||||
|
<div className="SelectAndTime w-100">
|
||||||
|
<label className="text">الصف & الشعبة *</label>
|
||||||
|
<div className="TowItemField">
|
||||||
|
<Field as={Input} type={"text"} name={"name"} size="large" />
|
||||||
|
<Field as={Input} type={"text"} name={"name"} size="large" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TowFiled;
|
||||||
19
src/Components/DataState/AddedSuccessfully.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const AddedSuccessfully = () => {
|
||||||
|
return (
|
||||||
|
<div className="AddedSuccessfully">
|
||||||
|
<Image src="/DataState/successfully.png" />
|
||||||
|
<h1>تمّت إضافة الطالب بنجاح!</h1>
|
||||||
|
<p>تمّت إضافة الطالب،هل تريد إضافة طالب آخر ؟</p>
|
||||||
|
|
||||||
|
<div className="TowButton">
|
||||||
|
<button>إضافة طالب جديد</button>
|
||||||
|
<button>تخطّي</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddedSuccessfully;
|
||||||
25
src/Components/DataState/EmptyData.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const EmptyData = ({
|
||||||
|
header,
|
||||||
|
info,
|
||||||
|
loading,
|
||||||
|
}: {
|
||||||
|
info: string;
|
||||||
|
header: string;
|
||||||
|
loading: boolean;
|
||||||
|
}) => {
|
||||||
|
if (loading) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="EmptyData">
|
||||||
|
<Image src="/DataState/EmptyData.gif" />
|
||||||
|
<h1>{header}</h1>
|
||||||
|
<p>{info}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmptyData;
|
||||||
12
src/Components/DataState/Loading.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div className="Loading">
|
||||||
|
<Image src="/DataState/loading.gif" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
||||||
59
src/Components/DataTable/FillterNav.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from "react";
|
||||||
|
import SearchField from "./SearchFieldWithSelect";
|
||||||
|
import { FaArrowRight, FaPlus } from "react-icons/fa";
|
||||||
|
import { useModalState } from "../../zustand/Modal";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import CustomSelect from "../CustomFields/Select/CustomSelect";
|
||||||
|
|
||||||
|
const FillterNav = () => {
|
||||||
|
const OptionsData = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: "Jack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: "Lucy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: "Tom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const onChange = (value: any) => {
|
||||||
|
// console.log(`selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { setIsOpen } = useModalState((state) => state);
|
||||||
|
return (
|
||||||
|
<div className="FillterNav">
|
||||||
|
<SearchField
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder={"practical.search_here"}
|
||||||
|
/>
|
||||||
|
<div className="SelectFillters">
|
||||||
|
<button className="add_button">
|
||||||
|
إضافة طالب جديد <FaPlus />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="add_button"
|
||||||
|
onClick={() => setIsOpen(ModalEnum?.FILLTER_NAV_MOVE_STUDENT)}
|
||||||
|
>
|
||||||
|
نقل طالب إلى <FaArrowRight />
|
||||||
|
</button>
|
||||||
|
<CustomSelect
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder="الشعبة"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<CustomSelect
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder="الشعبة"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FillterNav;
|
||||||
58
src/Components/DataTable/FillterNavWithRadio.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { FaArrowRight, FaPlus } from "react-icons/fa";
|
||||||
|
import SearchField from "./SearchFieldWithSelect";
|
||||||
|
import { useButtonState } from "../../zustand/ButtonState";
|
||||||
|
|
||||||
|
const FillterNavWithRadio = () => {
|
||||||
|
const [activeButton, setActiveButton] = useState(0);
|
||||||
|
const { setActiveTab } = useButtonState((state) => state);
|
||||||
|
|
||||||
|
// Function to handle button click
|
||||||
|
const handleButtonClick = (index: number) => {
|
||||||
|
setActiveButton(index);
|
||||||
|
setActiveTab(index);
|
||||||
|
};
|
||||||
|
const OptionsData = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: "Jack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: "Lucy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: "Tom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const buttonLabels = ["أوراق عمل", "نماذج السبر البيتي", "فيديوهات تعليمية"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="FillterNavWithRadio">
|
||||||
|
<header>
|
||||||
|
<FaArrowRight /> نشاطات الطالب <span>( آية العمري ) </span>
|
||||||
|
</header>
|
||||||
|
<span>
|
||||||
|
<SearchField
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder={"practical.search_here"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="ButtonTabs">
|
||||||
|
{buttonLabels.map((label, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={activeButton === index ? "Activebutton" : "button"}
|
||||||
|
onClick={() => handleButtonClick(index)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FillterNavWithRadio;
|
||||||
50
src/Components/DataTable/FillterNavWithSegmented.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaPlus } from "react-icons/fa";
|
||||||
|
import ClassesSegmented from "../Classes/ClassesSegmented";
|
||||||
|
import CustomSelect from "../CustomFields/Select/CustomSelect";
|
||||||
|
|
||||||
|
const FillterNavWithSegmented = () => {
|
||||||
|
const OptionsData = [
|
||||||
|
{
|
||||||
|
value: "jack",
|
||||||
|
label: "Jack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "lucy",
|
||||||
|
label: "Lucy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "tom",
|
||||||
|
label: "Tom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const onChange = (value: any) => {
|
||||||
|
// console.log(`selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="FillterNav">
|
||||||
|
<ClassesSegmented />
|
||||||
|
|
||||||
|
<div className="SelectFillters">
|
||||||
|
<button className="add_button">
|
||||||
|
إضافة طالب جديد
|
||||||
|
<FaPlus />
|
||||||
|
</button>
|
||||||
|
<CustomSelect
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder="الشعبة"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<CustomSelect
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder="الشعبة"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FillterNavWithSegmented;
|
||||||
79
src/Components/DataTable/SearchField.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { IoSearch } from "react-icons/io5";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
placeholder: string;
|
||||||
|
searchBy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchField: React.FC<Props> = ({ placeholder, searchBy }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const DEBOUNCE_DELAY = 500; // Adjust the debounce delay as needed
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
setSearchQuery(searchParams.get(searchBy) || "");
|
||||||
|
}, [location.search, searchBy]);
|
||||||
|
|
||||||
|
const handleInputChange = (value: string) => {
|
||||||
|
setSearchQuery(value);
|
||||||
|
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
if (value.trim() !== "") {
|
||||||
|
navigate(`${location.pathname}?${searchBy}=${value.trim()}`);
|
||||||
|
} else {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
params.delete(searchBy);
|
||||||
|
navigate(`${location.pathname}`);
|
||||||
|
}
|
||||||
|
}, DEBOUNCE_DELAY);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleDropdown = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (inputRef.current && !inputRef.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className={`search-field ${isOpen ? "open" : ""}`}>
|
||||||
|
<div className="search-header" onClick={handleToggleDropdown}>
|
||||||
|
<IoSearch className="search__icon" />
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
className="search__input"
|
||||||
|
placeholder={t(placeholder)}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => handleInputChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchField;
|
||||||
91
src/Components/DataTable/SearchFieldWithSelect.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { IoSearch } from "react-icons/io5";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Option {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
options: Option[];
|
||||||
|
placeholder: string;
|
||||||
|
onSelect?: (option: Option) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchFieldWithSelect: React.FC<Props> = ({
|
||||||
|
options,
|
||||||
|
placeholder,
|
||||||
|
onSelect,
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||||
|
const node = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Add event listener when component mounts
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
// Remove event listener when component unmounts
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
// Close the dropdown if clicked outside the component
|
||||||
|
if (node.current && !node.current.contains(event.target as Node)) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
};
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleOptionClick = (option: Option) => {
|
||||||
|
setSelectedOption(option);
|
||||||
|
setIsOpen(false);
|
||||||
|
onSelect && onSelect(option);
|
||||||
|
|
||||||
|
navigate(option?.value as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredOptions = options.filter((option) =>
|
||||||
|
option.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={node} className={`search-field ${isOpen ? "open" : ""}`}>
|
||||||
|
<div className="search-header" onClick={toggleDropdown}>
|
||||||
|
<IoSearch className="search__icon" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="search__input"
|
||||||
|
placeholder={selectedOption ? selectedOption.label : placeholder}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
{/* <IoMdArrowDropdown className={`search_select_icon ${isOpen ? 'open' : ''}`} /> */}
|
||||||
|
</div>
|
||||||
|
{(isOpen || (searchTerm !== "" && filteredOptions.length > 0)) && (
|
||||||
|
<div className="search-options">
|
||||||
|
<div className="search-options-list">
|
||||||
|
{filteredOptions.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className="search-option"
|
||||||
|
onClick={() => handleOptionClick(option)}
|
||||||
|
>
|
||||||
|
<div>{option.label}</div>
|
||||||
|
<IoSearch className="search__icon" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchFieldWithSelect;
|
||||||
37
src/Components/DataTable/SmallFillterNav.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
import SearchField from "./SearchFieldWithSelect";
|
||||||
|
import { IoSearch } from "react-icons/io5";
|
||||||
|
import { Select } from "antd";
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
import { FaArrowRight, FaPlus } from "react-icons/fa";
|
||||||
|
import { useModalState } from "../../zustand/Modal";
|
||||||
|
|
||||||
|
const SmallFillterNav = () => {
|
||||||
|
const OptionsData = [
|
||||||
|
{
|
||||||
|
value: 1,
|
||||||
|
label: "Jack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 2,
|
||||||
|
label: "Lucy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 3,
|
||||||
|
label: "Tom",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="SmallFillterNav">
|
||||||
|
<header>
|
||||||
|
<FaArrowRight /> نشاطات الطالب <span>( آية العمري ) </span>
|
||||||
|
</header>
|
||||||
|
<SearchField
|
||||||
|
options={OptionsData}
|
||||||
|
placeholder={"practical.search_here"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SmallFillterNav;
|
||||||
67
src/Components/Division/DivisionsHeaderSection.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useModalState } from "../../zustand/Modal";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import { FaMoneyBill } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown, IoMdArrowDropup } from "react-icons/io";
|
||||||
|
|
||||||
|
const DivisionsHeaderSection = () => {
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const toggleShowAll = () => {
|
||||||
|
setShowAll(!showAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isOpen, setIsOpen } = useModalState((state) => state);
|
||||||
|
|
||||||
|
const handelOpenModal = () => {
|
||||||
|
setIsOpen(ModalEnum?.DIVISION_ADD);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define the interface for your data
|
||||||
|
interface Item {
|
||||||
|
icon: JSX.Element;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
const data: Item[] = [
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Divisions_header_section">
|
||||||
|
<div className="CountCards">
|
||||||
|
{data?.map((item: Item, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`CountCard ${!showAll && index >= 5 ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
<i>{item?.icon}</i>
|
||||||
|
<h4>{item?.name}</h4>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{showAll ? (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropup />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropdown />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button className="Add_button" onClick={handelOpenModal}>
|
||||||
|
إضافة مادة
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DivisionsHeaderSection;
|
||||||
128
src/Components/EduClass/EduClassHeaderSection.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import { FaPlus, FaUser } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown, IoMdArrowDropup } from "react-icons/io";
|
||||||
|
import { FaEllipsis } from "react-icons/fa6";
|
||||||
|
import { Popover, Spin } from "antd";
|
||||||
|
import { useGetAllEduClass } from "../../api/eduClass";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { EduClassItem } from "../../types/Item";
|
||||||
|
import useModalHandler from "../../utils/useModalHandler";
|
||||||
|
import { usePage_titleState } from "../../zustand/PageTitleState";
|
||||||
|
import { hasAbility } from "../../utils/hasAbility";
|
||||||
|
import { ABILITIES_ENUM, ABILITIES_VALUES_ENUM } from "../../enums/abilities";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const HeaderSection = () => {
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const toggleShowAll = () => {
|
||||||
|
setShowAll(!showAll);
|
||||||
|
};
|
||||||
|
const { course_id } = useParams();
|
||||||
|
const { data, isLoading } = useGetAllEduClass({ course_id: course_id });
|
||||||
|
const eduClass = data?.data || [];
|
||||||
|
const { set_title } = usePage_titleState((state) => state);
|
||||||
|
const { handel_open_model } = useModalHandler();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const show_eduClass_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.EDUCATION_CLASS,
|
||||||
|
ABILITIES_VALUES_ENUM?.SHOW,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handelNavigate = (item: EduClassItem) => {
|
||||||
|
if (show_eduClass_ability) {
|
||||||
|
navigate(`${item?.id}`);
|
||||||
|
set_title(`${item?.name}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const store_eduClass_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.EDUCATION_CLASS,
|
||||||
|
ABILITIES_VALUES_ENUM?.STORE,
|
||||||
|
);
|
||||||
|
const delete_course_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.COURSE,
|
||||||
|
ABILITIES_VALUES_ENUM?.DELETE,
|
||||||
|
);
|
||||||
|
const store_note_ability = hasAbility(
|
||||||
|
ABILITIES_ENUM?.NOTE,
|
||||||
|
ABILITIES_VALUES_ENUM?.STORE,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PopoverContent = (
|
||||||
|
<div className="PopoverContent">
|
||||||
|
{store_eduClass_ability && (
|
||||||
|
<p onClick={() => handel_open_model(ModalEnum?.EDUClASS_ADD)}>
|
||||||
|
{" "}
|
||||||
|
{t("practical.add")} {t("models.education_class")} <FaPlus />{" "}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{store_note_ability && (
|
||||||
|
<p
|
||||||
|
onClick={() => handel_open_model(ModalEnum?.COURSE_SENDNOTIFICATION)}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
{t("practical.Send_Direct_Notification")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{delete_course_ability && (
|
||||||
|
<p onClick={() => handel_open_model(ModalEnum?.COURSE_DELETE)}>
|
||||||
|
{" "}
|
||||||
|
{t("practical.delete")} {t("models.course")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="header_cards_section">
|
||||||
|
<div className="CountCards">
|
||||||
|
{eduClass?.map((item: EduClassItem, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={() => handelNavigate(item)}
|
||||||
|
key={index}
|
||||||
|
className={`CountCard ${!showAll && index >= 6 ? "hidden" : ""}`}
|
||||||
|
>
|
||||||
|
<i>
|
||||||
|
<FaUser />
|
||||||
|
</i>
|
||||||
|
<h4>{item?.name}</h4>
|
||||||
|
<h5>
|
||||||
|
{item?.students_count} {t("models.student")}{" "}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{eduClass?.length > 4 &&
|
||||||
|
(showAll ? (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropup />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropdown />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<Popover
|
||||||
|
content={PopoverContent}
|
||||||
|
overlayClassName="Popover"
|
||||||
|
arrow={false}
|
||||||
|
placement="bottomRight"
|
||||||
|
>
|
||||||
|
<FaEllipsis />
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderSection;
|
||||||
103
src/Components/EduClass/SingleEduClassHeaderSection.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import { FaUser } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown, IoMdArrowDropup } from "react-icons/io";
|
||||||
|
import { FaEllipsis } from "react-icons/fa6";
|
||||||
|
import { Popover } from "antd";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import { Item } from "../../types/Item";
|
||||||
|
import useModalHandler from "../../utils/useModalHandler";
|
||||||
|
import { useButtonState } from "../../zustand/ButtonState";
|
||||||
|
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ABILITIES_VALUES_ENUM } from "../../enums/abilities";
|
||||||
|
// import useSetPage_title from "../../Hooks/useSetPageTitle";
|
||||||
|
import { EduClassComponents } from "../../Pages/EduClass/show/ActiveTable";
|
||||||
|
import { hasAbility } from "../../utils/hasAbility";
|
||||||
|
|
||||||
|
const HeaderSection = () => {
|
||||||
|
const [showAll, setShowAll] = useState<boolean>(true);
|
||||||
|
|
||||||
|
const toggleShowAll = () => {
|
||||||
|
setShowAll(!showAll);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
// useSetPage_title(
|
||||||
|
// `${t(ABILITIES_ENUM?.MAIN_PAGE)} / ${t(`models.${ABILITIES_ENUM.COURSE}`)} / ${t(`models.${ABILITIES_ENUM.EDUCATION_CLASS}`)} / ${t(`input.details`)}`,
|
||||||
|
// );
|
||||||
|
|
||||||
|
const { handel_open_model } = useModalHandler();
|
||||||
|
|
||||||
|
const filteredComponents = EduClassComponents?.filter((component) =>
|
||||||
|
hasAbility(component.abilities, ABILITIES_VALUES_ENUM.INDEX),
|
||||||
|
);
|
||||||
|
|
||||||
|
const PopoverContent = (
|
||||||
|
<div className="PopoverContent">
|
||||||
|
<p
|
||||||
|
onClick={() => handel_open_model(ModalEnum?.EDUClASS_SENDNOTIFICATION)}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
{t("practical.Send_Direct_Notification")}
|
||||||
|
</p>
|
||||||
|
<p onClick={() => handel_open_model(ModalEnum?.EDUClASS_DELETE)}>
|
||||||
|
{" "}
|
||||||
|
{t("practical.delete")} {t("models.education_class")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const [, setActiveButton] = useState(0);
|
||||||
|
const { setActiveTab, ActiveTab } = useButtonState((state) => state);
|
||||||
|
const { set_param_to_send } = useObjectToEdit();
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleButtonClick = (index: number) => {
|
||||||
|
setActiveButton(index);
|
||||||
|
setActiveTab(index);
|
||||||
|
set_param_to_send({});
|
||||||
|
navigate(`${location.pathname}`);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="header_cards_section">
|
||||||
|
<div className="CountCards">
|
||||||
|
{filteredComponents?.map((item: Item, index: number) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleButtonClick(index)}
|
||||||
|
className={`CountCard ${!showAll && index >= 6 ? "hidden" : ""} ${ActiveTab === index ? "active" : ""} `}
|
||||||
|
>
|
||||||
|
<i>
|
||||||
|
<FaUser />
|
||||||
|
</i>
|
||||||
|
<h4> {t(`education_class_actions.${item?.name}`)} </h4>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{filteredComponents?.length > 4 &&
|
||||||
|
(showAll ? (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropup />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="Show_More_Button" onClick={toggleShowAll}>
|
||||||
|
<IoMdArrowDropdown />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<Popover
|
||||||
|
content={PopoverContent}
|
||||||
|
overlayClassName="Popover"
|
||||||
|
arrow={false}
|
||||||
|
placement="bottomRight"
|
||||||
|
>
|
||||||
|
<FaEllipsis />
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderSection;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Col, Row } from "reactstrap";
|
||||||
|
import React from "react";
|
||||||
|
import ValidationField from "../../../../ValidationField/ValidationField";
|
||||||
|
|
||||||
|
const AttachmentsDetails = () => {
|
||||||
|
return (
|
||||||
|
<Row xs={1} sm={1} md={1} lg={2} xl={2}>
|
||||||
|
<Col xs="6" sm="6">
|
||||||
|
<ValidationField
|
||||||
|
name="attachment1"
|
||||||
|
type="DropFile"
|
||||||
|
label="school_document"
|
||||||
|
/>
|
||||||
|
{/* <ValidationField name='attachment2' type='DropFile' label='الوثيقة'/> */}
|
||||||
|
</Col>
|
||||||
|
<Col xs="6" sm="6"></Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AttachmentsDetails;
|
||||||
36
src/Components/EduClass/student/Add/Form/ContactDetails.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Col, Row } from "reactstrap";
|
||||||
|
import React from "react";
|
||||||
|
import ValidationField from "../../../../ValidationField/ValidationField";
|
||||||
|
|
||||||
|
const ContactDetails = () => {
|
||||||
|
return (
|
||||||
|
<div className="bigRow">
|
||||||
|
<ValidationField
|
||||||
|
type="number"
|
||||||
|
name="phone_number_1"
|
||||||
|
label="phone_number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValidationField
|
||||||
|
type="number"
|
||||||
|
name="phone_number_2"
|
||||||
|
label="additional_phone_number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ValidationField
|
||||||
|
type="TextArea"
|
||||||
|
name="address"
|
||||||
|
label="address"
|
||||||
|
placeholder="address"
|
||||||
|
/>
|
||||||
|
<ValidationField
|
||||||
|
type="TextArea"
|
||||||
|
name="notes"
|
||||||
|
label="note"
|
||||||
|
placeholder="note"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContactDetails;
|
||||||
34
src/Components/EduClass/student/Add/Form/ParentDetails.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Col, Row } from "reactstrap";
|
||||||
|
import React, { useRef } from "react";
|
||||||
|
import ValidationField from "../../../../ValidationField/ValidationField";
|
||||||
|
|
||||||
|
const ParentDetails = () => {
|
||||||
|
const handelClick = () => {};
|
||||||
|
return (
|
||||||
|
<div className="bigRow">
|
||||||
|
<ValidationField name="father_name" label="father_name" />
|
||||||
|
|
||||||
|
<ValidationField name="father_job" label="father_job" />
|
||||||
|
|
||||||
|
<ValidationField
|
||||||
|
type="number"
|
||||||
|
name="father_phone_number"
|
||||||
|
label="father_phone_number"
|
||||||
|
/>
|
||||||
|
<ValidationField name="mother_name" label="mother_name" />
|
||||||
|
|
||||||
|
<ValidationField name="mother_job" label="mother_job" />
|
||||||
|
|
||||||
|
<ValidationField
|
||||||
|
type="number"
|
||||||
|
name="mother_phone_number"
|
||||||
|
label="mother_phone_number"
|
||||||
|
/>
|
||||||
|
{/* <Col xs="8" sm="8">
|
||||||
|
|
||||||
|
</Col> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ParentDetails;
|
||||||
72
src/Components/EduClass/student/Add/Form/PersonalDetails.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Col, Row } from "reactstrap";
|
||||||
|
import React from "react";
|
||||||
|
import ValidationField from "../../../../ValidationField/ValidationField";
|
||||||
|
import {
|
||||||
|
Sex_Select_options_Student,
|
||||||
|
nationalities,
|
||||||
|
religion_Select_options,
|
||||||
|
} from "../../../../../types/App";
|
||||||
|
|
||||||
|
const PersonalDetails = () => {
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col xs="8" sm="8">
|
||||||
|
<ValidationField name="email" label="email" />
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField name="first_name" label="first_name" />
|
||||||
|
<ValidationField name="last_name" label="last_name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField name="username" label="username" />
|
||||||
|
|
||||||
|
<ValidationField name="password" label="password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField
|
||||||
|
name="nationality"
|
||||||
|
type="LocalSearch"
|
||||||
|
option={nationalities}
|
||||||
|
label="nationality"
|
||||||
|
placeholder="nationality"
|
||||||
|
/>
|
||||||
|
<ValidationField
|
||||||
|
name="religion"
|
||||||
|
type="Select"
|
||||||
|
option={religion_Select_options}
|
||||||
|
no_label
|
||||||
|
placeholder="religion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField
|
||||||
|
name="birthday"
|
||||||
|
type="Date"
|
||||||
|
label="birthday"
|
||||||
|
placeholder="birthday"
|
||||||
|
/>
|
||||||
|
<ValidationField
|
||||||
|
name="birth_place"
|
||||||
|
no_label
|
||||||
|
placeholder="birth_place"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ValidationField
|
||||||
|
name="sex"
|
||||||
|
label="sex"
|
||||||
|
type="Select"
|
||||||
|
option={Sex_Select_options_Student}
|
||||||
|
placeholder="sex"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs="4" sm="4">
|
||||||
|
<ValidationField name="image" type="DropFile" label="image" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PersonalDetails;
|
||||||
46
src/Components/EduClass/student/Add/Form/Successful.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from "react";
|
||||||
|
import Image from "../../../../Ui/Image";
|
||||||
|
import { useModalTabsState } from "../../../../../zustand/ModalTabsState";
|
||||||
|
import { useModalState } from "../../../../../zustand/Modal";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const Successful = () => {
|
||||||
|
const { setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
const { setIsOpen } = useModalState((state) => state);
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
const [t] = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="AddedSuccessfully">
|
||||||
|
<Image src="/DataState/successfully.png" />
|
||||||
|
<h1> {t("header.Student_added_successfully")} </h1>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
"header.the_student_has_been_added_Do_you_want_to_add_another_student",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="TowButton">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab(0);
|
||||||
|
formik?.resetForm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("header.Add_a_new_student")}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen("");
|
||||||
|
setActiveTab(0);
|
||||||
|
formik?.resetForm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("practical.skip")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Successful;
|
||||||
80
src/Components/EduClass/student/Add/Model/Modal.tsx
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { Modal } from "antd";
|
||||||
|
import { useModalState } from "../../../../../zustand/Modal";
|
||||||
|
import FormikForm from "../../../../../Layout/Dashboard/FormikForm";
|
||||||
|
import ModelBody from "./ModelBody";
|
||||||
|
import TabsSubmite from "./TabsSubmite";
|
||||||
|
import { getInitialValues, getValidationSchema } from "./formUtil";
|
||||||
|
import { ModalEnum } from "../../../../../enums/Model";
|
||||||
|
import { useAddStudent } from "../../../../../api/student";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useModalTabsState } from "../../../../../zustand/ModalTabsState";
|
||||||
|
|
||||||
|
const ModalForm: React.FC = () => {
|
||||||
|
const { isOpen, setIsOpen } = useModalState((state) => state);
|
||||||
|
const { mutate, isSuccess, isLoading } = useAddStudent();
|
||||||
|
const { edu_class_id } = useParams();
|
||||||
|
const handleSubmit = (values: any) => {
|
||||||
|
const Data_To_Send = { ...values };
|
||||||
|
delete Data_To_Send["id"];
|
||||||
|
|
||||||
|
Data_To_Send.attachments = [];
|
||||||
|
if (Data_To_Send.attachment1 !== null) {
|
||||||
|
Data_To_Send.attachments.push({
|
||||||
|
name: "attachment1",
|
||||||
|
file: Data_To_Send.attachment1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Data_To_Send.attachment2 !== null) {
|
||||||
|
Data_To_Send.attachments.push({
|
||||||
|
name: "attachment2",
|
||||||
|
file: Data_To_Send.attachment2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Data_To_Send["edu_class_id"] = edu_class_id;
|
||||||
|
Data_To_Send["birthday"] = values.birthday?.format(
|
||||||
|
"YYYY-MM-DD HH:mm:ss.SSS",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!values?.attachment1) {
|
||||||
|
delete Data_To_Send["attachments"];
|
||||||
|
}
|
||||||
|
// console.log(Data_To_Send);
|
||||||
|
mutate(Data_To_Send);
|
||||||
|
};
|
||||||
|
const { setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess) {
|
||||||
|
setActiveTab(4);
|
||||||
|
getInitialValues([]);
|
||||||
|
}
|
||||||
|
}, [isSuccess, setActiveTab]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
className="AddModalForm"
|
||||||
|
centered
|
||||||
|
width={"80vw"}
|
||||||
|
footer={null}
|
||||||
|
open={isOpen === ModalEnum.STUDENT_ADD}
|
||||||
|
onOk={() => setIsOpen("")}
|
||||||
|
onCancel={() => setIsOpen("")}
|
||||||
|
>
|
||||||
|
<FormikForm
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
initialValues={getInitialValues([])}
|
||||||
|
validationSchema={getValidationSchema}
|
||||||
|
>
|
||||||
|
<ModelBody />
|
||||||
|
<TabsSubmite isLoading={isLoading} steps={4} />
|
||||||
|
</FormikForm>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalForm;
|
||||||
67
src/Components/EduClass/student/Add/Model/ModelBody.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import PersonalDetails from "../Form/PersonalDetails";
|
||||||
|
import TabsBar from "../../../../Layout/Tabs/TabsBar";
|
||||||
|
import ActiveTabs from "../../../../Layout/Tabs/ActiveTabs";
|
||||||
|
import { usePage_titleState } from "../../../../../zustand/PageTitleState";
|
||||||
|
import ParentDetails from "../Form/ParentDetails";
|
||||||
|
import ContactDetails from "../Form/ContactDetails";
|
||||||
|
import AttachmentsDetails from "../Form/AttachmentsDetails";
|
||||||
|
import Successful from "../Form/Successful";
|
||||||
|
import { useModalState } from "../../../../../zustand/Modal";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const ModelBody = () => {
|
||||||
|
const { title } = usePage_titleState((state) => state);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: `${t(`practical.Step`)} 1`,
|
||||||
|
description: t("header.Student_Information"),
|
||||||
|
component: <PersonalDetails />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: `${t(`practical.Step`)} 2`,
|
||||||
|
description: t("header.Parent_Information"),
|
||||||
|
component: <ParentDetails />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: `${t(`practical.Step`)} 3`,
|
||||||
|
description: t("header.Contact_Information"),
|
||||||
|
component: <ContactDetails />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: `${t(`practical.Step`)} 4`,
|
||||||
|
description: t("header.Attachment_Images"),
|
||||||
|
component: <AttachmentsDetails />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "hidden",
|
||||||
|
description: "hidden",
|
||||||
|
hidden: true,
|
||||||
|
component: <Successful />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const { isOpen } = useModalState((state) => state);
|
||||||
|
const formik = useFormikContext();
|
||||||
|
useEffect(() => {
|
||||||
|
formik.setErrors({});
|
||||||
|
}, [isOpen === ""]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ModelBody">
|
||||||
|
<TabsBar steps={steps} />
|
||||||
|
<div className="ModelBodyForm">
|
||||||
|
<header>
|
||||||
|
{" "}
|
||||||
|
{t("practical.add")} {t("models.student")}{" "}
|
||||||
|
{" " + title === null ? "" : title}{" "}
|
||||||
|
</header>
|
||||||
|
<ActiveTabs steps={steps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelBody;
|
||||||
128
src/Components/EduClass/student/Add/Model/TabsSubmite.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useModalTabsState } from "../../../../../zustand/ModalTabsState";
|
||||||
|
import { useModalState } from "../../../../../zustand/Modal";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface TabsSubmiteProps {
|
||||||
|
steps: number;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsSubmite: React.FC<TabsSubmiteProps> = ({ steps, isLoading }) => {
|
||||||
|
const { values, isValid, submitForm } = useFormikContext<any>();
|
||||||
|
|
||||||
|
const { ActiveTab, setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
const IsSubmite = ActiveTab + 1 !== steps;
|
||||||
|
const {
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
nationality,
|
||||||
|
religion,
|
||||||
|
birthday,
|
||||||
|
birth_place,
|
||||||
|
sex,
|
||||||
|
} = values;
|
||||||
|
|
||||||
|
const {
|
||||||
|
father_name,
|
||||||
|
father_job,
|
||||||
|
father_phone_number,
|
||||||
|
mother_name,
|
||||||
|
mother_job,
|
||||||
|
mother_phone_number,
|
||||||
|
} = values;
|
||||||
|
|
||||||
|
const { address, attachment1 } = values;
|
||||||
|
|
||||||
|
const is_1_valued =
|
||||||
|
first_name &&
|
||||||
|
username &&
|
||||||
|
last_name &&
|
||||||
|
password &&
|
||||||
|
nationality &&
|
||||||
|
religion &&
|
||||||
|
birthday &&
|
||||||
|
birth_place &&
|
||||||
|
sex;
|
||||||
|
const is_2_valued =
|
||||||
|
father_name &&
|
||||||
|
father_job &&
|
||||||
|
father_phone_number &&
|
||||||
|
mother_name &&
|
||||||
|
mother_job &&
|
||||||
|
mother_phone_number;
|
||||||
|
const is_3_valued = address;
|
||||||
|
const is_4_valued = attachment1;
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
function handelNext() {
|
||||||
|
// console.log("submited");
|
||||||
|
// console.log(isValid,"isValid");
|
||||||
|
|
||||||
|
if (!is_1_valued && Number(ActiveTab) === 0) {
|
||||||
|
toast.error(t("validation.pleas_fill_all_label"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!is_2_valued && Number(ActiveTab) === 1) {
|
||||||
|
toast.error(t("validation.pleas_fill_all_label"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!is_3_valued && Number(ActiveTab) === 2) {
|
||||||
|
toast.error(t("validation.pleas_fill_all_label"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!is_4_valued && Number(ActiveTab) === 3) {
|
||||||
|
toast.error(t("validation.pleas_fill_all_label"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number(ActiveTab) >= steps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setActiveTab(Number(ActiveTab) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handelPre() {
|
||||||
|
setActiveTab(Number(ActiveTab) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (isValid) {
|
||||||
|
submitForm();
|
||||||
|
} else {
|
||||||
|
submitForm();
|
||||||
|
// console.log('Validation errors:', formik.errors);
|
||||||
|
// setActiveTab(4)
|
||||||
|
toast.error(t("validation.pleas_fill_all_label"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// console.log(ActiveTab,"ActiveTab");
|
||||||
|
|
||||||
|
if (ActiveTab === steps) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="SubmitButton">
|
||||||
|
<div onClick={handelPre}>
|
||||||
|
{ActiveTab > 0 ? "رجوع للخطوة السابقة" : ""}
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
onClick={isLoading ? undefined : IsSubmite ? handelNext : handleSubmit}
|
||||||
|
{...(!IsSubmite && !isLoading ? { type: "submit" } : {})}
|
||||||
|
>
|
||||||
|
{isLoading
|
||||||
|
? "جار الاضافة ...."
|
||||||
|
: IsSubmite
|
||||||
|
? "الخطوة التالية"
|
||||||
|
: "إضافة الطالب"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabsSubmite;
|
||||||
76
src/Components/EduClass/student/Add/Model/formUtil.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export const getInitialValues = (objectToEdit: any): any => {
|
||||||
|
return {
|
||||||
|
id: objectToEdit?.id ?? null,
|
||||||
|
// username:objectToEdit?.username ?? null ,
|
||||||
|
email: objectToEdit?.email ?? "",
|
||||||
|
username: objectToEdit?.username ?? null,
|
||||||
|
|
||||||
|
password: objectToEdit?.password ?? null,
|
||||||
|
notes: objectToEdit?.notes ?? null,
|
||||||
|
image: objectToEdit?.image ?? null,
|
||||||
|
nationality: objectToEdit?.nationality ?? null,
|
||||||
|
birth_place: objectToEdit?.birth_place ?? null,
|
||||||
|
birthday: objectToEdit?.birthday ?? null,
|
||||||
|
phone_number_2: objectToEdit?.phone_number_2 ?? null,
|
||||||
|
phone_number_1: objectToEdit?.phone_number_1 ?? null,
|
||||||
|
mother_name: objectToEdit?.mother_name ?? null,
|
||||||
|
father_name: objectToEdit?.father_name ?? null,
|
||||||
|
last_name: objectToEdit?.last_name ?? null,
|
||||||
|
first_name: objectToEdit?.first_name ?? null,
|
||||||
|
religion: objectToEdit?.religion ?? null,
|
||||||
|
attachments: objectToEdit?.attachments ?? null,
|
||||||
|
attachment1: objectToEdit?.attachment1 ?? null,
|
||||||
|
attachment2: objectToEdit?.attachment2 ?? null,
|
||||||
|
address: objectToEdit?.address ?? null,
|
||||||
|
|
||||||
|
father_job: objectToEdit?.father_job ?? null,
|
||||||
|
mother_phone_number: objectToEdit?.mother_phone_number ?? null,
|
||||||
|
father_phone_number: objectToEdit?.father_phone_number ?? null,
|
||||||
|
mother_job: objectToEdit?.mother_job ?? null,
|
||||||
|
edu_class_id: objectToEdit?.edu_class_id ?? null,
|
||||||
|
sex: objectToEdit?.sex ?? null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidationSchema = () => {
|
||||||
|
return Yup.object().shape({
|
||||||
|
// username: Yup.string().required('Username is required'),
|
||||||
|
email: Yup.string().email("validation.Invalid_email"),
|
||||||
|
username: Yup.string().required("validation.required"),
|
||||||
|
// .required("validation.Email_is_required"),
|
||||||
|
password: Yup.string()
|
||||||
|
.required("validation.Password_is_required")
|
||||||
|
.min(8, "validation.Password_must_be_at_least_8_characters_long"),
|
||||||
|
// notes: Yup.string(),
|
||||||
|
// image: Yup.string().required(),
|
||||||
|
nationality: Yup.string().required("validation.Nationality_is_required"),
|
||||||
|
address: Yup.string().required("validation.Address_is_required"),
|
||||||
|
|
||||||
|
birth_place: Yup.string().required("validation.Place_of_birth_is_required"),
|
||||||
|
birthday: Yup.string().required("validation.Date_of_birth_is_required"),
|
||||||
|
// phone_number_2: Yup.number(),
|
||||||
|
// phone_number_1: Yup.number(),
|
||||||
|
mother_name: Yup.string().required("validation.Mother's_name_is_required"),
|
||||||
|
father_name: Yup.string().required("validation.Father's_name_is_required"),
|
||||||
|
last_name: Yup.string().required("validation.Last_name_is_required"),
|
||||||
|
first_name: Yup.string().required("validation.First_name_is_required"),
|
||||||
|
religion: Yup.string().required("validation.Religion_is_required"),
|
||||||
|
sex: Yup.string().required("validation.Gender_is_required"),
|
||||||
|
|
||||||
|
// attachments: Yup.string(),
|
||||||
|
// attachment1: Yup.string(),
|
||||||
|
// attachment2: Yup.string(),
|
||||||
|
// attachment1: Yup.string().required("validation.Attachment1_is_required"),
|
||||||
|
|
||||||
|
father_job: Yup.string().required("validation.Father's_job_is_required"),
|
||||||
|
// mother_phone_number: Yup.string().required(
|
||||||
|
// "validation.Mother's_phone_number_is_required",
|
||||||
|
// ),
|
||||||
|
father_phone_number: Yup.string().required(
|
||||||
|
"validation.Father's_phone_number_is_required",
|
||||||
|
),
|
||||||
|
// mother_job: Yup.string().required("validation.Mother's_job_is_required"),
|
||||||
|
});
|
||||||
|
};
|
||||||
48
src/Components/HOC/WithDrawer.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { useState, ReactNode } from "react";
|
||||||
|
import type { DrawerProps } from "antd";
|
||||||
|
import { Drawer, Space } from "antd";
|
||||||
|
|
||||||
|
interface WithDrawerProps {
|
||||||
|
button: React.ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
title: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithDrawer: React.FC<WithDrawerProps> = ({
|
||||||
|
button,
|
||||||
|
children,
|
||||||
|
title = "Basic Drawer",
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [placement, setPlacement] = useState<DrawerProps["placement"]>("right");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Space>
|
||||||
|
{React.cloneElement(button as React.ReactElement, {
|
||||||
|
onClick: () => setOpen(true),
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
<Drawer
|
||||||
|
title={title}
|
||||||
|
placement={placement}
|
||||||
|
closable={false}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
open={open}
|
||||||
|
key={placement}
|
||||||
|
>
|
||||||
|
<div className={className}>{children}</div>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithDrawer;
|
||||||
|
|
||||||
|
// <WithDrawer
|
||||||
|
// button={<Button type="primary">Open</Button>}
|
||||||
|
// >
|
||||||
|
// {/* Your content goes here */}
|
||||||
|
// </WithDrawer>
|
||||||
28
src/Components/HOC/WithFormik.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { Form, Formik } from "formik";
|
||||||
|
import React from "react";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
const WithFormik = ({ children }: any) => {
|
||||||
|
const getInitialValues = () => {
|
||||||
|
return { name: "" };
|
||||||
|
};
|
||||||
|
const getValidationSchema = () => {
|
||||||
|
return Yup.object().shape({});
|
||||||
|
};
|
||||||
|
const handleSubmit = () => {};
|
||||||
|
return (
|
||||||
|
<div className="WithFormik">
|
||||||
|
{
|
||||||
|
<Formik
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
initialValues={getInitialValues}
|
||||||
|
validationSchema={getValidationSchema}
|
||||||
|
>
|
||||||
|
{(formik) => <Form>{children}</Form>}
|
||||||
|
</Formik>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithFormik;
|
||||||
32
src/Components/HOC/withVisibale.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
// This is your Layout Component
|
||||||
|
const Visibale = ({ children, ...props }: any) => {
|
||||||
|
const ref = useRef<any>();
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(([entry]) => {
|
||||||
|
if (isVisible === false) {
|
||||||
|
setIsVisible(entry.isIntersecting);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (ref.current) {
|
||||||
|
observer.observe(ref.current);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (ref.current) {
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
observer.unobserve(ref.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isVisible]); // Empty array ensures effect is only run on mount and unmount
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} {...props}>
|
||||||
|
{isVisible && children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Visibale;
|
||||||
59
src/Components/Home/ActivitySection.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from "react";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const ActivitySection = () => {
|
||||||
|
// Static fake data array
|
||||||
|
const ActivitysData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
image: "/Image/1.png",
|
||||||
|
info: "3 طالبات حصلوا على جائزة في مسابقة الشطرنج",
|
||||||
|
time: "منذ 3 ساعات",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
image: "/Image/1.png",
|
||||||
|
info: "3 طالبات حصلوا على جائزة في مسابقة الشطرنج",
|
||||||
|
time: "منذ 3 ساعات",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
image: "/Image/1.png",
|
||||||
|
info: "3 طالبات حصلوا على جائزة في مسابقة الشطرنج",
|
||||||
|
time: "منذ 3 ساعات",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
image: "/Image/1.png",
|
||||||
|
info: "3 طالبات حصلوا على جائزة في مسابقة الشطرنج",
|
||||||
|
time: "منذ 3 ساعات",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
image: "/Image/1.png",
|
||||||
|
info: "3 طالبات حصلوا على جائزة في مسابقة الشطرنج",
|
||||||
|
time: "منذ 3 ساعات",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ActivitySection">
|
||||||
|
<header>
|
||||||
|
<h4>النشاط الطلابي </h4>
|
||||||
|
</header>
|
||||||
|
<div className="ActivityScrollerChanger">
|
||||||
|
<div className="Activitys">
|
||||||
|
{ActivitysData.map((Activity) => (
|
||||||
|
<article key={Activity.id}>
|
||||||
|
<Image src={Activity.image} />
|
||||||
|
<h5>{Activity.info}</h5>
|
||||||
|
<p>{Activity?.time}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActivitySection;
|
||||||
123
src/Components/Home/CalendarSection.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import "dayjs/locale/zh-cn";
|
||||||
|
import type { Dayjs } from "dayjs";
|
||||||
|
import dayLocaleData from "dayjs/plugin/localeData";
|
||||||
|
import {
|
||||||
|
Calendar,
|
||||||
|
Col,
|
||||||
|
DatePicker,
|
||||||
|
Popover,
|
||||||
|
Radio,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Typography,
|
||||||
|
theme,
|
||||||
|
} from "antd";
|
||||||
|
import type { CalendarProps } from "antd";
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { RiDeleteBin6Fill } from "react-icons/ri";
|
||||||
|
|
||||||
|
dayjs.extend(dayLocaleData);
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [year, setYear] = useState<number>(dayjs().year());
|
||||||
|
const [month, setMonth] = useState<number>(dayjs().month());
|
||||||
|
const onChangeDatePicker = (value: any) => {
|
||||||
|
if (value) {
|
||||||
|
setYear(value.$y); // Extract year from the selected date and update the year state
|
||||||
|
setMonth(value.$M); // Extract month from the selected date and update the month state
|
||||||
|
} else {
|
||||||
|
const today = dayjs(); // Get the current date
|
||||||
|
setYear(today.year()); // Update the year state to the current year
|
||||||
|
setMonth(today.month());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const popoverDays = [
|
||||||
|
{
|
||||||
|
day: 10,
|
||||||
|
month: 3,
|
||||||
|
year: 2024,
|
||||||
|
title: "مسابقة الشطرنج الدولية",
|
||||||
|
time: "الساعة 11",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
day: 22,
|
||||||
|
month: 3,
|
||||||
|
year: 2024,
|
||||||
|
title: "مسابقة الشطرنج الدولية",
|
||||||
|
time: "الساعة 12",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [actionsVisible, setActionsVisible] = useState(0);
|
||||||
|
|
||||||
|
const toggleActionsVisibility = (day: number) => {
|
||||||
|
setActionsVisible(day);
|
||||||
|
};
|
||||||
|
const dateCellRender = (value: dayjs.Dayjs) => {
|
||||||
|
const day = value.date();
|
||||||
|
const month = value.month() + 1;
|
||||||
|
const year = value.year();
|
||||||
|
|
||||||
|
const matchingDay = popoverDays.find(
|
||||||
|
(date) => date.day === day && date.month === month && date.year === year,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingDay) {
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
placement="top"
|
||||||
|
className="CalenderPop"
|
||||||
|
rootClassName="CalenderPop"
|
||||||
|
trigger="hover"
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className="Calendar_Popover"
|
||||||
|
onClick={() => toggleActionsVisibility(day)}
|
||||||
|
>
|
||||||
|
<h5>{matchingDay.title}</h5>
|
||||||
|
<h6>{matchingDay.time}</h6>
|
||||||
|
{actionsVisible === day && ( // Only show actions if actionsVisible is true
|
||||||
|
<span className="Actions">
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
<RiDeleteBin6Fill size={22} style={{ color: "#C11313" }} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
// open={true}
|
||||||
|
>
|
||||||
|
<div className="Calendar_ActiveDiv">{day}</div>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="Calendar_Div">{day}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"CalendarSection"}>
|
||||||
|
<Calendar
|
||||||
|
fullscreen={false}
|
||||||
|
fullCellRender={dateCellRender}
|
||||||
|
value={dayjs(`${year}-${month + 1}-01`)}
|
||||||
|
headerRender={() => {
|
||||||
|
return (
|
||||||
|
<div className="CalendarHeader">
|
||||||
|
<h4>تقويم المدرسة</h4>
|
||||||
|
<DatePicker
|
||||||
|
suffixIcon={<IoMdArrowDropdown size={30} />}
|
||||||
|
onChange={(newDate: number) => onChangeDatePicker(newDate)}
|
||||||
|
picker="month"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
31
src/Components/Home/CountSection.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaMoneyBill } from "react-icons/fa";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const CountSection = () => {
|
||||||
|
const data = [
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات", number: 900 },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات", number: 900 },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات", number: 900 },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات", number: 900 },
|
||||||
|
{ icon: <FaMoneyBill />, name: "الطالبات", number: 900 },
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="CountSection">
|
||||||
|
<div className="CountCards">
|
||||||
|
{data?.map((item: any, index: any) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="CountCard">
|
||||||
|
<i>{item?.icon}</i>
|
||||||
|
<h4>{item?.name}</h4>
|
||||||
|
<h6>{item?.number}</h6>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Image src="../Home/HomeCounter.png" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountSection;
|
||||||
82
src/Components/Home/NoteSection.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaMoneyBill } from "react-icons/fa";
|
||||||
|
|
||||||
|
const NoteSection = () => {
|
||||||
|
// Static fake data array
|
||||||
|
const notesData = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
icon: <FaMoneyBill />,
|
||||||
|
title: "ملاحظة مالية",
|
||||||
|
content: [
|
||||||
|
"الرسوم الدراسية: المبلغ المطلوب من الأهل 200000 ل.س",
|
||||||
|
"مبلغ أخر: المبلغ المطلوب من الأهل 300000 ل.س",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
icon: <FaMoneyBill />,
|
||||||
|
title: "ملاحظة مالية",
|
||||||
|
content: [
|
||||||
|
"الرسوم الدراسية: المبلغ المطلوب من الأهل 200000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
icon: <FaMoneyBill />,
|
||||||
|
title: "ملاحظة مالية",
|
||||||
|
content: [
|
||||||
|
"الرسوم الدراسية: المبلغ المطلوب من الأهل 200000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
icon: <FaMoneyBill />,
|
||||||
|
title: "ملاحظة مالية",
|
||||||
|
content: [
|
||||||
|
"الرسوم الدراسية: المبلغ المطلوب من الأهل 200000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
icon: <FaMoneyBill />,
|
||||||
|
title: "ملاحظة مالية",
|
||||||
|
content: [
|
||||||
|
"الرسوم الدراسية: المبلغ المطلوب من الأهل 200000 ل.س",
|
||||||
|
"تكلفة أخرى: المبلغ المطلوب من الأهل 150000 ل.س",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="NoteSection">
|
||||||
|
<header>
|
||||||
|
<h4>ملاحظات اليوم</h4> <h6>8/3/2021</h6>
|
||||||
|
</header>
|
||||||
|
<div className="NoteScrollerChanger">
|
||||||
|
<div className="Notes">
|
||||||
|
{notesData.map((note) => (
|
||||||
|
<article key={note.id}>
|
||||||
|
<div>
|
||||||
|
<i>{note.icon}</i>
|
||||||
|
<h5>{note.title}</h5>
|
||||||
|
</div>
|
||||||
|
{note.content.map((item, index) => (
|
||||||
|
<span key={index}>{item}</span>
|
||||||
|
))}
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NoteSection;
|
||||||
72
src/Components/Home/StudentSubjects.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Progress, Select } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FaLaptop } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const StudentSubjects = () => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState("هذا الأسبوع");
|
||||||
|
const Options = [
|
||||||
|
{ value: "هذا الأسبوع", label: "هذا الأسبوع" },
|
||||||
|
{ value: "الأسبوع الماضي", label: "الأسبوع الماضي" },
|
||||||
|
{ value: "الشهر الماضي", label: "الشهر الماضي" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChangeSelect = (value: any) => {
|
||||||
|
setSelectedOption(value);
|
||||||
|
// console.log(`Selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
// Define static data
|
||||||
|
const staticData = [
|
||||||
|
{
|
||||||
|
SubjectImage: "/Subject/algebra.png",
|
||||||
|
SubjectName: "طالبات",
|
||||||
|
StudentSubjectsPercent: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubjectImage: "/Subject/flask.png",
|
||||||
|
SubjectName: "طالبات",
|
||||||
|
StudentSubjectsPercent: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubjectImage: "/Subject/koran.png",
|
||||||
|
SubjectName: "طالبات",
|
||||||
|
StudentSubjectsPercent: 70,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="StudentSubjects">
|
||||||
|
<header>
|
||||||
|
<h6>{t("عدد الطالبات")}</h6>
|
||||||
|
<Select
|
||||||
|
defaultValue={selectedOption}
|
||||||
|
onChange={handleChangeSelect}
|
||||||
|
suffixIcon={<IoMdArrowDropdown size={30} />}
|
||||||
|
options={Options}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{staticData.map((item, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<Progress
|
||||||
|
type="circle"
|
||||||
|
size={"small"}
|
||||||
|
strokeColor={"#0052B4"}
|
||||||
|
strokeWidth={7}
|
||||||
|
percent={item?.StudentSubjectsPercent}
|
||||||
|
/>
|
||||||
|
<h6>{item.SubjectName}</h6>
|
||||||
|
<Image src={item?.SubjectImage} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudentSubjects;
|
||||||
45
src/Components/Layout/NavBarSelect.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { CYCLE_OBJECT_KEY, TERM_OBJECT_KEY } from "../../config/AppKey";
|
||||||
|
import { Select } from "antd";
|
||||||
|
import { useGetAllTerm } from "../../api/term";
|
||||||
|
import { getLocalStorage } from "../../utils/LocalStorage";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import useAuthState from "../../zustand/AuthState";
|
||||||
|
import useFormatCycleDataToSelect from "../../utils/useFormatCycleDataToSelect";
|
||||||
|
|
||||||
|
const NavBarSelect = () => {
|
||||||
|
const [value, setValue] = useState(null);
|
||||||
|
const cycle_id = getLocalStorage(CYCLE_OBJECT_KEY)?.id;
|
||||||
|
const term_id = getLocalStorage(TERM_OBJECT_KEY)?.id;
|
||||||
|
const { setTerm } = useAuthState();
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const { data: TermData } = useGetAllTerm(
|
||||||
|
{ cy_id: cycle_id },
|
||||||
|
{ enabled: !!cycle_id },
|
||||||
|
);
|
||||||
|
|
||||||
|
const BranchTermData = useFormatCycleDataToSelect(TermData?.data);
|
||||||
|
|
||||||
|
const handleSelectChange = (value: any, option: any) => {
|
||||||
|
setValue(value);
|
||||||
|
localStorage.setItem(TERM_OBJECT_KEY, JSON.stringify(option));
|
||||||
|
setTerm(option?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="NavBarSelect">
|
||||||
|
<Select
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
options={BranchTermData}
|
||||||
|
className="Auth_Select"
|
||||||
|
placeholder={t("input.School_Term")}
|
||||||
|
defaultValue={term_id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavBarSelect;
|
||||||
11
src/Components/Layout/SpinContainer.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Spin } from 'antd'
|
||||||
|
|
||||||
|
const SpinContainer = () => {
|
||||||
|
return (
|
||||||
|
<div className='SpinContainer'>
|
||||||
|
<Spin/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SpinContainer
|
||||||
18
src/Components/Layout/Tabs/ActiveTabs.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
import { useModalTabsState } from "../../../zustand/ModalTabsState";
|
||||||
|
|
||||||
|
const ActiveTabs = ({ steps }: any) => {
|
||||||
|
const { ActiveTab } = useModalTabsState((state) => state);
|
||||||
|
|
||||||
|
const renderComponent = () => {
|
||||||
|
const selectedComponent = steps[Number(ActiveTab)];
|
||||||
|
// console.log(Number(ActiveTab),"Number(ActiveTab)");
|
||||||
|
|
||||||
|
return selectedComponent ? selectedComponent?.component : <Spin />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return renderComponent();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActiveTabs;
|
||||||
33
src/Components/Layout/Tabs/TabsBar.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useModalTabsState } from "../../../zustand/ModalTabsState";
|
||||||
|
|
||||||
|
const TabsBar = ({ steps }: any) => {
|
||||||
|
const { ActiveTab, setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
|
||||||
|
function handleTabClick(index: number) {
|
||||||
|
// setActiveTab(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ModelBodyTabs">
|
||||||
|
{steps?.map(
|
||||||
|
(step: any, index: any) =>
|
||||||
|
!step.hidden && (
|
||||||
|
<div
|
||||||
|
onClick={() => handleTabClick(index)}
|
||||||
|
className={`ModelBodyTab ${ActiveTab === index ? "activeModeltab" : ""}`}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<div>{index + 1}</div>
|
||||||
|
<span>
|
||||||
|
<h6>{step.title}</h6>
|
||||||
|
<h4>{step.description}</h4>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabsBar;
|
||||||
50
src/Components/Layout/Tabs/TabsSubmite.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useModalTabsState } from "../../../zustand/ModalTabsState";
|
||||||
|
import { useModalState } from "../../../zustand/Modal";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
interface TabsSubmiteProps {
|
||||||
|
steps: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsSubmite: React.FC<TabsSubmiteProps> = ({ steps }) => {
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
|
||||||
|
const { isOpen, setIsOpen } = useModalState((state) => state);
|
||||||
|
const { ActiveTab, setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
function handelNext() {
|
||||||
|
// console.log("submited");
|
||||||
|
|
||||||
|
if (Number(ActiveTab) >= steps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setActiveTab(Number(ActiveTab) + 1);
|
||||||
|
}
|
||||||
|
function handelPre() {
|
||||||
|
setActiveTab(Number(ActiveTab) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (formik.isValid) {
|
||||||
|
formik.submitForm();
|
||||||
|
} else {
|
||||||
|
toast.error("الرجاء ادخال جنيع البيانات المطلوبة");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SubmitButton">
|
||||||
|
<div onClick={handelPre}>
|
||||||
|
{ActiveTab > 0 ? "رجوع للخطوة السابقة" : ""}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={ActiveTab + 1 !== steps ? handelNext : () => handleSubmit()}
|
||||||
|
>
|
||||||
|
{ActiveTab + 1 !== steps ? "الخطوة التالية" : "إضافة الطالب"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabsSubmite;
|
||||||
46
src/Components/Student/Add/Model/Form.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Col, Row } from "reactstrap";
|
||||||
|
import React from "react";
|
||||||
|
import ValidationField from "../../../../Components/ValidationField/ValidationField";
|
||||||
|
|
||||||
|
const Form = () => {
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col xs="8" sm="8">
|
||||||
|
{" "}
|
||||||
|
{/* This column will take up 8 units on extra small screens and 6 units on small screens */}
|
||||||
|
<ValidationField name="الجنس" />
|
||||||
|
<ValidationField name="الاسم الأول *" />
|
||||||
|
<ValidationField name="اسم العائلة" />
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField name="اسم العائلة" />
|
||||||
|
<ValidationField name="اسم العائلة" no_label />
|
||||||
|
</div>
|
||||||
|
<div className="TowValidationItems">
|
||||||
|
<ValidationField name="اسم العائلة" type="Select" option={[]} />
|
||||||
|
<ValidationField name="اسم العائلة" />
|
||||||
|
</div>
|
||||||
|
{/* <div className='TowValidationItems'>
|
||||||
|
<ValidationField name='اسم العائلة'/>
|
||||||
|
<ValidationField name='اسم العائلة' no_label/>
|
||||||
|
</div>
|
||||||
|
<div className='TowValidationItems'>
|
||||||
|
<ValidationField name='اسم العائلة'/>
|
||||||
|
<ValidationField name='اسم العائلة' no_label/>
|
||||||
|
</div> */}
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col xs="4" sm="4">
|
||||||
|
{" "}
|
||||||
|
{/* This column will take up 4 units on extra small screens and 6 units on small screens */}
|
||||||
|
<ValidationField name="DropFile" type="DropFile" label="الصورة *" />
|
||||||
|
<ValidationField
|
||||||
|
name="DropFile"
|
||||||
|
type="DropFile"
|
||||||
|
label="الوثيقة المدرسية *"
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Form;
|
||||||
38
src/Components/Student/Add/Model/Modal.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Modal } from "antd";
|
||||||
|
import { useModalState } from "../../../../zustand/Modal";
|
||||||
|
import FormikForm from "../../../../Layout/Dashboard/FormikForm";
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import ModelBody from "./ModelBody";
|
||||||
|
import TabsSubmite from "../../../../Components/Layout/Tabs/TabsSubmite";
|
||||||
|
import { getInitialValues, getValidationSchema } from "./formUtil";
|
||||||
|
import { ModalEnum } from "../../../../enums/Model";
|
||||||
|
|
||||||
|
const ModalForm: React.FC = () => {
|
||||||
|
const { isOpen, setIsOpen } = useModalState((state) => state);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
className="AddModalForm"
|
||||||
|
centered
|
||||||
|
width={"80vw"}
|
||||||
|
footer={null}
|
||||||
|
open={isOpen === ModalEnum.STUDENT_ADD}
|
||||||
|
onOk={() => setIsOpen("")}
|
||||||
|
onCancel={() => setIsOpen("")}
|
||||||
|
>
|
||||||
|
<FormikForm
|
||||||
|
handleSubmit={() => {}}
|
||||||
|
initialValues={getInitialValues}
|
||||||
|
validationSchema={getValidationSchema}
|
||||||
|
>
|
||||||
|
<ModelBody />
|
||||||
|
{/* <TabsSubmite steps={5} /> */}
|
||||||
|
</FormikForm>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalForm;
|
||||||
60
src/Components/Student/Add/Model/ModelBody.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { Divider, Steps } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTabsState } from "../../../../zustand/TabsState";
|
||||||
|
import { useModalTabsState } from "../../../../zustand/ModalTabsState";
|
||||||
|
import Form from "./Form";
|
||||||
|
import TabsBar from "../../../../Components/Layout/Tabs/TabsBar";
|
||||||
|
import ActiveTabs from "../../../../Components/Layout/Tabs/ActiveTabs";
|
||||||
|
|
||||||
|
const ModelBody = () => {
|
||||||
|
const { ActiveTab, setActiveTab } = useModalTabsState((state) => state);
|
||||||
|
function handelTabClick(index: number) {
|
||||||
|
setActiveTab(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: "الخطوة 1",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <Form />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "الخطوة 2",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <>H2</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "الخطوة 3",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <>H2</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "الخطوة 4",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <>H2</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "الخطوة 5",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <>H2</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "الخطوة 5",
|
||||||
|
description: "التفاصيل الشخصية",
|
||||||
|
component: <>H2</>,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ModelBody">
|
||||||
|
<TabsBar steps={steps} />
|
||||||
|
<div className="ModelBodyForm">
|
||||||
|
<header>إضافة طالب</header>
|
||||||
|
<ActiveTabs steps={steps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelBody;
|
||||||
15
src/Components/Student/Add/Model/formUtil.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as Yup from "yup";
|
||||||
|
|
||||||
|
export const getInitialValues = (objectToEdit: any): any => {
|
||||||
|
return {
|
||||||
|
id: objectToEdit?.id ?? 0,
|
||||||
|
name: objectToEdit?.name ?? "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidationSchema = () => {
|
||||||
|
// validate input
|
||||||
|
return Yup.object().shape({
|
||||||
|
name: Yup.string().required("مطلوب"),
|
||||||
|
});
|
||||||
|
};
|
||||||
26
src/Components/Student/CountSection.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaMoneyBill } from "react-icons/fa";
|
||||||
|
import Image from "../Ui/Image";
|
||||||
|
|
||||||
|
const CountSection = () => {
|
||||||
|
return (
|
||||||
|
<div className="CountSection">
|
||||||
|
<div className="CountCards">
|
||||||
|
{[1, 2, 3, 4, 5]?.map((item: any, index: any) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="CountCard">
|
||||||
|
<i>
|
||||||
|
<FaMoneyBill />
|
||||||
|
</i>
|
||||||
|
<h4>الطالبات</h4>
|
||||||
|
<h6>932</h6>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{/* <Image src='../Home/HomeCounter.png'/> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountSection;
|
||||||
21
src/Components/Student/DetailsCard.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaChalkboardTeacher } from "react-icons/fa";
|
||||||
|
import { IoSchool } from "react-icons/io5";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const DetailsCard = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handelclick = () => {
|
||||||
|
navigate("/student/add");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="DetailsCard" onClick={() => handelclick()}>
|
||||||
|
<h6>إضافة طالب</h6>
|
||||||
|
<div>
|
||||||
|
<IoSchool />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailsCard;
|
||||||
56
src/Components/Student/StudentCount.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { Select } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FaLaptop } from "react-icons/fa";
|
||||||
|
import { IoMdArrowDropdown } from "react-icons/io";
|
||||||
|
|
||||||
|
const StudentCount = () => {
|
||||||
|
const [selectedOption, setSelectedOption] = useState("هذا الأسبوع");
|
||||||
|
const Options = [
|
||||||
|
{ value: "هذا الأسبوع", label: "هذا الأسبوع" },
|
||||||
|
{ value: "الأسبوع الماضي", label: "الأسبوع الماضي" },
|
||||||
|
{ value: "الشهر الماضي", label: "الشهر الماضي" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChangeSelect = (value: any) => {
|
||||||
|
setSelectedOption(value);
|
||||||
|
// console.log(`Selected ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
// Define static data
|
||||||
|
const staticData = [
|
||||||
|
{ studentName: "طالبات الشعبة الأولى 1", studentCount: 50 },
|
||||||
|
{ studentName: "طالبات الشعبة الأولى 2", studentCount: 60 },
|
||||||
|
{ studentName: "طالبات الشعبة الأولى 3", studentCount: 70 },
|
||||||
|
{ studentName: "طالبات الشعبة الأولى 4", studentCount: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="StudentCount">
|
||||||
|
<header>
|
||||||
|
<h6>{t("عدد الطالبات")}</h6>
|
||||||
|
<Select
|
||||||
|
defaultValue={selectedOption}
|
||||||
|
onChange={handleChangeSelect}
|
||||||
|
suffixIcon={<IoMdArrowDropdown size={30} />}
|
||||||
|
options={Options}
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{staticData.map((item, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<span>
|
||||||
|
<h6>{item.studentName}</h6>
|
||||||
|
<p>{item.studentCount} طالب</p>
|
||||||
|
</span>
|
||||||
|
<FaLaptop />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudentCount;
|
||||||