fist push

This commit is contained in:
karimalden 2024-08-13 15:06:10 +03:00
commit e8cc73ecc3
365 changed files with 50118 additions and 0 deletions

23
.gitignore vendored Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

78
package.json Normal file
View 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

File diff suppressed because it is too large Load Diff

BIN
public/App/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/App/SyriaLogo.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

17
public/Icon/Add.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/Icon/Error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/Icon/Flash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

BIN
public/Icon/bell.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

BIN
public/Icon/cash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
public/Icon/chart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

BIN
public/Icon/gear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/Icon/medal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
public/Icon/warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/Image/user_fake.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

27
public/site.webmanifest Normal file
View 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
View 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;

View 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} />
// )
// }
// },

View 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,
};

View 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;

View 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;

View 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;
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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} />
)}
</>
);
};

View 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;

View 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;

View 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>
)}
</>
);
};

View File

@ -0,0 +1,11 @@
import { Spin } from "antd";
const SpinContainer = () => {
return (
<div className="SpinContainer">
<Spin />
</div>
);
};
export default SpinContainer;

View 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;

View 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;

View 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;

View 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>
);

View 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>
);
});

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;
}
}

View 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;

View 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;
}
}

View 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;

View 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;
}
}

View 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;

View 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;

View 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;
}
}

View 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;

View 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;

View 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;

View 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;

View 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;

View 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);

View 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;

View 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;

View 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);

View 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;

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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;

View 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,
};

View 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,
};

View 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;
}

View 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 })),
}));

View 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
}

View File

@ -0,0 +1,6 @@
export const translateOptions = (options: any, t: any) => {
return options.map((opt: any) => ({
...opt,
label: t(`${opt.label}`),
}));
};

View 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;

View 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;

View 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;

Some files were not shown because too many files have changed in this diff Show More