fix
23
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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*
|
||||||
|
bundle-analysis.html
|
||||||
26
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"aldeen",
|
||||||
|
"antd",
|
||||||
|
"Attendence",
|
||||||
|
"Checkboxhandle",
|
||||||
|
"Createstudent",
|
||||||
|
"Datepicker",
|
||||||
|
"formik",
|
||||||
|
"Groupbutton",
|
||||||
|
"handelnavigate",
|
||||||
|
"Karim",
|
||||||
|
"queryqlent",
|
||||||
|
"registraion",
|
||||||
|
"SENDNOTIFICATION",
|
||||||
|
"setdateparams",
|
||||||
|
"szhsin",
|
||||||
|
"tagcontainer",
|
||||||
|
"toastify",
|
||||||
|
"Viewelement",
|
||||||
|
"webp",
|
||||||
|
"zustand",
|
||||||
|
"مطلوب"
|
||||||
|
],
|
||||||
|
"vite.https": true
|
||||||
|
}
|
||||||
29
README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
- Node v20.10.0
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.point-dev.net/Karimaldeen/zaker.git
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd school-dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm i
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
pnpm start
|
||||||
|
|
||||||
|
```
|
||||||
24
index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!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/Logo2.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/App/Logo2.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/App/Logo2.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>NERD DASHBOARD</title>
|
||||||
|
</head>
|
||||||
|
<body dir="rtl">
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18242
package-lock.json
generated
Normal file
80
package.json
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^5.3.7",
|
||||||
|
"antd": "^5.17.4",
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
|
"formik": "^2.4.6",
|
||||||
|
"html-to-image": "^1.11.11",
|
||||||
|
"i18next": "^23.11.5",
|
||||||
|
"path-to-regexp": "^6.2.2",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-i18next": "^13.5.0",
|
||||||
|
"react-icons": "^4.12.0",
|
||||||
|
"react-mathjax": "^1.0.1",
|
||||||
|
"react-query": "^3.39.3",
|
||||||
|
"react-router-dom": "^6.23.1",
|
||||||
|
"react-toastify": "^9.1.3",
|
||||||
|
"reactstrap": "^9.2.2",
|
||||||
|
"sass": "^1.77.4",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"vite-plugin-env-compatible": "^2.0.1",
|
||||||
|
"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/node": "^20.14.0",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@types/react-helmet": "^6.1.11",
|
||||||
|
"@vitejs/plugin-legacy": "^5.4.1",
|
||||||
|
"@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",
|
||||||
|
"ts-loader": "^9.5.1",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vite": "^5.2.12",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"webpack": "^5.93.0",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
8129
pnpm-lock.yaml
Normal file
BIN
public/App/Logo.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/App/Logo2.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/App/SyriaLogo.webp
Normal file
|
After Width: | Height: | Size: 48 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 |
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/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/bell.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
public/Icon/cash.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
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/plus.png
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
public/Icon/warning.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/Image/faker_user.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/Image/user_fake.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/Layout/BackgroundHeader.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/Layout/DefaultStudentImage.png
Normal file
|
After Width: | Height: | Size: 861 B |
BIN
public/Layout/ErrorUserImage.png
Normal file
|
After Width: | Height: | Size: 1.0 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
53
src/App.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Suspense, lazy } from "react";
|
||||||
|
import { Route, Routes } from "react-router-dom";
|
||||||
|
import { CrudRoute, menuItems } from "./Routes";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
import { hasAbility } from "./utils/hasAbility";
|
||||||
|
import { renderRoutesRecursively } from "./Components/Routes/RenderRoutesRecursively";
|
||||||
|
import { RenderRouteElement } from "./Components/Routes/RenderRouteElement";
|
||||||
|
|
||||||
|
const Page404 = lazy(() => import("./Layout/Ui/NotFoundPage"));
|
||||||
|
const Auth = lazy(() => import("./Pages/Auth/Page"));
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
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);
|
||||||
|
if (!useAbility) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
key={route.path ?? ""}
|
||||||
|
path={route.path ?? ""}
|
||||||
|
element={RenderRouteElement(route)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
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,
|
||||||
|
};
|
||||||
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;
|
||||||
120
src/Components/CustomFields/Select/CustomSelect.scss
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
.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;
|
||||||
|
height: 2.5vw !important;
|
||||||
|
// 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;
|
||||||
93
src/Components/DataTable/SearchFieldWithSelect.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
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 search__input_text"
|
||||||
|
placeholder={selectedOption ? selectedOption.label : ""}
|
||||||
|
// placeholder={selectedOption ? selectedOption.label : placeholder}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="search__input_text">{placeholder}</p>
|
||||||
|
{/* <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;
|
||||||
54
src/Components/Filter/OrderBySelect.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Divider, Select } from 'antd';
|
||||||
|
import SearchFieldWithSelect from '../../Components/DataTable/SearchFieldWithSelect';
|
||||||
|
import { translateOptions } from '../../utils/translatedOptions';
|
||||||
|
import { search_array } from '../../Routes';
|
||||||
|
import { useFilterStateState } from '../../zustand/Filter';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { TbReorder } from "react-icons/tb";
|
||||||
|
|
||||||
|
const OrderBySelect = () => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const { Filter, setFilter } = useFilterStateState();
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const type_param = searchParams.get('type');
|
||||||
|
const [type, setType] = useState(type_param);
|
||||||
|
const translateArray = translateOptions(search_array, t);
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
const newArray = Filter?.filter((item: any) => item.select !== true);
|
||||||
|
setFilter([
|
||||||
|
...newArray,
|
||||||
|
{ name: value, index: Filter.length, select: true },
|
||||||
|
]);
|
||||||
|
if (type_param) {
|
||||||
|
searchParams.delete('type');
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
setType(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// send this with api request
|
||||||
|
// type: type,
|
||||||
|
// page: currentPage,
|
||||||
|
return (
|
||||||
|
<div className='order_by_filter'>
|
||||||
|
<Select
|
||||||
|
className='order_by_select'
|
||||||
|
style={{ width: 200 }}
|
||||||
|
size="large"
|
||||||
|
placeholder={<div><TbReorder className='addition_select_icon'/> {t("ترتيب حسب")} </div>}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={[
|
||||||
|
{ value: "تصاعديا", label: t("تصاعديا") },
|
||||||
|
{ value: "تنازليا", label: t("تنازليا") },
|
||||||
|
{ value: "شوهدت مؤخرا", label: t("شوهدت مؤخرا") },
|
||||||
|
{ value: "وصلت مؤخرا", label: t("وصلت مؤخرا") },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderBySelect
|
||||||
55
src/Components/Filter/PaginationColumn.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Divider, Select } from 'antd';
|
||||||
|
import SearchFieldWithSelect from '../../Components/DataTable/SearchFieldWithSelect';
|
||||||
|
import { translateOptions } from '../../utils/translatedOptions';
|
||||||
|
import { search_array } from '../../Routes';
|
||||||
|
import { useFilterStateState } from '../../zustand/Filter';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
const PaginationColumn = () => {
|
||||||
|
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const { Filter, setFilter } = useFilterStateState();
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const type_param = searchParams.get('type');
|
||||||
|
const [type, setType] = useState(type_param);
|
||||||
|
const translateArray = translateOptions(search_array, t);
|
||||||
|
const handleChange = (value: string) => {
|
||||||
|
const newArray = Filter?.filter((item: any) => item.select !== true);
|
||||||
|
setFilter([
|
||||||
|
...newArray,
|
||||||
|
{ name: value, index: Filter.length, select: true },
|
||||||
|
]);
|
||||||
|
if (type_param) {
|
||||||
|
searchParams.delete('type');
|
||||||
|
setSearchParams(searchParams);
|
||||||
|
}
|
||||||
|
setType(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// send this with api request
|
||||||
|
// type: type,
|
||||||
|
// page: currentPage,
|
||||||
|
return (
|
||||||
|
<div className='pagination_column'>
|
||||||
|
<Select
|
||||||
|
className='pagination_select'
|
||||||
|
style={{ width: 70 }}
|
||||||
|
size="large"
|
||||||
|
defaultValue={"10"}
|
||||||
|
placeholder={"10"}
|
||||||
|
onChange={handleChange}
|
||||||
|
options={[
|
||||||
|
{ value: "10", label: t("10") },
|
||||||
|
{ value: "20", label: t("20") },
|
||||||
|
{ value: "50", label: t("50") },
|
||||||
|
{ value: "100", label: t("100") },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaginationColumn
|
||||||
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;
|
||||||
16
src/Components/Layout/Navbar/DropdownToggle.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { MdExpandLess, MdExpandMore } from "react-icons/md";
|
||||||
|
|
||||||
|
interface DropdownToggleProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownToggle: React.FC<DropdownToggleProps> = ({ isOpen, onClick }) => {
|
||||||
|
return (
|
||||||
|
<div className="DropDownIcon" onClick={onClick}>
|
||||||
|
{isOpen ? <MdExpandLess /> : <MdExpandMore />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownToggle;
|
||||||
40
src/Components/Layout/Navbar/MenuItem.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
import DropdownToggle from "./DropdownToggle"; // Adjust the import path as necessary
|
||||||
|
import SubMenu from "./SubMenu"; // Adjust the import path as necessary
|
||||||
|
|
||||||
|
export const MenuItem = ({ item, location, index }: any) => {
|
||||||
|
const isActive = location.pathname.split("/")[1] === item.path?.slice(1);
|
||||||
|
const [openDropdown, setOpenDropdown] = useState<number | null>(null);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleDropdown = (index: number) => {
|
||||||
|
setOpenDropdown((prev) => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDropdownOpen = openDropdown === index;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`link ${isActive ? "active" : ""} ${item?.children && "DropDownLink"}`}
|
||||||
|
onClick={() => navigate(item.path || "/")}
|
||||||
|
>
|
||||||
|
<i>{item.icon}</i>
|
||||||
|
<Link to={item.path || "/"}>{t(item.text)}</Link>
|
||||||
|
{item?.children && (
|
||||||
|
<DropdownToggle
|
||||||
|
isOpen={isDropdownOpen}
|
||||||
|
onClick={() => handleDropdown(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{item?.children && isDropdownOpen && (
|
||||||
|
<SubMenu items={item.children} location={location} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
46
src/Components/Layout/Navbar/NavBarRightSide.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from "react";
|
||||||
|
import { getLocalStorage } from "../../../utils/LocalStorage";
|
||||||
|
import { USER_KEY } from "../../../config/AppKey";
|
||||||
|
import { translateOptions } from "../../../utils/translatedOptions";
|
||||||
|
import { search_array } from "../../../Routes";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import SearchFieldWithSelect from "../../DataTable/SearchFieldWithSelect";
|
||||||
|
import { Tooltip } from "antd";
|
||||||
|
import useModalHandler from "../../../utils/useModalHandler";
|
||||||
|
import { ModalEnum } from "../../../enums/Model";
|
||||||
|
import Image from "../../Ui/Image";
|
||||||
|
import { BsPlus } from "react-icons/bs";
|
||||||
|
import { IoIosNotificationsOutline } from "react-icons/io";
|
||||||
|
import { CiCirclePlus } from "react-icons/ci";
|
||||||
|
import { TbWorld } from "react-icons/tb";
|
||||||
|
import TooltipComp from "./Tooltip";
|
||||||
|
|
||||||
|
const NavBarRightSide = () => {
|
||||||
|
const userData = getLocalStorage(USER_KEY);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
// const translateArray = translateOptions(search_array, t);
|
||||||
|
|
||||||
|
const { handel_open_model } = useModalHandler();
|
||||||
|
const handleEdit = () => {
|
||||||
|
handel_open_model(ModalEnum.CHANGE_PASSWORD);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<article>
|
||||||
|
|
||||||
|
<span className="header_icons">
|
||||||
|
<TooltipComp note="change_language" color="#E0E0E0" icon={<TbWorld size={25}/>}/>
|
||||||
|
<TooltipComp note="add" color="#E0E0E0" icon={<CiCirclePlus size={25}/>}/>
|
||||||
|
<TooltipComp className="NotificationsIcon" note="notification" color="#E0E0E0" icon={< > <IoIosNotificationsOutline size={25} /> </>}/>
|
||||||
|
</span>
|
||||||
|
<div className="header_profile">
|
||||||
|
{/* <span>
|
||||||
|
<h6>{userData?.username}</h6>
|
||||||
|
<p>{userData?.type}</p>
|
||||||
|
</span> */}
|
||||||
|
<Image src="/Image/faker_user.png" alt="Profile" />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavBarRightSide;
|
||||||
24
src/Components/Layout/Navbar/SubMenu.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import { MenuItem } from "./MenuItem"; // Adjust the import path as necessary
|
||||||
|
|
||||||
|
interface SubMenuProps {
|
||||||
|
items: any[];
|
||||||
|
location: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubMenu: React.FC<SubMenuProps> = ({ items, location }) => {
|
||||||
|
return (
|
||||||
|
<div className="sub-menu">
|
||||||
|
{items.map((childItem, index) => (
|
||||||
|
<MenuItem
|
||||||
|
key={index}
|
||||||
|
item={childItem}
|
||||||
|
location={location}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubMenu;
|
||||||
35
src/Components/Layout/Navbar/Tooltip.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Tooltip } from "antd";
|
||||||
|
import { ModalEnum } from '../../../enums/Model';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { CiCirclePlus } from 'react-icons/ci';
|
||||||
|
import useModalHandler from '../../../utils/useModalHandler';
|
||||||
|
|
||||||
|
const TooltipComp = ({note,color,icon,className=""}:{note:string,color:string,icon:any,className?:string}) => {
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const { handel_open_model } = useModalHandler();
|
||||||
|
|
||||||
|
const handleEdit = () => {
|
||||||
|
handel_open_model(ModalEnum.CHANGE_PASSWORD);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<Tooltip
|
||||||
|
placement="top"
|
||||||
|
title={
|
||||||
|
<div onClick={handleEdit}>
|
||||||
|
{t(`header.${note}`)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
color={color}
|
||||||
|
>
|
||||||
|
<div className={`gear `}>
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TooltipComp
|
||||||
57
src/Components/Layout/SideBar/MenuItem.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MdExpandLess, MdExpandMore } from "react-icons/md";
|
||||||
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export const MenuItem = ({ item, location, index, isOpen }: any) => {
|
||||||
|
const isActive = location.pathname.split("/")[1] === item.path?.slice(1);
|
||||||
|
const [openDropdown, setOpenDropdown] = useState<number | null>(null);
|
||||||
|
const handleDropdown = (index: number) => {
|
||||||
|
setOpenDropdown((prev) => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
const isDropdownOpen = openDropdown === index;
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`link ${isActive ? "active" : ""} ${item?.children && "DropDownLink"}`}
|
||||||
|
onClick={() => navigate(item.path || "/")}
|
||||||
|
>
|
||||||
|
<i>{item.icon}</i>
|
||||||
|
{/* Conditionally render the text based on sidebar width */}
|
||||||
|
<span style={{ display: isOpen === false ? 'none' : 'inline' }}>
|
||||||
|
{t(item.text)}
|
||||||
|
</span>
|
||||||
|
{item?.children && (
|
||||||
|
<>
|
||||||
|
{isDropdownOpen ? (
|
||||||
|
<div className="DropDownIcon" onClick={() => handleDropdown(index)}>
|
||||||
|
<MdExpandLess />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="DropDownIcon" onClick={() => handleDropdown(index)}>
|
||||||
|
<MdExpandMore />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{item?.children && isDropdownOpen && (
|
||||||
|
<div className="sub-menu">
|
||||||
|
{item.children.map((childItem: any, childIndex: number) => (
|
||||||
|
<MenuItem
|
||||||
|
key={childIndex}
|
||||||
|
item={childItem}
|
||||||
|
location={location}
|
||||||
|
index={childIndex}
|
||||||
|
isOpen={isOpen}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
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;
|
||||||
15
src/Components/Routes/RenderRouteElement.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import SpinContainer from "../Layout/SpinContainer";
|
||||||
|
import Layout from "../../Layout/Ui/Layout";
|
||||||
|
|
||||||
|
export const RenderRouteElement = (route: any) => (
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Layout>
|
||||||
|
<SpinContainer />
|
||||||
|
</Layout>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{route.header ? <Layout>{route.element}</Layout> : route.element || <></>}
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
19
src/Components/Routes/RenderRoutesRecursively.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import { TMenuItem } from "../../types/App";
|
||||||
|
import { hasAbility } from "../../utils/hasAbility";
|
||||||
|
import { Route } from "react-router-dom";
|
||||||
|
import { RenderRouteElement } from "./RenderRouteElement";
|
||||||
|
|
||||||
|
export const renderRoutesRecursively = (routes: TMenuItem[]) =>
|
||||||
|
routes.map((route: TMenuItem) => {
|
||||||
|
const useAbility = hasAbility(route.abilities, route.abilities_value);
|
||||||
|
if (!useAbility) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<React.Fragment key={route.path}>
|
||||||
|
<Route path={route.path} element={RenderRouteElement(route)} />
|
||||||
|
{route.children && renderRoutesRecursively(route.children)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
31
src/Components/Table/ActionAddButton.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaPlus } from "react-icons/fa";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface ActionAddButtonProps {
|
||||||
|
canAdd: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
buttonText?: string;
|
||||||
|
modelName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionAddButton: React.FC<ActionAddButtonProps> = ({
|
||||||
|
canAdd,
|
||||||
|
onClick,
|
||||||
|
buttonText = "practical.add",
|
||||||
|
modelName,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!canAdd) {
|
||||||
|
return null; // Render nothing if canAdd is false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} className="add_button">
|
||||||
|
{t(buttonText)} {t(`models.${modelName}`)} <FaPlus />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionAddButton;
|
||||||
65
src/Components/Table/ActionButtons.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Tooltip, Space } from "antd";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { RiDeleteBin6Fill } from "react-icons/ri";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { BsEyeFill } from "react-icons/bs";
|
||||||
|
import { GoTrash } from "react-icons/go";
|
||||||
|
|
||||||
|
interface ActionButtonsProps {
|
||||||
|
canEdit: boolean;
|
||||||
|
canDelete: boolean;
|
||||||
|
canShow?: boolean;
|
||||||
|
editTooltipTitle?: string;
|
||||||
|
deleteTooltipTitle?: string;
|
||||||
|
onEdit?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onShow?: () => void;
|
||||||
|
index?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionButtons: React.FC<ActionButtonsProps> = ({
|
||||||
|
canEdit,
|
||||||
|
canDelete,
|
||||||
|
editTooltipTitle = "practical.edit",
|
||||||
|
className = "",
|
||||||
|
onEdit = () => {},
|
||||||
|
onDelete = () => {},
|
||||||
|
index,
|
||||||
|
canShow = false,
|
||||||
|
onShow = () => {},
|
||||||
|
}) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const CustomClassName = index
|
||||||
|
? index % 2 === 0
|
||||||
|
? "even-row buttonAction"
|
||||||
|
: "odd-row buttonAction"
|
||||||
|
: "buttonAction";
|
||||||
|
return (
|
||||||
|
<Space size="middle" className={`${CustomClassName} ${className} `}>
|
||||||
|
{canEdit && (
|
||||||
|
<Tooltip placement="top" title={t(editTooltipTitle)} color="#E0E0E0">
|
||||||
|
<span onClick={onEdit}>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canDelete && (
|
||||||
|
// <Tooltip placement="top" title={t(deleteTooltipTitle)} color="#E0E0E0">
|
||||||
|
<GoTrash
|
||||||
|
onClick={onDelete}
|
||||||
|
size={22}
|
||||||
|
style={{ color: "#A098AE" }}
|
||||||
|
/>
|
||||||
|
// </Tooltip>
|
||||||
|
)}
|
||||||
|
{canShow && (
|
||||||
|
<BsEyeFill onClick={onShow} size={22} style={{ color: "green" }} />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ActionButtons;
|
||||||
48
src/Components/Table/ActionColumn.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import { ColumnType } from "antd/lib/table";
|
||||||
|
import ActionAddButton from "./ActionAddButton";
|
||||||
|
import ActionButtons from "./ActionButtons";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
|
||||||
|
interface ActionColumnProps {
|
||||||
|
canAddPayment: boolean;
|
||||||
|
canDeletePayment: boolean;
|
||||||
|
canEditPayment: boolean;
|
||||||
|
handelAdd: () => void;
|
||||||
|
handelDelete: (record: any) => void;
|
||||||
|
handleEdit: (record: any) => void;
|
||||||
|
ModalEnum: ModalEnum; // Adjust this type based on your ModalEnum definition
|
||||||
|
}
|
||||||
|
|
||||||
|
const createActionColumn = ({
|
||||||
|
canAddPayment,
|
||||||
|
canDeletePayment,
|
||||||
|
canEditPayment,
|
||||||
|
handelDelete,
|
||||||
|
handleEdit,
|
||||||
|
handelAdd,
|
||||||
|
}: ActionColumnProps): ColumnType<any> => {
|
||||||
|
return {
|
||||||
|
title: (
|
||||||
|
<ActionAddButton
|
||||||
|
canAdd={canAddPayment}
|
||||||
|
modelName="payment"
|
||||||
|
onClick={() => handelAdd()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: "actions",
|
||||||
|
align: "end",
|
||||||
|
className: "custom_add_button_column",
|
||||||
|
render: (_text, record, index) => (
|
||||||
|
<ActionButtons
|
||||||
|
canDelete={canDeletePayment}
|
||||||
|
canEdit={canEditPayment}
|
||||||
|
index={index}
|
||||||
|
onDelete={() => handelDelete(record)}
|
||||||
|
onEdit={() => handleEdit(record)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createActionColumn;
|
||||||
37
src/Components/Table/TableAddButton.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FaPlus } from "react-icons/fa";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ModalEnum } from "../../enums/Model";
|
||||||
|
import { useModalState } from "../../zustand/Modal";
|
||||||
|
|
||||||
|
interface TableAddButtonProps {
|
||||||
|
canAdd: boolean;
|
||||||
|
ModalEnumValue: ModalEnum;
|
||||||
|
buttonText?: string;
|
||||||
|
modelName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableAddButton: React.FC<TableAddButtonProps> = ({
|
||||||
|
canAdd,
|
||||||
|
ModalEnumValue,
|
||||||
|
buttonText = "practical.add",
|
||||||
|
modelName,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setIsOpen } = useModalState((state) => state);
|
||||||
|
|
||||||
|
if (!canAdd) {
|
||||||
|
return null; // Render nothing if canAdd is false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
// onClick={() => setIsOpen(ModalEnum?.[ModalEnumValue])}
|
||||||
|
className="add_button"
|
||||||
|
>
|
||||||
|
{t(buttonText)} {t(`models.${modelName}`)} <FaPlus />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableAddButton;
|
||||||
35
src/Components/Ui/CurserBlob.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
const CursorBlob = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
const blob = document.getElementById("Blob");
|
||||||
|
|
||||||
|
const handleMouseMove = (event: any) => {
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
|
||||||
|
const scrollTop =
|
||||||
|
window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
const scrollLeft =
|
||||||
|
window.pageXOffset || document.documentElement.scrollLeft;
|
||||||
|
|
||||||
|
blob &&
|
||||||
|
blob.animate(
|
||||||
|
{
|
||||||
|
top: `${clientY + scrollTop}px`,
|
||||||
|
left: `${clientX + scrollLeft}px`,
|
||||||
|
},
|
||||||
|
{ duration: 5000, fill: "forwards" },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div id="Blob" className="Blob2"></div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CursorBlob;
|
||||||
46
src/Components/Ui/Custom/AuthSelect.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Select } from "antd";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface AuthSelectProps {
|
||||||
|
options: {}[];
|
||||||
|
name: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
label: string;
|
||||||
|
placeholder: string;
|
||||||
|
KEY_OBJECT: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthSelect: React.FC<AuthSelectProps> = ({
|
||||||
|
options,
|
||||||
|
name,
|
||||||
|
disabled,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
KEY_OBJECT,
|
||||||
|
}: AuthSelectProps) => {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
const handleSelectChange = (value: any, option: any) => {
|
||||||
|
setFieldValue(name, value);
|
||||||
|
localStorage.setItem(KEY_OBJECT, JSON.stringify(option));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="AuthSelect">
|
||||||
|
<label className="form-label" htmlFor="password">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
options={options}
|
||||||
|
className="Auth_Select"
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthSelect;
|
||||||
39
src/Components/Ui/Custom/CustomDatePicker.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { DatePicker } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import type { DatePickerProps } from "antd";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { DateEnum } from "../../../enums/Date";
|
||||||
|
|
||||||
|
const CustomDatePicker = () => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const { setParamToSend, paramToSend } = useObjectToEdit();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const onChange: DatePickerProps["onChange"] = (date, dateString) => {
|
||||||
|
// console.log(date, dateString);
|
||||||
|
const newObj = { ...paramToSend };
|
||||||
|
newObj.date = dateString;
|
||||||
|
setParamToSend(newObj);
|
||||||
|
navigate(
|
||||||
|
`${location.pathname}?${paramToSend?.state ?? "all"}=${dateString}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const Today = new Date() as any;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="CustomDatePicker">
|
||||||
|
<DatePicker
|
||||||
|
defaultValue={dayjs(Today)}
|
||||||
|
placeholder={t(`input.select_date`)}
|
||||||
|
onChange={onChange}
|
||||||
|
format={DateEnum?.FORMATE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomDatePicker;
|
||||||
33
src/Components/Ui/Image.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React, { FC, ImgHTMLAttributes } from "react";
|
||||||
|
import { BaseURL } from "../../api/config";
|
||||||
|
import useImageError from "../../Hooks/useImageError";
|
||||||
|
|
||||||
|
interface ImageProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||||
|
src: string | any;
|
||||||
|
className?: string;
|
||||||
|
isBaseURL?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image: FC<ImageProps> = ({
|
||||||
|
src = "",
|
||||||
|
className = "",
|
||||||
|
isBaseURL = false,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const handleError = useImageError;
|
||||||
|
const imageUrl = isBaseURL ? BaseURL + src : src;
|
||||||
|
// console.log(imageUrl,"imageUrl");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={imageUrl || "/Image/user_fake.png"}
|
||||||
|
className={className}
|
||||||
|
onError={handleError}
|
||||||
|
alt={src ?? ""}
|
||||||
|
loading="lazy"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Image;
|
||||||
37
src/Components/Ui/SearchBar/SearchBar.scss
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
.SearchBar {
|
||||||
|
// margin-top: 20px;
|
||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
max-width: 350px;
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 2px 2px 7px 0 var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input::placeholder {
|
||||||
|
color: var(--subtext);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
fill: var(--subtext);
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/Components/Ui/SearchBar/SearchBar.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import "./SearchBar.scss";
|
||||||
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
const SearchBar = () => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleChange = (event: any) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setSearchQuery(value);
|
||||||
|
updateUrlParams(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUrlParams = (value: any) => {
|
||||||
|
navigate(`?search=${value}`, { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SearchBar">
|
||||||
|
<div className="group">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-1b5stb0 icon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
data-testid="SearchIcon"
|
||||||
|
>
|
||||||
|
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
placeholder="Search Product...."
|
||||||
|
type="search"
|
||||||
|
className="input"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBar;
|
||||||
91
src/Components/Utils/Loading/Loading.scss
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
.Loading {
|
||||||
|
.wrapper {
|
||||||
|
width: 200px;
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
left: 40%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary);
|
||||||
|
left: 15%;
|
||||||
|
transform-origin: 50%;
|
||||||
|
animation: circle7124 0.5s alternate infinite ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes circle7124 {
|
||||||
|
0% {
|
||||||
|
top: 60px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50px 50px 25px 25px;
|
||||||
|
transform: scaleX(1.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle:nth-child(2) {
|
||||||
|
left: 45%;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle:nth-child(3) {
|
||||||
|
left: auto;
|
||||||
|
right: 15%;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
width: 20px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
position: absolute;
|
||||||
|
top: 62px;
|
||||||
|
transform-origin: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
left: 15%;
|
||||||
|
filter: blur(1px);
|
||||||
|
animation: shadow046 0.5s alternate infinite ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shadow046 {
|
||||||
|
0% {
|
||||||
|
transform: scaleX(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: scaleX(1);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scaleX(0.2);
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow:nth-child(4) {
|
||||||
|
left: 45%;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow:nth-child(5) {
|
||||||
|
left: auto;
|
||||||
|
right: 15%;
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Components/Utils/Loading/Loading.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./Loading.scss";
|
||||||
|
const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div className="Loading">
|
||||||
|
<div className="wrapper">
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
||||||
34
src/Components/Utils/SearchBar/SearchBar.scss
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
.SearchBar {
|
||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
max-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--bg);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 2px 2px 7px 0 var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input::placeholder {
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
fill: var(--bg);
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Components/Utils/SearchBar/SearchBar.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./SearchBar.scss";
|
||||||
|
const SearchBar = () => {
|
||||||
|
return (
|
||||||
|
<div className="SearchBar">
|
||||||
|
<div className="group">
|
||||||
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
|
<g>
|
||||||
|
<path d="M21.53 20.47l-3.66-3.66C19.195 15.24 20 13.214 20 11c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.215 0 4.24-.804 5.808-2.13l3.66 3.66c.147.146.34.22.53.22s.385-.073.53-.22c.295-.293.295-.767.002-1.06zM3.5 11c0-4.135 3.365-7.5 7.5-7.5s7.5 3.365 7.5 7.5-3.365 7.5-7.5 7.5-7.5-3.365-7.5-7.5z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input placeholder="Search" type="search" className="input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBar;
|
||||||
24
src/Components/ValidationField/Ui/KarimSpinner.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Spin } from "antd";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
loading: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KarimSpinner: React.FC<Props> = ({ loading, className, children }) => {
|
||||||
|
return (
|
||||||
|
<div className={className ?? ""}>
|
||||||
|
{loading ? (
|
||||||
|
<div className="text-center">
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KarimSpinner;
|
||||||
37
src/Components/ValidationField/Ui/SearchBar.scss
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// .SearchBar {
|
||||||
|
// // margin-top: 20px;
|
||||||
|
// .group {
|
||||||
|
// display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
// position: relative;
|
||||||
|
// max-width: 350px;
|
||||||
|
// width: 350px;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .input {
|
||||||
|
// width: 100%;
|
||||||
|
// height: 40px;
|
||||||
|
// padding: 0 1rem;
|
||||||
|
// padding-left: 2.5rem;
|
||||||
|
// border-radius: 8px;
|
||||||
|
// outline: none;
|
||||||
|
// font-weight: 500;
|
||||||
|
// background: var(--bg);
|
||||||
|
// color: var(--text);
|
||||||
|
// border: none;
|
||||||
|
// box-shadow: 2px 2px 7px 0 var(--bg);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .input::placeholder {
|
||||||
|
// color: var(--subtext);
|
||||||
|
// opacity: 0.4;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .icon {
|
||||||
|
// position: absolute;
|
||||||
|
// left: 1rem;
|
||||||
|
// fill: var(--subtext);
|
||||||
|
// width: 1rem;
|
||||||
|
// height: 1rem;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
45
src/Components/ValidationField/Ui/SearchBar.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import "./SearchBar.scss";
|
||||||
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
const SearchBar = () => {
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleChange = (event: any) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setSearchQuery(value);
|
||||||
|
updateUrlParams(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUrlParams = (value: any) => {
|
||||||
|
navigate(`?search=${value}`, { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="SearchBar">
|
||||||
|
<div className="group">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||||
|
className="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-1b5stb0 icon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
data-testid="SearchIcon"
|
||||||
|
>
|
||||||
|
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
placeholder="Search Product...."
|
||||||
|
type="search"
|
||||||
|
className="input"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchBar;
|
||||||
48
src/Components/ValidationField/ValidationField.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./utils/ValidationField.scss";
|
||||||
|
import {
|
||||||
|
Date,
|
||||||
|
Time,
|
||||||
|
File,
|
||||||
|
DataRange,
|
||||||
|
SelectField,
|
||||||
|
Default,
|
||||||
|
CheckboxField,
|
||||||
|
MaltyFile,
|
||||||
|
SearchField,
|
||||||
|
TextField,
|
||||||
|
DropFile,
|
||||||
|
} from "./View";
|
||||||
|
import { ValidationFieldProps, ValidationFieldType } from "./utils/types";
|
||||||
|
import LocalSearchField from "./View/LocalSearch";
|
||||||
|
import NumberFormate from "./View/NumberFormate";
|
||||||
|
import NumberField from "./View/NumberField";
|
||||||
|
|
||||||
|
const components: { [key: string]: React.FC<any> } = {
|
||||||
|
Select: SelectField,
|
||||||
|
Search: SearchField,
|
||||||
|
LocalSearch: LocalSearchField,
|
||||||
|
DataRange: DataRange,
|
||||||
|
TextArea: TextField,
|
||||||
|
Date: Date,
|
||||||
|
Time: Time,
|
||||||
|
File: File,
|
||||||
|
DropFile: DropFile,
|
||||||
|
MaltyFile: MaltyFile,
|
||||||
|
Checkbox: CheckboxField,
|
||||||
|
NumberFormate: NumberFormate,
|
||||||
|
Number: NumberField,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValidationField: React.FC<ValidationFieldProps> = React.memo(
|
||||||
|
({ type, ...otherProps }: any) => {
|
||||||
|
const Component = components[type as ValidationFieldType];
|
||||||
|
|
||||||
|
if (!Component) {
|
||||||
|
return <Default {...otherProps} />;
|
||||||
|
}
|
||||||
|
return <Component {...otherProps} />;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ValidationField;
|
||||||
39
src/Components/ValidationField/View/CheckboxField.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { Checkbox, Form } from "antd";
|
||||||
|
import { getNestedValue } from "../utils/getNestedValue";
|
||||||
|
const CheckboxField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
Group,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
}: any) => {
|
||||||
|
const { t, formik, isError, errorMsg } = useFormField(name, props);
|
||||||
|
const CheckboxhandleChange = (value: any) => {
|
||||||
|
formik.setFieldValue(name, value?.target?.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Group ? "d-inline mt-3 Checkboxs" : ``}>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
onChange={onChange || CheckboxhandleChange}
|
||||||
|
disabled={isDisabled}
|
||||||
|
checked={formik.values?.[name] ?? false}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CheckboxField;
|
||||||
64
src/Components/ValidationField/View/DataRange.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Form, DatePicker } from "antd";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
const DataRange = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
Format,
|
||||||
|
props,
|
||||||
|
onChange,
|
||||||
|
isDisabled,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100 ">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<RangePicker
|
||||||
|
placeholder={placeholder}
|
||||||
|
size="large"
|
||||||
|
allowClear
|
||||||
|
className={`${className} w-100`}
|
||||||
|
format={Format}
|
||||||
|
onChange={onChange || onCalendarChange}
|
||||||
|
disabled={isDisabled}
|
||||||
|
defaultValue={formik.values[name]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DataRange;
|
||||||
69
src/Components/ValidationField/View/Date.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { Form, DatePicker } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const Date = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
picker = "date",
|
||||||
|
isDisabled,
|
||||||
|
props,
|
||||||
|
onChange,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
|
||||||
|
const FormikValue = formik.values[name];
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
// console.log(value,"value ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const Formater = "YYYY/MM/DD";
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100 ">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
picker={picker}
|
||||||
|
placeholder={t(`input.${placeholder}`)}
|
||||||
|
allowClear
|
||||||
|
className={`${className} w-100`}
|
||||||
|
value={FormikValue ? FormikValue : null}
|
||||||
|
size="large"
|
||||||
|
onChange={onChange || onCalendarChange}
|
||||||
|
disabled={isDisabled}
|
||||||
|
format={Formater}
|
||||||
|
/>
|
||||||
|
{/* <DatePicker onChange={onChange} /> */}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Date;
|
||||||
67
src/Components/ValidationField/View/Default.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Form, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { Field } from "formik";
|
||||||
|
import { ValidationFieldPropsInput } from "../utils/types";
|
||||||
|
|
||||||
|
const Default = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
type,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
label2,
|
||||||
|
...props
|
||||||
|
}: ValidationFieldPropsInput) => {
|
||||||
|
const { errorMsg, isError, t } = useFormField(name, props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
{label2 ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{label2}
|
||||||
|
</label>
|
||||||
|
) : no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : placeholder ? placeholder : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={Input}
|
||||||
|
type={type ?? "text"}
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
size="large"
|
||||||
|
{...(type === "number" && { min: 0 })}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(Default);
|
||||||
93
src/Components/ValidationField/View/DropFile.tsx.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
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 useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { ImageBaseURL } from "../../../api/config";
|
||||||
|
|
||||||
|
type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
|
||||||
|
|
||||||
|
const DropFile = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
isDisabled,
|
||||||
|
placholder,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, t, isError } = useFormField(name, props);
|
||||||
|
let FormikName = formik?.values[name];
|
||||||
|
|
||||||
|
const FormikValue =
|
||||||
|
typeof FormikName === "string"
|
||||||
|
? ImageBaseURL + FormikName
|
||||||
|
: FormikName instanceof File
|
||||||
|
? URL.createObjectURL(FormikName)
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const [imageUrl, setImageUrl] = useState<any>(FormikValue ?? "");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageUrl(FormikName);
|
||||||
|
}, [FormikName]);
|
||||||
|
|
||||||
|
const getBase64 = (img: FileType, callback: (url: string) => void) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => callback(reader.result as string));
|
||||||
|
reader.readAsDataURL(img);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange: UploadProps["onChange"] = (info) => {
|
||||||
|
if (info.file.status === "done") {
|
||||||
|
getBase64(info.file.originFileObj as FileType, (url) => {
|
||||||
|
setImageUrl(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
formik.setFieldValue(name, info.file.originFileObj);
|
||||||
|
};
|
||||||
|
const customRequest = async ({ onSuccess }: any) => {
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
const uploadButton = (
|
||||||
|
<button style={{ border: 0, background: "none" }} type="button">
|
||||||
|
<div className={`CustomFile ${isError ? "uploader_error" : ""} `}>
|
||||||
|
{t("input.drag_and_drop_or_click_here_to_select_the_file")}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label || name}`)}
|
||||||
|
</label>
|
||||||
|
<Upload
|
||||||
|
name="avatar"
|
||||||
|
listType="picture-card"
|
||||||
|
className={`avatar-uploader ${isError ? "uploader_error" : ""} ${className}`}
|
||||||
|
showUploadList={false}
|
||||||
|
customRequest={customRequest}
|
||||||
|
onChange={onChange || handleChange}
|
||||||
|
>
|
||||||
|
{imageUrl ? (
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
imageUrl instanceof File
|
||||||
|
? URL.createObjectURL(imageUrl)
|
||||||
|
: imageUrl
|
||||||
|
}
|
||||||
|
alt=""
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
uploadButton
|
||||||
|
)}
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropFile;
|
||||||
82
src/Components/ValidationField/View/File.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { Button, Upload, UploadFile } from "antd";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const File = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
isDisabled,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
icon,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, t, isError, errorMsg } = useFormField(name, props);
|
||||||
|
let imageUrl = formik?.values?.[name] ?? null;
|
||||||
|
console.log(imageUrl);
|
||||||
|
console.log(typeof imageUrl === "string");
|
||||||
|
|
||||||
|
const fileList: UploadFile[] = useMemo(() => {
|
||||||
|
if (!imageUrl) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
typeof imageUrl === "string"
|
||||||
|
? {
|
||||||
|
uid: "-1",
|
||||||
|
name: "uploaded-image",
|
||||||
|
status: "done",
|
||||||
|
url: imageUrl,
|
||||||
|
thumbUrl: imageUrl,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
uid: imageUrl.uid || "-1",
|
||||||
|
name: imageUrl.name || "uploaded-image",
|
||||||
|
status: "done",
|
||||||
|
originFileObj: imageUrl,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [imageUrl]);
|
||||||
|
// console.log(1);
|
||||||
|
|
||||||
|
const FilehandleChange = (value: any) => {
|
||||||
|
// console.log(value,"filevalue");
|
||||||
|
if (value.fileList.length === 0) {
|
||||||
|
formik.setFieldValue(name, null);
|
||||||
|
} else {
|
||||||
|
formik.setFieldValue(name, value?.file?.originFileObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const customRequest = async ({ onSuccess, no_label, label_icon }: any) => {
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={`ValidationField upload_image_button ${className ?? ""} `}>
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label || name}`)}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Upload
|
||||||
|
disabled={isDisabled}
|
||||||
|
listType="picture"
|
||||||
|
maxCount={1}
|
||||||
|
fileList={[...fileList]}
|
||||||
|
onChange={onChange || FilehandleChange}
|
||||||
|
customRequest={customRequest}
|
||||||
|
className={` w-100`}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={isError ? "isError w-100 " : " w-100"}
|
||||||
|
icon={icon ? icon : <UploadOutlined />}
|
||||||
|
>
|
||||||
|
{placeholder ?? t("input.Click_to_upload_the_image")}
|
||||||
|
</Button>
|
||||||
|
<div className="Error_color"> {isError ? "required" : ""}</div>
|
||||||
|
{errorMsg}
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default File;
|
||||||
93
src/Components/ValidationField/View/LocalSearch.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { Form, Select } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { translateOptions } from "../utils/translatedOptions";
|
||||||
|
|
||||||
|
const LocalSelectField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
option,
|
||||||
|
isMulti,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
|
||||||
|
// State to manage the search input value
|
||||||
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
|
||||||
|
const handleSearch = (
|
||||||
|
input: string,
|
||||||
|
option: { value: string; label: React.ReactNode | undefined },
|
||||||
|
) =>
|
||||||
|
option?.label?.toString().toLowerCase().includes(input.toLowerCase()) ||
|
||||||
|
option?.value?.toString().toLowerCase().includes(input.toLowerCase());
|
||||||
|
|
||||||
|
const SelectableChange = (value: {
|
||||||
|
value: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (value: any) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
if (onChange) onChange(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (input: string) => {
|
||||||
|
setSearchValue(input); // Update the search input value
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
options={translateOptions(option, t)}
|
||||||
|
size="large"
|
||||||
|
className={`${className} ${isError ? "Select_error" : ""} w-100`}
|
||||||
|
value={formik.values[name]}
|
||||||
|
allowClear
|
||||||
|
{...(isMulti && { mode: "multiple" })}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
showSearch
|
||||||
|
filterOption={handleSearch} // Custom filter function
|
||||||
|
searchValue={searchValue} // Control the search input value
|
||||||
|
onSearch={handleSearchChange} // Update search input value on change
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(LocalSelectField);
|
||||||
84
src/Components/ValidationField/View/MaltyFile.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { Button, Upload } from "antd";
|
||||||
|
import { UploadOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
|
||||||
|
const MaltyFile = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
onChange,
|
||||||
|
isDisabled,
|
||||||
|
placeholder,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, t, isError } = useFormField(name, props);
|
||||||
|
let imageUrl = formik?.values?.[name] ?? null;
|
||||||
|
|
||||||
|
// Mapping formik values to fileList format
|
||||||
|
const fileList = imageUrl
|
||||||
|
? imageUrl.map((file: any, index: number) => {
|
||||||
|
// console.log(file,"file");
|
||||||
|
|
||||||
|
return file instanceof File
|
||||||
|
? {
|
||||||
|
uid: index,
|
||||||
|
name: file?.name,
|
||||||
|
status: "done",
|
||||||
|
originFileObj: file,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
uid: index,
|
||||||
|
id: file?.id,
|
||||||
|
name: file?.name,
|
||||||
|
status: "done",
|
||||||
|
url: file?.url || "",
|
||||||
|
thumbUrl: file?.url || "",
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const FilehandleChange = ({ fileList }: any) => {
|
||||||
|
if (fileList.length === 0) {
|
||||||
|
formik.setFieldValue(name, null);
|
||||||
|
} else {
|
||||||
|
formik.setFieldValue(
|
||||||
|
name,
|
||||||
|
fileList.map((file: any) => file?.originFileObj ?? file),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Custom request function
|
||||||
|
const customRequest = async ({ onSuccess }: any) => {
|
||||||
|
// Perform any necessary actions before onSuccess is called
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField upload_image_button">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label || name}`)}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Upload
|
||||||
|
disabled={isDisabled}
|
||||||
|
listType="picture"
|
||||||
|
fileList={fileList} // Using fileList instead of defaultFileList
|
||||||
|
onChange={onChange || FilehandleChange}
|
||||||
|
customRequest={customRequest}
|
||||||
|
className={`${className} w-100`}
|
||||||
|
multiple // Allow multiple files to be selected
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className={isError ? "isError w-100" : " w-100"}
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
>
|
||||||
|
{t(`input.` + placeholder) ?? t("input.upload_image")}
|
||||||
|
</Button>
|
||||||
|
<div className="Error_color"> {isError ? "required" : ""}</div>
|
||||||
|
</Upload>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MaltyFile;
|
||||||
70
src/Components/ValidationField/View/NumberField.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Form, Input, InputNumber } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { Field } from "formik";
|
||||||
|
import { ValidationFieldPropsInput } from "../utils/types";
|
||||||
|
|
||||||
|
const NumberField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
type,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
...props
|
||||||
|
}: ValidationFieldPropsInput) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
) => {
|
||||||
|
console.log("Change:", e);
|
||||||
|
formik.setFieldValue(name, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : placeholder ? placeholder : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={InputNumber}
|
||||||
|
type={type ?? "text"}
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
size="large"
|
||||||
|
onChange={handleChange}
|
||||||
|
{...(type === "number" && { min: 0 })}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(NumberField);
|
||||||
74
src/Components/ValidationField/View/NumberFormate.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { Form, Input, InputNumber } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { Field } from "formik";
|
||||||
|
|
||||||
|
const NumberFormate = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
props,
|
||||||
|
type,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
const SelectableChange = (value: {
|
||||||
|
value: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : placeholder ? placeholder : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={InputNumber}
|
||||||
|
formatter={(value: any) =>
|
||||||
|
`${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||||
|
}
|
||||||
|
parser={(value: any) =>
|
||||||
|
value?.replace(/\$\s?|(,*)/g, "") as unknown as number
|
||||||
|
}
|
||||||
|
min="0"
|
||||||
|
type={type ?? "text"}
|
||||||
|
value={formik.values[name]}
|
||||||
|
onChange={SelectableChange}
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
size="large"
|
||||||
|
|
||||||
|
// onChange={onChange ? onChange : handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(NumberFormate);
|
||||||
89
src/Components/ValidationField/View/SearchField.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { Form, Select, Spin } from "antd";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
|
||||||
|
const SearchField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
searchBy,
|
||||||
|
option,
|
||||||
|
isMulti,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
isLoading,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
const searchParams = new URLSearchParams(window?.location?.search);
|
||||||
|
setSearchQuery(searchParams?.get("search") || "");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const SelectableChange = (value: {
|
||||||
|
value: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
formik?.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
const SearchHandleChange = (value: any) => {
|
||||||
|
navigate(`${window?.location?.pathname}?${searchBy}=${value}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100 ">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
options={option}
|
||||||
|
size="large"
|
||||||
|
className={`${className} w-100`}
|
||||||
|
value={formik.values[name]}
|
||||||
|
loading={isLoading}
|
||||||
|
allowClear
|
||||||
|
{...(isMulti && { mode: "multiple" })}
|
||||||
|
onChange={onChange || SelectableChange}
|
||||||
|
showSearch
|
||||||
|
optionFilterProp="label"
|
||||||
|
notFoundContent={isLoading ? <Spin /> : "لا يوجد"}
|
||||||
|
onSearch={SearchHandleChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SearchField);
|
||||||
72
src/Components/ValidationField/View/SelectField.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Form, Select, Spin } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { translateOptions } from "../utils/translatedOptions";
|
||||||
|
|
||||||
|
const SelectField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
option,
|
||||||
|
isMulti,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
props,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
const SelectableChange = (value: {
|
||||||
|
value: string;
|
||||||
|
label: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
// console.log(name,"Select");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
options={translateOptions(option, t)}
|
||||||
|
loading={option?.length < 1}
|
||||||
|
size="large"
|
||||||
|
className={`${className} ${isError ? "Select_error" : ""} w-100`}
|
||||||
|
value={formik.values[name]}
|
||||||
|
allowClear
|
||||||
|
{...(isMulti && { mode: "multiple" })}
|
||||||
|
onChange={onChange || SelectableChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SelectField);
|
||||||
50
src/Components/ValidationField/View/TextAreaField.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { Form, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { Field } from "formik";
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
const TextAreaField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
props,
|
||||||
|
type,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, isError, errorMsg, t } = useFormField(name, props);
|
||||||
|
|
||||||
|
const handleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
) => {
|
||||||
|
// console.log('Change:', e.target.value);
|
||||||
|
formik.setFieldValue(name, e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={TextArea}
|
||||||
|
placeholder={t(`input.${placeholder ? placeholder : name}`)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
size="large"
|
||||||
|
onChange={onChange || handleChange}
|
||||||
|
|
||||||
|
// onChange={onChange ? onChange : handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TextAreaField);
|
||||||
67
src/Components/ValidationField/View/TextField.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Form, Input } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import { Field } from "formik";
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
const TextField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
label2,
|
||||||
|
placeholder,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
props,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
className,
|
||||||
|
}: any) => {
|
||||||
|
const { formik, isError, errorMsg, t } = useFormField(name, props);
|
||||||
|
const TextFilehandleChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
) => {
|
||||||
|
// console.log('Change:', e.target.value);
|
||||||
|
formik.setFieldValue(name, e.target.value);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={`ValidationField w-100 ${className ?? ""} `}>
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{label2 ? label2 : t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{label2 ? label2 : t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={TextArea}
|
||||||
|
placeholder={t(`input.${placeholder ? placeholder : name}`)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
size="large"
|
||||||
|
showCount
|
||||||
|
maxLength={1000}
|
||||||
|
onChange={onChange || TextFilehandleChange}
|
||||||
|
style={{ height: 120 }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TextField);
|
||||||
68
src/Components/ValidationField/View/Time.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { Form, TimePicker } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import useFormField from "../../../Hooks/useFormField";
|
||||||
|
import { MdOutlineEdit } from "react-icons/md";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const Time = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
className,
|
||||||
|
isDisabled,
|
||||||
|
onChange,
|
||||||
|
props,
|
||||||
|
placeholder,
|
||||||
|
no_label,
|
||||||
|
label_icon,
|
||||||
|
}: any) => {
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props);
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
formik.setFieldValue(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Formater = "H:mm";
|
||||||
|
const FormikValue = formik.values[name];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ValidationField w-100 ">
|
||||||
|
{no_label ? (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
<span>empty</span>
|
||||||
|
</label>
|
||||||
|
) : label_icon ? (
|
||||||
|
<div className="LabelWithIcon">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<MdOutlineEdit size={22} style={{ color: "#A098AE" }} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`input.${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<TimePicker
|
||||||
|
allowClear
|
||||||
|
className={`${className} w-100`}
|
||||||
|
size="large"
|
||||||
|
value={FormikValue ? dayjs(FormikValue, Formater) : null}
|
||||||
|
onChange={onChange || onCalendarChange}
|
||||||
|
disabled={isDisabled}
|
||||||
|
placeholder={t(
|
||||||
|
`input.${placeholder ? placeholder : label ? label : name}`,
|
||||||
|
)}
|
||||||
|
format={Formater}
|
||||||
|
needConfirm={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Time;
|
||||||
25
src/Components/ValidationField/View/index.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Time from "./Time";
|
||||||
|
import SelectField from "./SelectField";
|
||||||
|
import Date from "./Date";
|
||||||
|
import DataRange from "./DataRange";
|
||||||
|
import CheckboxField from "./CheckboxField";
|
||||||
|
import Default from "./Default";
|
||||||
|
import File from "./File";
|
||||||
|
import MaltyFile from "./MaltyFile";
|
||||||
|
import SearchField from "./SearchField";
|
||||||
|
import TextField from "./TextField";
|
||||||
|
import DropFile from "./DropFile.tsx";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Time,
|
||||||
|
SelectField,
|
||||||
|
Date,
|
||||||
|
DataRange,
|
||||||
|
CheckboxField,
|
||||||
|
Default,
|
||||||
|
File,
|
||||||
|
MaltyFile,
|
||||||
|
SearchField,
|
||||||
|
TextField,
|
||||||
|
DropFile,
|
||||||
|
};
|
||||||
16
src/Components/ValidationField/index.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ErrorMessage, useField, Field, useFormikContext } from "formik";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FaExclamationCircle } from "react-icons/fa";
|
||||||
|
import { convert_data_to_select } from "../../Layout/app/Const";
|
||||||
|
|
||||||
|
export {
|
||||||
|
useState,
|
||||||
|
ErrorMessage,
|
||||||
|
useField,
|
||||||
|
Field,
|
||||||
|
useFormikContext,
|
||||||
|
useTranslation,
|
||||||
|
FaExclamationCircle,
|
||||||
|
convert_data_to_select,
|
||||||
|
};
|
||||||
228
src/Components/ValidationField/utils/ValidationField.scss
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
.LabelWithIcon {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.ValidationField {
|
||||||
|
margin-bottom: 1.3vw;
|
||||||
|
position: relative;
|
||||||
|
> * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.text,
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 7px !important;
|
||||||
|
> span {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Select_error {
|
||||||
|
.ant-select-selector {
|
||||||
|
border: 1px solid red !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ValidationField{
|
||||||
|
// .ant-select-selector{
|
||||||
|
// border: 1px solid var(--border-color) ;
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
> span {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 1px var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:has(.is-invalid) {
|
||||||
|
border-color: red !important ;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
-webkit-box-shadow: 0 0 0 30px white inset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.Checkboxs {
|
||||||
|
padding: 4%;
|
||||||
|
}
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.SearchField {
|
||||||
|
button {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text {
|
||||||
|
color: var(--text);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
color: var(--text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isError {
|
||||||
|
outline: red 1px solid;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.Error_color {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
input:-webkit-autofill {
|
||||||
|
-webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */
|
||||||
|
}
|
||||||
|
input:-webkit-autofill:focus {
|
||||||
|
-webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove autofill background color on hover */
|
||||||
|
input:-webkit-autofill:hover {
|
||||||
|
-webkit-box-shadow: 0 0 0 1000px white inset !important; /* Change the color to your desired background color */
|
||||||
|
}
|
||||||
|
.upload_image_button {
|
||||||
|
.ant-btn {
|
||||||
|
min-height: 3vw !important;
|
||||||
|
border: 0.1vw solid var(--border-color);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-select-outlined:not(.ant-select-customize-input) .ant-select-selector {
|
||||||
|
min-height: 3vw !important;
|
||||||
|
}
|
||||||
|
.ant-select-multiple.ant-select-lg .ant-select-selection-overflow {
|
||||||
|
min-height: 3vw !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-list .ant-upload-list-item {
|
||||||
|
// height:3vw !important;
|
||||||
|
border: 1px solid var(--border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TowValidationItems {
|
||||||
|
display: flex;
|
||||||
|
gap: 3%;
|
||||||
|
> label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select .ant-select-arrow {
|
||||||
|
inset-inline-end: 1vw;
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper-lg {
|
||||||
|
padding: 0.5vw 1vw;
|
||||||
|
font-size: 1vw;
|
||||||
|
min-height: 3vw;
|
||||||
|
border-radius: 0.6vw;
|
||||||
|
border: 1.5px solid var(--opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-picker-outlined {
|
||||||
|
padding: 0.5vw 1vw;
|
||||||
|
font-size: 1vw;
|
||||||
|
min-height: 3vw;
|
||||||
|
border-radius: 0.6vw;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-single.ant-select-lg .ant-select-selector {
|
||||||
|
min-height: 3vw;
|
||||||
|
border-radius: 0.6vw;
|
||||||
|
}
|
||||||
|
.ant-select-single .ant-select-selector .ant-select-selection-search-input {
|
||||||
|
min-height: 3vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-outlined .ant-select-selector {
|
||||||
|
min-height: 3vw !important;
|
||||||
|
border-radius: 0.6vw !important;
|
||||||
|
}
|
||||||
|
.ant-select-single.ant-select-lg {
|
||||||
|
min-height: 3vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-wrapper .ant-upload-list .ant-upload-list-item {
|
||||||
|
width: 21.5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-number-outlined {
|
||||||
|
width: 100%;
|
||||||
|
height: 3vw;
|
||||||
|
}
|
||||||
|
.ant-input-number-lg input.ant-input-number-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 3vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bigRow {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
> *.w-100 {
|
||||||
|
width: 48% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-number-affix-wrapper-lg {
|
||||||
|
width: 100%;
|
||||||
|
height: 3vw;
|
||||||
|
border-radius: 0.6vw;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.TwoSelectGroup {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.TwoSelectGroupbutton {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
margin-top: 25px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add_new_button {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
svg {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ValidationField:has(.input_number) {
|
||||||
|
max-width: 100px;
|
||||||
|
|
||||||
|
.input_number {
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
max-width: 80% !important;
|
||||||
|
}
|
||||||