fist push
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://repos.point-dev.net/Karimaldeen/school-dashboard.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/Logo.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/App/Logo.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/App/Logo.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<meta
|
||||
name="description"
|
||||
content="social networking platform with automated content moderation and context-based authentication system"
|
||||
/>
|
||||
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
|
||||
<title>NERD_DASHBOARD</title>
|
||||
</head>
|
||||
<body dir="rtl">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
21054
package-lock.json
generated
Normal file
78
package.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"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-dom": "^18.3.1",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
"reactstrap": "^9.2.2",
|
||||
"sass": "^1.77.4",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
10452
pnpm-lock.yaml
Normal file
BIN
public/App/Logo.png
Normal file
|
After Width: | Height: | Size: 13 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/warning.png
Normal file
|
After Width: | Height: | Size: 1.5 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;
|
||||
66
src/Components/Columns/ColumnsImage.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import {
|
||||
RotateLeftOutlined,
|
||||
RotateRightOutlined,
|
||||
SwapOutlined,
|
||||
ZoomInOutlined,
|
||||
ZoomOutOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import { Image, Space } from "antd";
|
||||
import useImageError from "../../Hooks/useImageError";
|
||||
import { ErrorImage } from "../../Layout/app/Const";
|
||||
|
||||
const ColumnsImage = ({ src }: any) => {
|
||||
const imageUrl = src || ErrorImage;
|
||||
|
||||
const handleError = useImageError;
|
||||
|
||||
|
||||
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;
|
||||
91
src/Components/DataTable/SearchFieldWithSelect.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { IoSearch } from "react-icons/io5";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
options: Option[];
|
||||
placeholder: string;
|
||||
onSelect?: (option: Option) => void;
|
||||
}
|
||||
|
||||
const SearchFieldWithSelect: React.FC<Props> = ({
|
||||
options,
|
||||
placeholder,
|
||||
onSelect,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<Option | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Add event listener when component mounts
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
// Remove event listener when component unmounts
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
// Close the dropdown if clicked outside the component
|
||||
if (node.current && !node.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
const navigate = useNavigate();
|
||||
const handleOptionClick = (option: Option) => {
|
||||
setSelectedOption(option);
|
||||
setIsOpen(false);
|
||||
onSelect && onSelect(option);
|
||||
|
||||
navigate(option?.value as string);
|
||||
};
|
||||
|
||||
const filteredOptions = options.filter((option) =>
|
||||
option.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={node} className={`search-field ${isOpen ? "open" : ""}`}>
|
||||
<div className="search-header" onClick={toggleDropdown}>
|
||||
<IoSearch className="search__icon" />
|
||||
<input
|
||||
type="text"
|
||||
className="search__input"
|
||||
placeholder={selectedOption ? selectedOption.label : placeholder}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
{/* <IoMdArrowDropdown className={`search_select_icon ${isOpen ? 'open' : ''}`} /> */}
|
||||
</div>
|
||||
{(isOpen || (searchTerm !== "" && filteredOptions.length > 0)) && (
|
||||
<div className="search-options">
|
||||
<div className="search-options-list">
|
||||
{filteredOptions.map((option) => (
|
||||
<div
|
||||
key={option.value}
|
||||
className="search-option"
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
<div>{option.label}</div>
|
||||
<IoSearch className="search__icon" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchFieldWithSelect;
|
||||
37
src/Components/DataTable/SmallFillterNav.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import SearchField from "./SearchFieldWithSelect";
|
||||
import { IoSearch } from "react-icons/io5";
|
||||
import { Select } from "antd";
|
||||
import { IoMdArrowDropdown } from "react-icons/io";
|
||||
import { FaArrowRight, FaPlus } from "react-icons/fa";
|
||||
import { useModalState } from "../../zustand/Modal";
|
||||
|
||||
const SmallFillterNav = () => {
|
||||
const OptionsData = [
|
||||
{
|
||||
value: 1,
|
||||
label: "Jack",
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: "Lucy",
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: "Tom",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="SmallFillterNav">
|
||||
<header>
|
||||
<FaArrowRight /> نشاطات الطالب <span>( آية العمري ) </span>
|
||||
</header>
|
||||
<SearchField
|
||||
options={OptionsData}
|
||||
placeholder={"practical.search_here"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallFillterNav;
|
||||
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} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
58
src/Components/Layout/Navbar/NavBarRightSide.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
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";
|
||||
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>
|
||||
<div className="header_search">
|
||||
<SearchFieldWithSelect
|
||||
options={translateArray}
|
||||
placeholder={t("practical.search_here")}
|
||||
/>
|
||||
</div>
|
||||
<span className="header_icons">
|
||||
<div>
|
||||
<Image src="/Icon/bell.png" alt="Notifications" />
|
||||
</div>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
<div onClick={handleEdit}>
|
||||
{t("header.change_your_current_password")}
|
||||
</div>
|
||||
}
|
||||
color="#E0E0E0"
|
||||
>
|
||||
<div className="gear">
|
||||
<Image src="/Icon/gear.png" alt="Settings" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<div className="header_profile">
|
||||
<span>
|
||||
<h6>{userData?.username}</h6>
|
||||
<p>{userData?.type}</p>
|
||||
</span>
|
||||
<Image src="/Layout/DefaultStudentImage.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;
|
||||
63
src/Components/Layout/SideBar/MenuItem.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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 }: any) => {
|
||||
const isActive = location.pathname.split("/")[1] === item.path?.slice(1);
|
||||
// console.log(location.pathname.split("/")[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>
|
||||
<Link to={item.path || "/"}>{t(item.text)}</Link>
|
||||
{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, index: any) => (
|
||||
<MenuItem
|
||||
key={index}
|
||||
item={childItem}
|
||||
location={location}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</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;
|
||||
64
src/Components/Table/ActionButtons.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
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";
|
||||
|
||||
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">
|
||||
<RiDeleteBin6Fill
|
||||
onClick={onDelete}
|
||||
size={22}
|
||||
style={{ color: "#C11313" }}
|
||||
/>
|
||||
// </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;
|
||||
81
src/Components/ValidationField/View/File.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
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,
|
||||
placholder,
|
||||
className,
|
||||
props,
|
||||
}: 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={<UploadOutlined />}
|
||||
>
|
||||
{placholder ?? 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,
|
||||
};
|
||||
227
src/Components/ValidationField/utils/ValidationField.scss
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
11
src/Components/ValidationField/utils/ValidationState.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
interface ValidationState {
|
||||
Validation: any[];
|
||||
setValidation: (value: any[]) => void;
|
||||
}
|
||||
|
||||
export const useValidationState = create<ValidationState>((set) => ({
|
||||
Validation: [],
|
||||
setValidation: (value) => set((state) => ({ Validation: value })),
|
||||
}));
|
||||
7
src/Components/ValidationField/utils/getNestedValue.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export function getNestedValue(obj: any, path: any) {
|
||||
return path
|
||||
.replace(/\?.\[|\]\[|\]\.?/g, ".") // Replace question mark and square brackets
|
||||
.split(".") // Split by dots
|
||||
.filter(Boolean) // Remove empty strings
|
||||
.reduce((acc: any, key: any) => acc && acc[key], obj); // Access nested properties
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export const translateOptions = (options: any, t: any) => {
|
||||
return options.map((opt: any) => ({
|
||||
...opt,
|
||||
label: t(`${opt.label}`),
|
||||
}));
|
||||
};
|
||||
194
src/Components/ValidationField/utils/types.ts
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import { InputProps } from "antd";
|
||||
|
||||
export type ValidationFieldType =
|
||||
| "text"
|
||||
| "Select"
|
||||
| "LocalSearch"
|
||||
| "Search"
|
||||
| "DataRange"
|
||||
| "Date"
|
||||
| "Time"
|
||||
| "File"
|
||||
| "MaltyFile"
|
||||
| "DropFile"
|
||||
| "Checkbox"
|
||||
| "number"
|
||||
| "password"
|
||||
| "email"
|
||||
| "TextArea";
|
||||
|
||||
export interface ValidationFieldPropsText {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "text";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
}
|
||||
|
||||
export interface ValidationFieldPropsSelect {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "Select";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: any;
|
||||
dir?: "ltr" | "rtl";
|
||||
option: any[];
|
||||
isMulti?: boolean;
|
||||
}
|
||||
|
||||
export interface ValidationFieldPropsLocalSearch {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "LocalSearch";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
option: any[];
|
||||
isMulti?: boolean;
|
||||
}
|
||||
export interface ValidationFieldPropsSearch {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "Search";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
option: any[];
|
||||
isMulti?: boolean;
|
||||
searchBy: string;
|
||||
isLoading?: any;
|
||||
}
|
||||
export interface ValidationFieldPropsDataRange {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "DataRange";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
Format?: "YYYY/MM/DD" | "MM/DD" | "YYYY/MM" | "YYYY-MM-DD HH:mm:ss.SSS";
|
||||
}
|
||||
export interface ValidationFieldPropsDate {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "Date";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
picker?: "data" | "week" | "month" | "quarter" | "year";
|
||||
}
|
||||
|
||||
export interface ValidationFieldPropsTime {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "Time";
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
}
|
||||
|
||||
export interface ValidationFieldPropsFile {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "File" | "MaltyFile" | "DropFile";
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
}
|
||||
export interface ValidationFieldPropsCheckbox {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type: "Checkbox";
|
||||
label?: string;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
Group?: boolean;
|
||||
}
|
||||
export interface ValidationFieldPropstext {
|
||||
name: string;
|
||||
no_label?: boolean;
|
||||
label_icon?: boolean;
|
||||
type?:
|
||||
| "text"
|
||||
| "number"
|
||||
| "password"
|
||||
| "email"
|
||||
| "TextArea"
|
||||
| "NumberFormate";
|
||||
label?: string;
|
||||
label2?: string;
|
||||
className?: string;
|
||||
placeholder?: string;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (value: any) => void;
|
||||
dir?: "ltr" | "rtl";
|
||||
Group?: boolean;
|
||||
[key: string]: any; // Index signature to allow any additional props
|
||||
}
|
||||
|
||||
///// new
|
||||
export interface BaseField {
|
||||
name: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
export type OmitBaseType = "placeholder" | "name" | "label" | "type";
|
||||
|
||||
export type OmitPicker = OmitBaseType | "format";
|
||||
|
||||
export interface ValidationFieldPropsInput
|
||||
extends Omit<InputProps, OmitBaseType>,
|
||||
BaseField {
|
||||
type: "text" | "number" | "password" | "email" | "Number";
|
||||
isDisabled?: boolean;
|
||||
no_label?: string;
|
||||
label_icon?: string;
|
||||
label2?: string;
|
||||
}
|
||||
|
||||
export type ValidationFieldProps =
|
||||
| ValidationFieldPropsInput
|
||||
| ValidationFieldPropsSelect
|
||||
| ValidationFieldPropsLocalSearch
|
||||
| ValidationFieldPropsDataRange
|
||||
| ValidationFieldPropsDate
|
||||
| ValidationFieldPropsTime
|
||||
| ValidationFieldPropsFile
|
||||
| ValidationFieldPropsCheckbox
|
||||
| ValidationFieldPropstext
|
||||
| ValidationFieldPropsSearch;
|
||||
42
src/Components/exercise/Header.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { useFormikContext } from "formik";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GoArrowSwitch } from "react-icons/go";
|
||||
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
|
||||
import { QUESTION_OBJECT_KEY } from "../../config/AppKey";
|
||||
|
||||
const Header = () => {
|
||||
const [t] = useTranslation();
|
||||
const { values, setFieldValue, setValues } = useFormikContext<any>();
|
||||
const { isBseQuestion, setIsBseQuestion } = useObjectToEdit();
|
||||
const { setSavedQuestionData } = useObjectToEdit();
|
||||
const handleChange = () => {
|
||||
setSavedQuestionData(null);
|
||||
localStorage.removeItem(QUESTION_OBJECT_KEY);
|
||||
if (isBseQuestion) {
|
||||
setIsBseQuestion(false);
|
||||
setValues(null);
|
||||
setFieldValue("isBase", 0);
|
||||
} else {
|
||||
setIsBseQuestion(true);
|
||||
setValues(null);
|
||||
setFieldValue("isBase", 1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="exercise_add_header mb-4">
|
||||
<div>
|
||||
{t("practical.add")} {t("models.exercise")}{" "}
|
||||
</div>
|
||||
<div>
|
||||
<GoArrowSwitch onClick={handleChange} className="m-2" />
|
||||
{isBseQuestion || values?.isBase === 1
|
||||
? t("header.malty_exercise")
|
||||
: t("header.exercise")}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
34
src/Components/models/ModelButtons.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
|
||||
import { useModalState } from "../../zustand/Modal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Spin } from "antd";
|
||||
|
||||
interface ButtonsProps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const ModelButtons: React.FC<ButtonsProps> = ({ isLoading }) => {
|
||||
const { setIsOpen } = useModalState((state) => state);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsOpen("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="buttons">
|
||||
<div onClick={handleCancel}>{t("practical.back")}</div>
|
||||
<button disabled={isLoading} type="submit">
|
||||
{t("practical.submit")}
|
||||
{isLoading && (
|
||||
<span className="Spinier_Div">
|
||||
<Spin />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelButtons;
|
||||