Compare commits

...

5 Commits
main ... dev

Author SHA1 Message Date
karimaldeen
a5f54f1794 add protected route 2024-08-21 17:30:31 +03:00
karimaldeen
ee5a3b7b14 end drag and drop 2024-08-21 15:23:33 +03:00
karimaldeen
8f993912fc end seller page 2024-08-19 15:13:54 +03:00
karimaldeen
7e8fa6431c add package item 2024-08-18 16:07:34 +03:00
karimaldeen
5e0e0afd01 end package 2024-08-18 15:25:56 +03:00
114 changed files with 6724 additions and 6553 deletions

View File

@ -10,6 +10,7 @@
"Groupbutton", "Groupbutton",
"handelnavigate", "handelnavigate",
"Karim", "Karim",
"latlng",
"queryqlent", "queryqlent",
"registraion", "registraion",
"SENDNOTIFICATION", "SENDNOTIFICATION",

View File

@ -18,6 +18,7 @@
"formik": "^2.4.6", "formik": "^2.4.6",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.11.5", "i18next": "^23.11.5",
"leaflet": "^1.9.4",
"path-to-regexp": "^6.2.2", "path-to-regexp": "^6.2.2",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"react": "^18.3.1", "react": "^18.3.1",
@ -26,6 +27,7 @@
"react-dragula": "^1.1.17", "react-dragula": "^1.1.17",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-leaflet": "^4.2.1",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
@ -66,6 +68,7 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/leaflet": "^1.9.12",
"@types/node": "^20.14.0", "@types/node": "^20.14.0",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,13 @@ import { Spin } from "antd";
import { hasAbility } from "./utils/hasAbility"; import { hasAbility } from "./utils/hasAbility";
import { renderRoutesRecursively } from "./Components/Routes/RenderRoutesRecursively"; import { renderRoutesRecursively } from "./Components/Routes/RenderRoutesRecursively";
import { RenderRouteElement } from "./Components/Routes/RenderRouteElement"; import { RenderRouteElement } from "./Components/Routes/RenderRouteElement";
import { UserTypeEnum } from "./enums/UserType";
import { RoleByType } from "./utils/RoleByType";
const Page404 = lazy(() => import("./Layout/Ui/NotFoundPage")); const Page404 = lazy(() => import("./Layout/Ui/NotFoundPage"));
const Auth = lazy(() => import("./Pages/Auth/Page")); const Auth = lazy(() => import("./Pages/Auth/Page"));
const App = () => { const App = () => {
return ( return (
<Routes> <Routes>
<Route <Route
@ -31,10 +33,14 @@ const App = () => {
} }
/> />
{renderRoutesRecursively(menuItems)} {renderRoutesRecursively(menuItems)}
{CrudRoute.map((route) => { {CrudRoute.map((route) => {
const useAbility = hasAbility(route.abilities, route.abilities_value); const useAbility = hasAbility(route.abilities, route.abilities_value);
if(!RoleByType(route)){
return false ;
}
if (!useAbility) { if (!useAbility) {
return false; return false;
} }

View File

@ -3,10 +3,17 @@ import { TMenuItem } from "../../types/App";
import { hasAbility } from "../../utils/hasAbility"; import { hasAbility } from "../../utils/hasAbility";
import { Route } from "react-router-dom"; import { Route } from "react-router-dom";
import { RenderRouteElement } from "./RenderRouteElement"; import { RenderRouteElement } from "./RenderRouteElement";
import { UserTypeEnum } from "../../enums/UserType";
import Item from "antd/es/list/Item";
import { RoleByType } from "../../utils/RoleByType";
export const renderRoutesRecursively = (routes: TMenuItem[]) => export const renderRoutesRecursively = (routes: TMenuItem[]) =>
routes.map((route: TMenuItem) => { routes.map((route: TMenuItem) => {
const useAbility = hasAbility(route.abilities, route.abilities_value); const useAbility = hasAbility(route.abilities, route.abilities_value);
if(!RoleByType(route)){
return false ;
}
if (!useAbility) { if (!useAbility) {
return false; return false;
} }

View File

@ -1,24 +0,0 @@
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

@ -1,37 +0,0 @@
.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

@ -1,45 +0,0 @@
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

@ -6,17 +6,16 @@ import {
File, File,
DataRange, DataRange,
SelectField, SelectField,
Default,
CheckboxField, CheckboxField,
MaltyFile, MaltyFile,
SearchField, SearchField,
TextField, TextField,
DropFile, DropFile,
Default,
} from "./View"; } from "./View";
import { ValidationFieldProps, ValidationFieldType } from "./utils/types"; import { ValidationFieldProps, ValidationFieldType } from "./utils/types";
import LocalSearchField from "./View/LocalSearch"; import LocalSearchField from "./View/LocalSearch";
import NumberFormate from "./View/NumberFormate"; import NumberFormate from "./View/NumberFormate";
import NumberField from "./View/NumberField";
const components: { [key: string]: React.FC<any> } = { const components: { [key: string]: React.FC<any> } = {
Select: SelectField, Select: SelectField,
@ -31,12 +30,11 @@ const components: { [key: string]: React.FC<any> } = {
MaltyFile: MaltyFile, MaltyFile: MaltyFile,
Checkbox: CheckboxField, Checkbox: CheckboxField,
NumberFormate: NumberFormate, NumberFormate: NumberFormate,
Number: NumberField,
}; };
const ValidationField: React.FC<ValidationFieldProps> = React.memo( const ValidationField: React.FC<ValidationFieldProps> = React.memo(
({ type, ...otherProps }: any) => { ({ type = "text", ...otherProps }) => {
const Component = components[type as ValidationFieldType]; const Component = components[type ?? ("text" as ValidationFieldType)];
if (!Component) { if (!Component) {
return <Default {...otherProps} />; return <Default {...otherProps} />;

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { Checkbox, Form } from "antd"; import { Checkbox } from "antd";
import { getNestedValue } from "../utils/getNestedValue"; import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const CheckboxField = ({ const CheckboxField = ({
name, name,
label, label,
@ -13,25 +13,21 @@ const CheckboxField = ({
}: any) => { }: any) => {
const { t, formik, isError, errorMsg } = useFormField(name, props); const { t, formik, isError, errorMsg } = useFormField(name, props);
const CheckboxhandleChange = (value: any) => { const CheckboxhandleChange = (value: any) => {
formik.setFieldValue(name, value?.target?.checked ? 1 : 0); formik.setFieldValue(name, value?.target?.checked);
}; };
return ( return (
<div className={Group ? "d-inline mt-3 Checkboxs" : ``}> <div className={Group ? "d-inline mt-3 Checkbox" : ``}>
<Form.Item <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Checkbox <Checkbox
onChange={onChange || CheckboxhandleChange} onChange={onChange || CheckboxhandleChange}
disabled={isDisabled} disabled={isDisabled}
checked={formik.values?.[name] === 1} checked={formik.values?.[name] ?? false}
className={className} className={className}
> >
{t(`input.${label ? label : name}`)} {t(`input.${label ? label : name}`)}
</Checkbox> </Checkbox>
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -3,6 +3,8 @@ import { Form, DatePicker } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md"; import { MdOutlineEdit } from "react-icons/md";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@ -24,28 +26,16 @@ const DataRange = ({
}; };
return ( return (
<div className="ValidationField w-100 "> <div className="ValidationField w-100 ">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{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 <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<RangePicker <RangePicker
placeholder={placeholder} placeholder={placeholder}
size="large" size="large"
@ -55,8 +45,9 @@ const DataRange = ({
onChange={onChange || onCalendarChange} onChange={onChange || onCalendarChange}
disabled={isDisabled} disabled={isDisabled}
defaultValue={formik.values[name]} defaultValue={formik.values[name]}
id={name}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -3,6 +3,9 @@ import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md"; import { MdOutlineEdit } from "react-icons/md";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { DateEnum } from "../../../enums/Date";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const Date = ({ const Date = ({
name, name,
@ -21,34 +24,21 @@ const Date = ({
const FormikValue = formik.values[name]; const FormikValue = formik.values[name];
const onCalendarChange = (value: any) => { const onCalendarChange = (value: any) => {
formik.setFieldValue(name, value); formik.setFieldValue(name, value);
// console.log(value,"value ");
}; };
const Formater = "YYYY/MM/DD"; const Formatter = [DateEnum?.FORMATE];
return ( return (
<div className="ValidationField w-100 "> <div className="ValidationField w-100 ">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{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 <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<DatePicker <DatePicker
picker={picker} picker={picker}
placeholder={t(`input.${placeholder}`)} placeholder={t(`input.${placeholder}`)}
@ -58,10 +48,11 @@ const Date = ({
size="large" size="large"
onChange={onChange || onCalendarChange} onChange={onChange || onCalendarChange}
disabled={isDisabled} disabled={isDisabled}
format={Formater} format={Formatter}
id={name}
/> />
{/* <DatePicker onChange={onChange} /> */} {/* <DatePicker onChange={onChange} /> */}
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -1,9 +1,10 @@
import { Form, Input } from "antd"; import { Input } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik"; import { Field } from "formik";
import { ValidationFieldPropsInput } from "../utils/types"; import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
import { FieldProps } from "../utils/types";
const Default = ({ const Default = ({
name, name,
@ -14,52 +15,34 @@ const Default = ({
type, type,
no_label, no_label,
label_icon, label_icon,
label2,
...props ...props
}: ValidationFieldPropsInput) => { }: any) => {
const { errorMsg, isError, t } = useFormField(name, props); const { errorMsg, isError, t } = useFormField(name, props);
return ( return (
<div className="ValidationField w-100"> <div className="ValidationField w-100">
{label2 ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
{label2} label={label}
</label> label_icon={label_icon}
) : no_label ? ( no_label={no_label}
<label htmlFor={name} className="text"> placeholder={placeholder}
<span>empty</span> t={t}
</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 <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field <Field
as={Input} as={Input}
type={type ?? "text"} type={type ?? "text"}
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`,
)}
name={name} name={name}
id={name}
disabled={isDisabled} disabled={isDisabled}
size="large" size="large"
{...(type === "number" && { min: 0 })} {...(type === "number" && { min: 0 })}
{...props} {...props}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -3,7 +3,6 @@ import { LoadingOutlined, PlusOutlined } from "@ant-design/icons";
import { message, Upload } from "antd"; import { message, Upload } from "antd";
import type { GetProp, UploadProps } from "antd"; import type { GetProp, UploadProps } from "antd";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { ImageBaseURL } from "../../../api/config";
type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0]; type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
@ -12,7 +11,7 @@ const DropFile = ({
label, label,
onChange, onChange,
isDisabled, isDisabled,
placholder, placeholder,
className, className,
props, props,
no_label, no_label,
@ -23,7 +22,7 @@ const DropFile = ({
const FormikValue = const FormikValue =
typeof FormikName === "string" typeof FormikName === "string"
? ImageBaseURL + FormikName ? FormikName
: FormikName instanceof File : FormikName instanceof File
? URL.createObjectURL(FormikName) ? URL.createObjectURL(FormikName)
: ""; : "";
@ -71,6 +70,7 @@ const DropFile = ({
showUploadList={false} showUploadList={false}
customRequest={customRequest} customRequest={customRequest}
onChange={onChange || handleChange} onChange={onChange || handleChange}
id={name}
> >
{imageUrl ? ( {imageUrl ? (
<img <img

View File

@ -1,14 +1,14 @@
import { Button, Upload, UploadFile } from "antd"; import { Button, Upload, UploadFile } from "antd";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { UploadOutlined } from "@ant-design/icons"; import { UploadOutlined } from "@ant-design/icons";
import { useMemo } from "react"; import React, { useMemo } from "react";
const File = ({ const File = ({
name, name,
label, label,
onChange, onChange,
isDisabled, isDisabled,
placholder, placeholder,
className, className,
props, props,
}: any) => { }: any) => {
@ -16,29 +16,29 @@ const File = ({
let imageUrl = formik?.values?.[name] ?? null; let imageUrl = formik?.values?.[name] ?? null;
const fileList: UploadFile[] = useMemo(() => { const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return []; if (!imageUrl) {
return [];
}
return [ return [
typeof imageUrl === "string" typeof imageUrl === "string"
? { ? {
uid: "-1", uid: "-1",
name: "uploaded-image", name: "",
status: "done", status: "done",
url: imageUrl, url: imageUrl,
thumbUrl: imageUrl, thumbUrl: imageUrl,
} }
: { : {
uid: imageUrl.uid || "-1", uid: imageUrl.uid || "-1",
name: imageUrl.name || "uploaded-image", name: imageUrl.name || "",
status: "done", status: "done",
originFileObj: imageUrl, originFileObj: imageUrl,
}, },
]; ];
}, [imageUrl]); }, [imageUrl]);
// console.log(1);
const FilehandleChange = (value: any) => { const FilehandleChange = (value: any) => {
// console.log(value,"filevalue");
if (value.fileList.length === 0) { if (value.fileList.length === 0) {
formik.setFieldValue(name, null); formik.setFieldValue(name, null);
} else { } else {
@ -62,12 +62,15 @@ const File = ({
onChange={onChange || FilehandleChange} onChange={onChange || FilehandleChange}
customRequest={customRequest} customRequest={customRequest}
className={` w-100`} className={` w-100`}
id={name}
> >
<Button <Button
className={isError ? "isError w-100 " : " w-100"} className={isError ? "isError w-100 " : " w-100"}
icon={<UploadOutlined />} icon={<UploadOutlined />}
> >
{placholder ?? t("input.Click_to_upload_the_image")} {placeholder
? t(`input.${placeholder}`)
: t("input.Click_to_upload_the_image")}
</Button> </Button>
<div className="Error_color"> {isError ? "required" : ""}</div> <div className="Error_color"> {isError ? "required" : ""}</div>
{errorMsg} {errorMsg}
@ -76,4 +79,4 @@ const File = ({
); );
}; };
export default File; export default React.memo(File);

View File

@ -1,8 +1,9 @@
import { Form, Select } from "antd"; import { Select } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { translateOptions } from "../utils/translatedOptions"; import { translateOptions } from "../utils/translatedOptions";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const LocalSelectField = ({ const LocalSelectField = ({
name, name,
@ -13,9 +14,9 @@ const LocalSelectField = ({
isMulti, isMulti,
onChange, onChange,
className, className,
props,
no_label, no_label,
label_icon, label_icon,
...props
}: any) => { }: any) => {
const { errorMsg, isError, t, formik } = useFormField(name, props); const { errorMsg, isError, t, formik } = useFormField(name, props);
@ -29,13 +30,6 @@ const LocalSelectField = ({
option?.label?.toString().toLowerCase().includes(input.toLowerCase()) || option?.label?.toString().toLowerCase().includes(input.toLowerCase()) ||
option?.value?.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) => { const handleSelectChange = (value: any) => {
formik.setFieldValue(name, value); formik.setFieldValue(name, value);
if (onChange) onChange(value); if (onChange) onChange(value);
@ -47,35 +41,21 @@ const LocalSelectField = ({
return ( return (
<div className="ValidationField w-100"> <div className="ValidationField w-100">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{t(`input.${label ? label : name}`)} />
</label> <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
<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 <Select
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`,
)}
disabled={isDisabled} disabled={isDisabled}
options={translateOptions(option, t)} options={translateOptions(option, t)}
size="large" size="large"
className={`${className} ${isError ? "Select_error" : ""} w-100`} className={`${className} ${isError ? "SelectError" : ""} w-100`}
value={formik.values[name]} value={formik.values[name]}
allowClear allowClear
{...(isMulti && { mode: "multiple" })} {...(isMulti && { mode: "multiple" })}
@ -84,8 +64,11 @@ const LocalSelectField = ({
filterOption={handleSearch} // Custom filter function filterOption={handleSearch} // Custom filter function
searchValue={searchValue} // Control the search input value searchValue={searchValue} // Control the search input value
onSearch={handleSearchChange} // Update search input value on change onSearch={handleSearchChange} // Update search input value on change
id={name}
fieldNames={{ label: "name", value: "id" }}
{...props}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { useMemo } from "react";
import { Button, Upload } from "antd"; import { Button, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons"; import { UploadOutlined } from "@ant-design/icons";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
const MaltyFile = ({ const MaltyFile = ({
@ -15,28 +15,28 @@ const MaltyFile = ({
const { formik, t, isError } = useFormField(name, props); const { formik, t, isError } = useFormField(name, props);
let imageUrl = formik?.values?.[name] ?? null; let imageUrl = formik?.values?.[name] ?? null;
// Mapping formik values to fileList format // Memoizing the fileList to prevent unnecessary recalculations
const fileList = imageUrl const fileList = useMemo(() => {
? imageUrl.map((file: any, index: number) => { return imageUrl
// console.log(file,"file"); ? imageUrl.map((file: any, index: number) => {
return file instanceof File
return file instanceof File ? {
? { uid: index,
uid: index, name: file?.name,
name: file?.name, status: "done",
status: "done", originFileObj: file,
originFileObj: file, }
} : {
: { uid: index,
uid: index, id: file?.id,
id: file?.id, name: file?.name,
name: file?.name, status: "done",
status: "done", url: file?.url || "",
url: file?.url || "", thumbUrl: file?.url || "",
thumbUrl: file?.url || "", };
}; })
}) : [];
: []; }, [imageUrl]); // Dependency array ensures it recalculates only when imageUrl changes
const FilehandleChange = ({ fileList }: any) => { const FilehandleChange = ({ fileList }: any) => {
if (fileList.length === 0) { if (fileList.length === 0) {
@ -48,6 +48,7 @@ const MaltyFile = ({
); );
} }
}; };
// Custom request function // Custom request function
const customRequest = async ({ onSuccess }: any) => { const customRequest = async ({ onSuccess }: any) => {
// Perform any necessary actions before onSuccess is called // Perform any necessary actions before onSuccess is called
@ -63,19 +64,20 @@ const MaltyFile = ({
<Upload <Upload
disabled={isDisabled} disabled={isDisabled}
listType="picture" listType="picture"
fileList={fileList} // Using fileList instead of defaultFileList fileList={fileList} // Using memoized fileList
onChange={onChange || FilehandleChange} onChange={onChange || FilehandleChange}
customRequest={customRequest} customRequest={customRequest}
className={`${className} w-100`} className={`${className} w-100`}
multiple // Allow multiple files to be selected multiple // Allow multiple files to be selected
id={name}
> >
<Button <Button
className={isError ? "isError w-100" : " w-100"} className={isError ? "isError w-100" : "w-100"}
icon={<UploadOutlined />} icon={<UploadOutlined />}
> >
{t(`input.` + placeholder) ?? t("input.upload_image")} {t(`input.` + placeholder) ?? t("input.upload_image")}
</Button> </Button>
<div className="Error_color"> {isError ? "required" : ""}</div> <div className="Error_color">{isError ? "required" : ""}</div>
</Upload> </Upload>
</div> </div>
); );

View File

@ -1,70 +0,0 @@
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

@ -1,18 +1,19 @@
import { Form, Input, InputNumber } from "antd"; import { InputNumber } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik"; import { Field } from "formik";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const NumberFormate = ({ const NumberFormate = ({
name, name,
label, label,
placeholder, placeholder,
isDisabled, isDisabled,
props,
type, type,
no_label, no_label,
label_icon, label_icon,
...props
}: any) => { }: any) => {
const { errorMsg, isError, t, formik } = useFormField(name, props); const { errorMsg, isError, t, formik } = useFormField(name, props);
const SelectableChange = (value: { const SelectableChange = (value: {
@ -23,28 +24,15 @@ const NumberFormate = ({
}; };
return ( return (
<div className="ValidationField w-100"> <div className="ValidationField w-100">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{t(`input.${label ? label : name}`)} />
</label> <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
<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 <Field
as={InputNumber} as={InputNumber}
formatter={(value: any) => formatter={(value: any) =>
@ -57,16 +45,16 @@ const NumberFormate = ({
type={type ?? "text"} type={type ?? "text"}
value={formik.values[name]} value={formik.values[name]}
onChange={SelectableChange} onChange={SelectableChange}
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`,
)}
name={name} name={name}
disabled={isDisabled} disabled={isDisabled}
size="large" size="large"
id={name}
{...props}
// onChange={onChange ? onChange : handleChange} // onChange={onChange ? onChange : handleChange}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -1,87 +1,143 @@
import { Form, Select, Spin } from "antd"; import { Select, Spin } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { useNavigate } from "react-router-dom"; import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { MdOutlineEdit } from "react-icons/md"; import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
import { SearchFieldProps } from "../utils/types";
import { useValidationValidationParamState } from "../state/ValidationValidationParamState";
import { useDebounce } from "../../../Hooks/useDebounce";
const SearchField = ({ const SearchField = ({
name, name,
label, label,
placeholder, placeholder,
isDisabled, isDisabled,
searchBy, searchBy = "search",
option, option = [],
isMulti, isMulti,
onChange, onChange,
className, className,
props,
no_label, no_label,
label_icon, label_icon,
isLoading, isLoading,
}: any) => { canChangePage,
PageName,
page,
...props
}: SearchFieldProps) => {
const { errorMsg, isError, t, formik } = useFormField(name, props); const { errorMsg, isError, t, formik } = useFormField(name, props);
const [searchQuery, setSearchQuery] = useState<string>(""); const { pushValidationParamState, setValidationParamState } =
const navigate = useNavigate(); useValidationValidationParamState();
useEffect(() => {
const searchParams = new URLSearchParams(window?.location?.search);
setSearchQuery(searchParams?.get("search") || "");
}, []);
const SelectableChange = (value: { const [AllPagesOption, setAllPagesOption] = useState<any>([]);
value: string;
label: React.ReactNode; useEffect(() => {
}) => { if (option?.length > 0) {
const NewOption = [...option, ...AllPagesOption];
const FilteredOption = NewOption.filter(
(value, index, self) =>
index === self.findIndex((t) => t.id === value.id),
);
const sortedNewOption = FilteredOption.sort((a, b) => a.id - b.id);
setAllPagesOption(sortedNewOption);
}
}, [option]);
useEffect(() => {
if (page === 1) {
setAllPagesOption(option);
}
}, [page]);
const SelectableChange = (value: any) => {
formik?.setFieldValue(name, value); formik?.setFieldValue(name, value);
const isCleared = value?.length === 0 || !value;
if (isCleared) {
if (PageName) {
setValidationParamState({
[PageName]: 1,
});
}
}
console.log(value, "value");
}; };
const SearchHandleChange = (value: any) => {
navigate(`${window?.location?.pathname}?${searchBy}=${value}`, { const handleChange = useDebounce((value: string) => {
replace: true, if (PageName) {
pushValidationParamState({
[PageName]: 1,
});
}
pushValidationParamState({
[searchBy]: value,
}); });
});
const handleBlur = () => {
if (PageName && page === 1) {
setValidationParamState({
[PageName]: null,
});
}
if (PageName && page !== 1) {
setValidationParamState({
[PageName]: 1,
});
// setAllPagesOption([]);
}
}; };
const handleScroll = (event: any) => {
const target = event.target;
const isAtBottom =
target.scrollHeight === target.scrollTop + target.clientHeight;
if (isAtBottom && canChangePage && PageName && page) {
console.log("Scrolled to the last option!");
let newPage = page + 1;
pushValidationParamState({
[PageName]: newPage,
});
}
};
console.log(AllPagesOption);
console.log(option,"option");
return ( return (
<div className="ValidationField w-100 "> <div className="ValidationField w-100">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{t(`input.${label ? label : name}`)} />
</label> <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
<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 <Select
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`,
)}
disabled={isDisabled} disabled={isDisabled}
options={option} options={AllPagesOption}
size="large" size="large"
className={`${className} w-100`} className={`${className} w-100`}
value={formik.values[name]} value={formik.values[name]}
loading={isLoading} // loading={isLoading}
allowClear allowClear
{...(isMulti && { mode: "multiple" })} {...(isMulti && { mode: "multiple" })}
onChange={onChange || SelectableChange} onChange={onChange || SelectableChange}
showSearch showSearch
optionFilterProp="label" optionFilterProp="name"
notFoundContent={isLoading ? <Spin /> : "لا يوجد"} notFoundContent={isLoading ? <Spin /> : t("validation.undefined")}
onSearch={SearchHandleChange} onSearch={handleChange}
onBlur={handleBlur}
id={name}
onPopupScroll={handleScroll}
fieldNames={{ label: "name", value: "id" }}
{...props}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -1,8 +1,10 @@
import { Form, Select, Spin } from "antd"; import { Select } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { translateOptions } from "../utils/translatedOptions"; import { translateOptions } from "../utils/translatedOptions";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
import { SelectFieldProps } from "../utils/types";
const SelectField = ({ const SelectField = ({
name, name,
@ -13,10 +15,11 @@ const SelectField = ({
isMulti, isMulti,
onChange, onChange,
className, className,
props,
no_label, no_label,
label_icon, label_icon,
}: any) => { isLoading,
...props
}: SelectFieldProps) => {
const { errorMsg, isError, t, formik } = useFormField(name, props); const { errorMsg, isError, t, formik } = useFormField(name, props);
const SelectableChange = (value: { const SelectableChange = (value: {
value: string; value: string;
@ -24,47 +27,36 @@ const SelectField = ({
}) => { }) => {
formik.setFieldValue(name, value); formik.setFieldValue(name, value);
}; };
// console.log(name,"Select"); const options = translateOptions(option, t);
return ( return (
<div className="ValidationField w-100"> <div className="ValidationField w-100">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{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 <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Select <Select
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`,
)}
disabled={isDisabled} disabled={isDisabled}
options={translateOptions(option, t)} options={options}
loading={option?.length < 1} {...(isLoading && { loading: isLoading })}
size="large" size="large"
className={`${className} ${isError ? "Select_error" : ""} w-100`} className={`${className} ${isError ? "SelectError" : ""} w-100`}
value={formik.values[name]} value={formik.values[name]}
allowClear allowClear
{...(isMulti && { mode: "multiple" })} {...(isMulti && { mode: "multiple" })}
onChange={onChange || SelectableChange} onChange={onChange || SelectableChange}
showSearch={false}
id={name}
fieldNames={{label:"name",value:"id"}}
{...props}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -1,7 +1,8 @@
import { Form, Input } from "antd"; import { Input } from "antd";
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { Field } from "formik"; import { Field } from "formik";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const { TextArea } = Input; const { TextArea } = Input;
const TextAreaField = ({ const TextAreaField = ({
@ -11,14 +12,12 @@ const TextAreaField = ({
isDisabled, isDisabled,
onChange, onChange,
props, props,
type,
}: any) => { }: any) => {
const { formik, isError, errorMsg, t } = useFormField(name, props); const { formik, isError, errorMsg, t } = useFormField(name, props);
const handleChange = ( const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => { ) => {
// console.log('Change:', e.target.value);
formik.setFieldValue(name, e.target.value); formik.setFieldValue(name, e.target.value);
}; };
@ -27,11 +26,7 @@ const TextAreaField = ({
<label htmlFor={name} className="text"> <label htmlFor={name} className="text">
{t(`input.${label ? label : name}`)} {t(`input.${label ? label : name}`)}
</label> </label>
<Form.Item <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field <Field
as={TextArea} as={TextArea}
placeholder={t(`input.${placeholder ? placeholder : name}`)} placeholder={t(`input.${placeholder ? placeholder : name}`)}
@ -39,10 +34,10 @@ const TextAreaField = ({
disabled={isDisabled} disabled={isDisabled}
size="large" size="large"
onChange={onChange || handleChange} onChange={onChange || handleChange}
id={name}
// onChange={onChange ? onChange : handleChange} // onChange={onChange ? onChange : handleChange}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -3,6 +3,8 @@ import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md"; import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik"; import { Field } from "formik";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
import { ValidationFieldContainer } from "../components/ValidationFieldContainer";
const { TextArea } = Input; const { TextArea } = Input;
const TextField = ({ const TextField = ({
@ -21,33 +23,20 @@ const TextField = ({
const TextFilehandleChange = ( const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => { ) => {
// console.log('Change:', e.target.value);
formik.setFieldValue(name, e.target.value); formik.setFieldValue(name, e.target.value);
}; };
return ( return (
<div className={`ValidationField w-100 ${className ?? ""} `}> <div className={`ValidationField w-100 ${className ?? ""} `}>
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{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 <ValidationFieldContainer isError={isError} errorMsg={errorMsg}>
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field <Field
as={TextArea} as={TextArea}
placeholder={t(`input.${placeholder ? placeholder : name}`)} placeholder={t(`input.${placeholder ? placeholder : name}`)}
@ -58,8 +47,9 @@ const TextField = ({
maxLength={1000} maxLength={1000}
onChange={onChange || TextFilehandleChange} onChange={onChange || TextFilehandleChange}
style={{ height: 120 }} style={{ height: 120 }}
id={name}
/> />
</Form.Item> </ValidationFieldContainer>
</div> </div>
); );
}; };

View File

@ -3,6 +3,7 @@ import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md"; import { MdOutlineEdit } from "react-icons/md";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ValidationFieldLabel } from "../components/ValidationFieldLabel";
const Time = ({ const Time = ({
name, name,
@ -20,27 +21,19 @@ const Time = ({
formik.setFieldValue(name, value); formik.setFieldValue(name, value);
}; };
const Formater = "H:mm"; const Formatter = "H:mm";
const FormikValue = formik.values[name]; const FormikValue = formik.values[name];
return ( return (
<div className="ValidationField w-100 "> <div className="ValidationField w-100 ">
{no_label ? ( <ValidationFieldLabel
<label htmlFor={name} className="text"> name={name}
<span>empty</span> label={label}
</label> label_icon={label_icon}
) : label_icon ? ( no_label={no_label}
<div className="LabelWithIcon"> placeholder={placeholder}
<label htmlFor={name} className="text"> t={t}
{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 <Form.Item
hasFeedback hasFeedback
@ -51,14 +44,13 @@ const Time = ({
allowClear allowClear
className={`${className} w-100`} className={`${className} w-100`}
size="large" size="large"
value={FormikValue ? dayjs(FormikValue, Formater) : null} value={FormikValue ? dayjs(FormikValue, Formatter) : null}
onChange={onChange || onCalendarChange} onChange={onChange || onCalendarChange}
disabled={isDisabled} disabled={isDisabled}
placeholder={t( placeholder={t(`input.${placeholder || label || name}`)}
`input.${placeholder ? placeholder : label ? label : name}`, format={Formatter}
)}
format={Formater}
needConfirm={false} needConfirm={false}
id={name}
/> />
</Form.Item> </Form.Item>
</div> </div>

View File

@ -0,0 +1,24 @@
import React, { FC } from "react";
import { Form } from "antd";
interface ValidationFieldContainerProps {
children: React.ReactNode;
isError: boolean;
errorMsg: string;
}
export const ValidationFieldContainer: FC<ValidationFieldContainerProps> = ({
children,
isError,
errorMsg,
}) => (
<div className="ValidationFieldContainer">
<Form.Item
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
{children}
</Form.Item>
</div>
);

View File

@ -0,0 +1,39 @@
import React from "react";
import { MdOutlineEdit } from "react-icons/md";
interface ValidationFieldLabelProps {
name: string;
label?: string;
no_label?: boolean;
label_icon?: boolean;
placeholder?: string;
t: (key: string) => string;
}
export const ValidationFieldLabel: React.FC<ValidationFieldLabelProps> = ({
name,
label,
placeholder,
no_label,
label_icon,
t,
}) => (
<>
{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>
)}
</>
);

View File

@ -2,7 +2,6 @@ import { useState } from "react";
import { ErrorMessage, useField, Field, useFormikContext } from "formik"; import { ErrorMessage, useField, Field, useFormikContext } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaExclamationCircle } from "react-icons/fa"; import { FaExclamationCircle } from "react-icons/fa";
import { convert_data_to_select } from "../../Layout/app/Const";
export { export {
useState, useState,
@ -12,5 +11,4 @@ export {
useFormikContext, useFormikContext,
useTranslation, useTranslation,
FaExclamationCircle, FaExclamationCircle,
convert_data_to_select,
}; };

View File

@ -0,0 +1,36 @@
import { create } from "zustand";
interface ValidationParamState {
[key: string]: any;
}
interface ModalState {
ValidationParamState: ValidationParamState;
setValidationParamState: (validationParamState: ValidationParamState) => void;
pushValidationParamState: (
validationParamState: ValidationParamState,
) => void;
clearValidationParamState: () => void;
}
export const useValidationValidationParamState = create<ModalState>((set) => ({
ValidationParamState: {},
setValidationParamState: (validationParamState) =>
set(() => ({
ValidationParamState: validationParamState,
})),
pushValidationParamState: (validationParamState) =>
set((state) => ({
ValidationParamState: {
...state.ValidationParamState,
...validationParamState,
},
})),
clearValidationParamState: () =>
set({
ValidationParamState: {},
}),
}));

View File

@ -21,7 +21,7 @@
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
} }
.Select_error { .SelectError {
.ant-select-selector { .ant-select-selector {
border: 1px solid red !important; border: 1px solid red !important;
} }
@ -60,12 +60,9 @@
.ant-upload-select { .ant-upload-select {
width: 100%; width: 100%;
} }
.Checkboxs { .Checkbox {
padding: 4%; padding: 4%;
} }
.ant-checkbox-wrapper {
min-width: 100px;
}
.SearchField { .SearchField {
button { button {
background: var(--primary); background: var(--primary);
@ -201,27 +198,3 @@ input:-webkit-autofill:hover {
.TwoSelectGroupbutton { .TwoSelectGroupbutton {
margin-bottom: 20px; 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

@ -1,7 +0,0 @@
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

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

View File

@ -1,194 +1,80 @@
import { InputProps } from "antd"; import { InputProps, SelectProps } from "antd";
export type ValidationFieldType = // Common properties for all field types
| "text" interface BaseFieldProps {
| "Select"
| "LocalSearch"
| "Search"
| "DataRange"
| "Date"
| "Time"
| "File"
| "MaltyFile"
| "DropFile"
| "Checkbox"
| "number"
| "password"
| "email"
| "TextArea";
export interface ValidationFieldPropsText {
name: string; name: string;
no_label?: boolean;
label_icon?: boolean;
type: "text";
placeholder?: string;
label?: string; label?: string;
placeholder?: string;
className?: string; className?: string;
isDisabled?: boolean; isDisabled?: boolean;
onChange?: (value: any) => void; onChange?: (value: any) => void;
dir?: "ltr" | "rtl"; dir?: "ltr" | "rtl";
no_label?: boolean;
label_icon?: boolean;
} }
export interface ValidationFieldPropsSelect { // Specific field type properties
name: string; export type SelectFieldProps = BaseFieldProps &
no_label?: boolean; SelectProps & {
label_icon?: boolean; type: "Select" | "LocalSearch";
type: "Select"; option: any[];
placeholder?: string; isMulti?: boolean;
label?: string; isLoading?: boolean;
className?: string; searchBy?: string;
isDisabled?: boolean; canChangePage?: boolean;
onChange?: any; PageName?: string;
dir?: "ltr" | "rtl"; page?: number;
option: any[]; };
isMulti?: boolean;
}
export interface ValidationFieldPropsLocalSearch { export type SearchFieldProps = BaseFieldProps &
name: string; SelectProps & {
no_label?: boolean; type: "Search";
label_icon?: boolean; option: any[];
type: "LocalSearch"; isMulti?: boolean;
placeholder?: string; isLoading: boolean;
label?: string; searchBy: string;
className?: string; canChangePage: boolean;
isDisabled?: boolean; PageName: string;
onChange?: (value: any) => void; page: number;
dir?: "ltr" | "rtl"; };
option: any[];
isMulti?: boolean; type DateFieldProps = BaseFieldProps & {
} type: "DataRange" | "Date" | "Time";
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"; 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"; picker?: "data" | "week" | "month" | "quarter" | "year";
} };
export interface ValidationFieldPropsTime { type FileFieldProps = BaseFieldProps & {
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"; type: "File" | "MaltyFile" | "DropFile";
placeholder?: string; };
label?: string;
className?: string; type CheckboxFieldProps = BaseFieldProps & {
isDisabled?: boolean;
onChange?: (value: any) => void;
dir?: "ltr" | "rtl";
}
export interface ValidationFieldPropsCheckbox {
name: string;
no_label?: boolean;
label_icon?: boolean;
type: "Checkbox"; type: "Checkbox";
label?: string;
className?: string;
isDisabled?: boolean;
onChange?: (value: any) => void;
dir?: "ltr" | "rtl";
Group?: boolean; 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 type FieldProps = BaseFieldProps &
export interface BaseField { InputProps & {
name: string; type?:
label?: string; | "text"
placeholder?: string; | "number"
} | "password"
export type OmitBaseType = "placeholder" | "name" | "label" | "type"; | "email"
| "TextArea"
export type OmitPicker = OmitBaseType | "format"; | "NumberFormate";
label2?: string;
export interface ValidationFieldPropsInput Group?: boolean;
extends Omit<InputProps, OmitBaseType>, [key: string]: any;
BaseField { };
type: "text" | "number" | "password" | "email" | "Number";
isDisabled?: boolean;
no_label?: string;
label_icon?: string;
label2?: string;
}
// Union type for all field types
export type ValidationFieldProps = export type ValidationFieldProps =
| ValidationFieldPropsInput | SelectFieldProps
| ValidationFieldPropsSelect | DateFieldProps
| ValidationFieldPropsLocalSearch | FileFieldProps
| ValidationFieldPropsDataRange | CheckboxFieldProps
| ValidationFieldPropsDate | SearchFieldProps
| ValidationFieldPropsTime | FieldProps;
| ValidationFieldPropsFile
| ValidationFieldPropsCheckbox // Validation field type
| ValidationFieldPropstext export type ValidationFieldType = ValidationFieldProps["type"];
| ValidationFieldPropsSearch;

25
src/Hooks/useDebounce.ts Normal file
View File

@ -0,0 +1,25 @@
import { useCallback, useRef } from "react";
export const DEBOUNCE_DELAY = 500;
export const useDebounce = (
callback: (...args: any[]) => void,
delay: number = DEBOUNCE_DELAY,
) => {
const timeoutRef = useRef<any>(null);
const debouncedCallback = useCallback(
(...args: any[]) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
},
[callback, delay],
);
return debouncedCallback;
};

View File

@ -9,6 +9,8 @@ import { useTranslation } from "react-i18next";
import { getLocalStorage } from "../../utils/LocalStorage"; import { getLocalStorage } from "../../utils/LocalStorage";
import { BRANCH_OBJECT_KEY } from "../../config/AppKey"; import { BRANCH_OBJECT_KEY } from "../../config/AppKey";
import { MenuItem } from "../../Components/Layout/SideBar/MenuItem"; import { MenuItem } from "../../Components/Layout/SideBar/MenuItem";
import { UserTypeEnum } from "../../enums/UserType";
import { RoleByType } from "../../utils/RoleByType";
const SideBar = () => { const SideBar = () => {
const location = useLocation(); const location = useLocation();
@ -17,7 +19,6 @@ const SideBar = () => {
const { logout } = useAuthState(); const { logout } = useAuthState();
const [t] = useTranslation(); const [t] = useTranslation();
const branch_name = getLocalStorage(BRANCH_OBJECT_KEY)?.name; const branch_name = getLocalStorage(BRANCH_OBJECT_KEY)?.name;
return ( return (
<div className="side_bar"> <div className="side_bar">
<h1> <h1>
@ -31,6 +32,11 @@ const SideBar = () => {
if (!useAbility) { if (!useAbility) {
return <React.Fragment key={index}></React.Fragment>; return <React.Fragment key={index}></React.Fragment>;
} }
if(!RoleByType(item)){
return <React.Fragment key={index}></React.Fragment> ;
}
return ( return (
<MenuItem <MenuItem
key={index} key={index}

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } from "react";
import { Formik } from "formik"; import { Formik } from "formik";
import useAuthState from "../../zustand/AuthState"; import useAuthState from "../../zustand/AuthState";
import useNavigateOnSuccess from "../../Hooks/useNavigateOnSuccess"; import useNavigateOnSuccess from "../../Hooks/useNavigateOnSuccess";
@ -8,6 +8,9 @@ import { initialValues } from "./formutils";
import { FormValues } from "../../types/Auth"; import { FormValues } from "../../types/Auth";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { getLocalStorage } from "../../utils/LocalStorage";
import { USER_KEY } from "../../config/AppKey";
const LoginForm = () => { const LoginForm = () => {
const { mutate, isLoading, isSuccess, data } = useLoginAdmin(); const { mutate, isLoading, isSuccess, data } = useLoginAdmin();
@ -16,12 +19,28 @@ const LoginForm = () => {
mutate(values); mutate(values);
}; };
const { login } = useAuthState(); const { login,isAuthenticated} = useAuthState();
const LoginData = { const LoginData = {
...data, ...data,
} as any;
useNavigateOnSuccess(isSuccess, "/", () => login(LoginData?.data as any));
} as any;
const navigate = useNavigate()
const LocalType = getLocalStorage(USER_KEY)?.type ?? false ;
useEffect(() => {
if(isSuccess){
login(LoginData?.data as any)
}
}, [isSuccess])
useEffect(() => {
if(LocalType ){
window.location.href = ("/")
}
}, [LocalType])
return ( return (
<div className="LoginForm"> <div className="LoginForm">
<Formik initialValues={initialValues} onSubmit={handelSubmit}> <Formik initialValues={initialValues} onSubmit={handelSubmit}>

View File

@ -0,0 +1,9 @@
import React from 'react'
const ConfigurationField = () => {
return (
<div>ConfigurationField</div>
)
}
export default ConfigurationField

View File

@ -0,0 +1,96 @@
import React from "react";
import { Input, Button } from "antd";
import { useFormikContext } from "formik";
import { useTranslation } from "react-i18next";
interface FieldGroupProps {
array_name: string;
title:string;
static?: boolean;
only_value?: boolean;
}
const FieldGroup: React.FC<FieldGroupProps> = ({ array_name ,title,static:isStatic,only_value}) => {
const { values, setFieldValue } = useFormikContext<any>();
const addFieldGroup = () => {
setFieldValue(array_name, [
...(values?.[array_name] || []),
{ key: null, value: null },
]);
};
const deleteFieldGroup = () => {
const updatedArray = values?.[array_name]?.slice(0, -1) || [];
setFieldValue(array_name, updatedArray);
};
const handleChangeKey = (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => {
const Selects = values?.[array_name] ?? [];
Selects[index].key = e.target.value;
setFieldValue(array_name, Selects);
};
const handleChangeValue = (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => {
const Selects = values?.[array_name] ?? [];
Selects[index].value = e.target.value;
setFieldValue(array_name, Selects);
};
const selectsArray = values?.[array_name] ?? [];
const { t } = useTranslation();
return (
<>
<label htmlFor={array_name} className="text">
<h5 className="mb-2">{t(`header.${title}`)}</h5>
</label>
{selectsArray.map((group: { key: string; value: string }, index: number) => (
<div key={index} className="d-flex gap-2 mb-3">
<Input
placeholder={t("practical.key")}
onChange={(e) => handleChangeKey(e, index)}
value={group.key}
size="large"
style={{ width: "100%" }}
allowClear
disabled={only_value}
/>
<Input
placeholder={t("practical.value")}
onChange={(e) => handleChangeValue(e, index)}
value={group.value}
size="large"
style={{ width: "100%" }}
allowClear
/>
</div>
))}
{!isStatic &&
<div className="d-flex gap-2 mb-4">
<Button
type="dashed"
onClick={addFieldGroup}
>
{t("practical.add_new")}
</Button>
<Button
type="dashed"
onClick={deleteFieldGroup}
>
{t("practical.delete_last")}
</Button>
</div>
}
</>
);
};
export default FieldGroup;

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect } from "react";
import { getInitialValues, getValidationSchema } from "./formUtil"; import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model"; import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel"; import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
@ -6,52 +6,65 @@ import { QueryStatusEnum } from "../../../enums/QueryStatus";
import ModelForm from "./ModelForm"; import ModelForm from "./ModelForm";
import { useAddPackage } from "../../../api/package"; import { useAddPackage } from "../../../api/package";
import FormikForm from "../../../Layout/Dashboard/FormikForm"; import FormikForm from "../../../Layout/Dashboard/FormikForm";
import { Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import useSetPageTitle from "../../../Hooks/useSetPageTitle";
import { PackageInitialValues } from "../../../types/Package";
import { arrayToObject } from "../../../utils/arrayToObject";
const AddModel: React.FC = () => { const AddModel: React.FC = () => {
const { mutate, status } = useAddPackage(); const { mutate, status ,isLoading,isSuccess} = useAddPackage();
const [t] = useTranslation();
const navigate = useNavigate()
const handleSubmit = (values: PackageInitialValues) => {
const DataToSend = JSON.parse(JSON.stringify(values) );
console.log(DataToSend,"DataToSend");
console.log(values?.configuration);
const configuration = JSON.stringify(arrayToObject(values?.configuration )) ;
console.log(configuration);
const handleSubmit = (values: any) => {
mutate({ mutate({
...values, ...values,
}); });
}; };
useEffect(() => {
if(isSuccess){
navigate("/package")
}
}, [isSuccess])
useSetPageTitle(t(`page_header.package`));
return ( return (
<> <div className="page">
{/* <LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.Package_ADD}
modelTitle="package"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})}
getValidationSchema={getValidationSchema}
>
<ModelForm />
moaz
</LayoutModel> */}
casascaas
<FormikForm <FormikForm
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
initialValues={getInitialValues('')} initialValues={getInitialValues({})}
validationSchema={getValidationSchema} validationSchema={getValidationSchema}
> >
<header>
{t("header.add_new_package")}
</header>
<main className="w-100 exercise_add_main"> <main className="w-100 exercise_add_main">
<ModelForm /> <ModelForm />
<div className="exercise_add_buttons">
<button disabled={isLoading} className="relative button center" type="submit">
{t("practical.add")}
<div className="exercise_add_buttons"> {isLoading && (
{/* <div onClick={handleCancel}>{t("practical.back")}</div> */} <span className="Spinier_Div">
{/* <button disabled={isLoading} className="relative" type="submit"> <Spin />
{t("practical.add")} </span>
)}
{isLoading && ( </button>
<span className="Spinier_Div"> </div>
<Spin /> </main>
</span>
)}
</button> */}
</div>
</main>
</FormikForm> </FormikForm>
</> </div>
); );
}; };

View File

@ -1,35 +1,78 @@
import React from "react"; import React, { useEffect } from "react";
import { getInitialValues, getValidationSchema } from "./formUtil"; import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import ModelForm from "./ModelForm"; import ModelForm from "./ModelForm";
import { QueryStatusEnum } from "../../../enums/QueryStatus"; import FormikForm from "../../../Layout/Dashboard/FormikForm";
import { Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import useSetPageTitle from "../../../Hooks/useSetPageTitle";
import { PackageInitialValues } from "../../../types/Package";
import { arrayToObject } from "../../../utils/arrayToObject";
import { useGetAllPackage, useUpdatePackage } from "../../../api/package";
import { ParamsEnum } from "../../../enums/params";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useUpdatePackage } from "../../../api/package"; import SpinContainer from "../../../Components/Layout/SpinContainer";
const EditModel: React.FC = () => { const EditModel: React.FC = () => {
const { mutate, status } = useUpdatePackage(); const { mutate, status ,isLoading,isSuccess} = useUpdatePackage();
const { objectToEdit } = useObjectToEdit((state) => state); const [t] = useTranslation();
const {package_id} = useParams<ParamsEnum>();
const {objectToEdit} = useObjectToEdit();
const {data,isLoading:isLoadingData} = useGetAllPackage({show:package_id})
console.log(data?.data,"data");
const handleSubmit = (values: PackageInitialValues) => {
const DataToSend = JSON.parse(JSON.stringify(values) );
console.log(DataToSend,"DataToSend");
console.log(values?.configuration);
const configuration = JSON.stringify(arrayToObject(values?.configuration )) ;
console.log(configuration);
const handleSubmit = (values: any) => {
mutate({ mutate({
...values, ...values,
}); });
}; };
const navigate = useNavigate()
useEffect(() => {
if(isSuccess){
navigate("/package")
}
}, [isSuccess])
useSetPageTitle(t(`page_header.package`));
if(isLoadingData){
return <SpinContainer/>
}
return ( return (
<> <div className="page">
<LayoutModel <FormikForm
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.Package_EDIT}
modelTitle="package_details"
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)} initialValues={getInitialValues(data?.data)}
getValidationSchema={getValidationSchema} validationSchema={getValidationSchema}
isAddModal={false} >
>
<header>
{t("header.edit_package")}
</header>
<main className="w-100 exercise_Edit_main">
<ModelForm /> <ModelForm />
</LayoutModel> <div className="exercise_Edit_buttons">
</> <button disabled={isLoading} className="relative button center" type="submit">
{t("practical.edit")}
{isLoading && (
<span className="Spinier_Div">
<Spin />
</span>
)}
</button>
</div>
</main>
</FormikForm>
</div>
); );
}; };

View File

@ -1,15 +1,237 @@
import { Col, Row } from "reactstrap"; import { Col, Row } from "reactstrap";
import ValidationField from "../../../Components/ValidationField/ValidationField"; import ValidationField from "../../../Components/ValidationField/ValidationField";
import { useGetAllGrade } from "../../../api/grade";
import { useGetAllSubject } from "../../../api/subject";
import { useGetAllUnit } from "../../../api/unit";
import { useGetAllCurriculum } from "../../../api/curriculum";
import { useGetAllLesson } from "../../../api/lesson";
import { useFormikContext } from "formik";
import { PackageInitialValues } from "../../../types/Package";
import { useValidationValidationParamState } from "../../../Components/ValidationField/state/ValidationValidationParamState";
import { useEffect } from "react";
import ConfigurationField from "../Field/ConfigurationField";
import FieldGroup from "../Field/FieldGroup";
const Form = () => { const Form = () => {
const { values, setFieldValue } = useFormikContext<PackageInitialValues>();
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
SubjectName, SubjectCurrentPage,
UnitName, UnitCurrentPage,
CurriculumName, CurriculumCurrentPage,
LessonName, LessonCurrentPage
} = ValidationParamState;
const { curriculums_ids, grade_id, subjects_ids, units_ids, lessons_ids } = values;
/// grade_id
const GradeDisabled = !!grade_id;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
/// subjects_ids
const SubjectDisabled = !!subjects_ids && subjects_ids?.length > 0;
const { data: Subject, isLoading: isLoadingSubject } = useGetAllSubject({
grade_id: grade_id,
name: SubjectName,
page: SubjectCurrentPage
}, { enabled: GradeDisabled });
const SubjectOption = Subject?.data ?? []
const canChangeSubjectPage = !!Subject?.links?.next;
const SubjectPage = Subject?.meta?.currentPage;
/// units_ids
const UnitDisabled = !!units_ids && units_ids?.length > 0;
const { data: Unit, isLoading: isLoadingUnit } = useGetAllUnit({
subjects_ids: subjects_ids,
name: UnitName,
page: UnitCurrentPage
}, { enabled: SubjectDisabled });
const UnitOption = Unit?.data ?? []
const canChangeUnitPage = !!Unit?.links?.next;
const UnitPage = Unit?.meta?.currentPage;
/// curriculums_ids
const CurriculumDisabled = !!curriculums_ids && curriculums_ids?.length > 0;
const { data: Curriculum, isLoading: isLoadingCurriculum } = useGetAllCurriculum({
units_ids: units_ids,
name: CurriculumName,
page: CurriculumCurrentPage
}, { enabled: UnitDisabled });
const CurriculumOption = Curriculum?.data ?? []
const canChangeCurriculumPage = !!Curriculum?.links?.next;
const CurriculumPage = Curriculum?.meta?.currentPage;
/// lessons_ids
const { data: Lesson, isLoading: isLoadingLesson } = useGetAllLesson({
curriculums_ids: curriculums_ids,
name: LessonName,
page: LessonCurrentPage
}, { enabled: CurriculumDisabled });
const LessonOption = Lesson?.data ?? []
const canChangeLessonPage = !!Lesson?.links?.next;
const LessonPage = Lesson?.meta?.currentPage;
useEffect(() => {
const GradeChildren = (subjects_ids && subjects_ids?.length > 0 || units_ids && units_ids?.length > 0 || curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0);
if (!grade_id && GradeChildren) {
setFieldValue("subjects_ids", []);
setFieldValue("units_ids", []);
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
console.log(1);
const SubjectChildren = (units_ids && units_ids?.length > 0 || curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0)
console.log( subjects_ids && subjects_ids?.length < 1 && SubjectChildren);
if (subjects_ids && subjects_ids?.length < 1 && SubjectChildren) {
console.log(1);
setFieldValue("units_ids", []);
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
const UnitChildren = (curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0)
if (units_ids && units_ids?.length < 1 && UnitChildren) {
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
const CurriculumChildren = (lessons_ids && lessons_ids?.length > 0)
if (curriculums_ids && curriculums_ids?.length < 1 && CurriculumChildren) {
setFieldValue("lessons_ids", []);
return;
}
}, [grade_id, subjects_ids, units_ids, curriculums_ids])
return ( return (
<Row className="w-100"> <Row className="w-100">
<Col> <Col>
<ValidationField placeholder="name" label="name" name="name" /> <ValidationField placeholder="name" label="name" name="name" />
<ValidationField placeholder="price" label="price" name="price" /> <ValidationField placeholder="price" label="price" name="price" type="number" />
<ValidationField placeholder="name" label="name" name="name" /> <FieldGroup array_name='configuration' title='configuration' />
</Col>
<Col>
{/*
grade_id
*/}
<ValidationField
searchBy="GradeName"
name="grade_id"
label="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
{/*
subjects_ids
*/}
<ValidationField
searchBy="SubjectName"
name="subjects_ids"
label="subject"
type="Search"
option={SubjectOption}
isMulti
isLoading={isLoadingSubject}
canChangePage={canChangeSubjectPage}
PageName={"SubjectCurrentPage"}
page={SubjectPage}
disabled={!GradeDisabled}
/>
{/*
units_ids
*/}
<ValidationField
searchBy="UnitName"
name="units_ids"
label="unit"
type="Search"
option={UnitOption}
isMulti
isLoading={isLoadingUnit}
canChangePage={canChangeUnitPage}
PageName={"UnitCurrentPage"}
page={UnitPage}
disabled={!SubjectDisabled}
/>
{/*
curriculums_ids
*/}
<ValidationField
searchBy="CurriculumName"
name="curriculums_ids"
label="curriculum"
type="Search"
option={CurriculumOption}
isMulti
isLoading={isLoadingCurriculum}
canChangePage={canChangeCurriculumPage}
PageName={"CurriculumCurrentPage"}
page={CurriculumPage}
disabled={!UnitDisabled}
/>
{/*
lessons_ids
*/}
<ValidationField
searchBy="LessonName"
name="lessons_ids"
label="lesson"
type="Search"
option={LessonOption}
isMulti
isLoading={isLoadingLesson}
canChangePage={canChangeLessonPage}
PageName={"LessonCurrentPage"}
page={LessonPage}
disabled={!CurriculumDisabled}
/>
</Col> </Col>
</Row> </Row>
); );
}; };

View File

@ -1,16 +1,32 @@
import * as Yup from "yup"; import * as Yup from "yup";
export const getInitialValues = (objectToEdit: any): any => { import { Package, PackageInitialValues } from "../../../types/Package";
import { arrayToObject } from "../../../utils/arrayToObject";
import { objectToArray } from "../../../utils/objectToArray";
export const getInitialValues = (objectToEdit: Partial<Package>): PackageInitialValues => {
console.log(objectToEdit,"objectToEdit");
const configuration = Array.isArray(objectToEdit?.configuration) ? objectToEdit?.configuration : objectToArray(objectToEdit?.configuration)
return { return {
id: objectToEdit?.id ?? null, id: objectToEdit?.id ?? null,
name: objectToEdit?.name ?? null, name: objectToEdit?.name ?? null,
price: objectToEdit?.price ?? null, price: objectToEdit?.price ?? null,
grade_id: objectToEdit?.grade_id ?? null, grade_id: objectToEdit?.grade_id ?? null,
curriculums_ids: objectToEdit?.curriculums_ids ?? [],
lessons_ids: objectToEdit?.lessons_ids ?? [],
subjects_ids: objectToEdit?.subjects_ids ?? [],
units_ids: objectToEdit?.units_ids ?? [],
configuration: configuration ?? [{key:"",value:""}],
}; };
}; };
export const getValidationSchema = () => { export const getValidationSchema = () => {
return Yup.object().shape({ return Yup.object().shape({
name: Yup.string().required("validation.required"), name: Yup.string().required("validation.required"),
price: Yup.number().required("validation.required").typeError("validation.Must_be_a_number"),
grade_id: Yup.string().required("validation.required"),
curriculums_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
lessons_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
subjects_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
units_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
}); });
}; };

View File

@ -0,0 +1,9 @@
import React from 'react'
const ConfigurationField = () => {
return (
<div>ConfigurationField</div>
)
}
export default ConfigurationField

View File

@ -0,0 +1,96 @@
import React from "react";
import { Input, Button } from "antd";
import { useFormikContext } from "formik";
import { useTranslation } from "react-i18next";
interface FieldGroupProps {
array_name: string;
title:string;
static?: boolean;
only_value?: boolean;
}
const FieldGroup: React.FC<FieldGroupProps> = ({ array_name ,title,static:isStatic,only_value}) => {
const { values, setFieldValue } = useFormikContext<any>();
const addFieldGroup = () => {
setFieldValue(array_name, [
...(values?.[array_name] || []),
{ key: null, value: null },
]);
};
const deleteFieldGroup = () => {
const updatedArray = values?.[array_name]?.slice(0, -1) || [];
setFieldValue(array_name, updatedArray);
};
const handleChangeKey = (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => {
const Selects = values?.[array_name] ?? [];
Selects[index].key = e.target.value;
setFieldValue(array_name, Selects);
};
const handleChangeValue = (
e: React.ChangeEvent<HTMLInputElement>,
index: number
) => {
const Selects = values?.[array_name] ?? [];
Selects[index].value = e.target.value;
setFieldValue(array_name, Selects);
};
const selectsArray = values?.[array_name] ?? [];
const { t } = useTranslation();
return (
<>
<label htmlFor={array_name} className="text">
<h5 className="mb-2">{t(`header.${title}`)}</h5>
</label>
{selectsArray.map((group: { key: string; value: string }, index: number) => (
<div key={index} className="d-flex gap-2 mb-3">
<Input
placeholder={t("practical.key")}
onChange={(e) => handleChangeKey(e, index)}
value={group.key}
size="large"
style={{ width: "100%" }}
allowClear
disabled={only_value}
/>
<Input
placeholder={t("practical.value")}
onChange={(e) => handleChangeValue(e, index)}
value={group.value}
size="large"
style={{ width: "100%" }}
allowClear
/>
</div>
))}
{!isStatic &&
<div className="d-flex gap-2 mb-4">
<Button
type="dashed"
onClick={addFieldGroup}
>
{t("practical.add_new")}
</Button>
<Button
type="dashed"
onClick={deleteFieldGroup}
>
{t("practical.delete_last")}
</Button>
</div>
}
</>
);
};
export default FieldGroup;

View File

@ -0,0 +1,66 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../../enums/Model";
import LayoutModel from "../../../../Layout/Dashboard/LayoutModel";
import { QueryStatusEnum } from "../../../../enums/QueryStatus";
import ModelForm from "./ModelForm";
import { useAddPackage } from "../../../../api/package";
import FormikForm from "../../../../Layout/Dashboard/FormikForm";
import { Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import useSetPageTitle from "../../../../Hooks/useSetPageTitle";
import { PackageInitialValues } from "../../../../types/Package";
import { arrayToObject } from "../../../../utils/arrayToObject";
const AddModel: React.FC = () => {
const { mutate, status ,isLoading} = useAddPackage();
const [t] = useTranslation();
const navigate = useNavigate()
const handleSubmit = (values: PackageInitialValues) => {
const DataToSend = JSON.parse(JSON.stringify(values) );
console.log(DataToSend,"DataToSend");
console.log(values?.configuration);
const configuration = JSON.stringify(arrayToObject(values?.configuration )) ;
console.log(configuration);
mutate({
...values,
});
};
useSetPageTitle(t(`page_header.package`));
return (
<div className="page">
<FormikForm
handleSubmit={handleSubmit}
initialValues={getInitialValues({})}
validationSchema={getValidationSchema}
>
<header>
{t("header.add_new_package")}
</header>
<main className="w-100 exercise_add_main">
<ModelForm />
<div className="exercise_add_buttons">
<button disabled={isLoading} className="relative button center" type="submit">
{t("practical.add")}
{isLoading && (
<span className="Spinier_Div">
<Spin />
</span>
)}
</button>
</div>
</main>
</FormikForm>
</div>
);
};
export default AddModel;

View File

@ -0,0 +1,73 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import ModelForm from "./ModelForm";
import FormikForm from "../../../../Layout/Dashboard/FormikForm";
import { Spin } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import useSetPageTitle from "../../../../Hooks/useSetPageTitle";
import { PackageInitialValues } from "../../../../types/Package";
import { arrayToObject } from "../../../../utils/arrayToObject";
import { useGetAllPackage, useUpdatePackage } from "../../../../api/package";
import { ParamsEnum } from "../../../../enums/params";
import { useObjectToEdit } from "../../../../zustand/ObjectToEditState";
import SpinContainer from "../../../../Components/Layout/SpinContainer";
const EditModel: React.FC = () => {
const { mutate, status ,isLoading} = useUpdatePackage();
const [t] = useTranslation();
const {package_id} = useParams<ParamsEnum>();
const {objectToEdit} = useObjectToEdit();
const {data,isLoading:isLoadingData} = useGetAllPackage({show:package_id})
console.log(data?.data,"data");
const handleSubmit = (values: PackageInitialValues) => {
const DataToSend = JSON.parse(JSON.stringify(values) );
console.log(DataToSend,"DataToSend");
console.log(values?.configuration);
const configuration = JSON.stringify(arrayToObject(values?.configuration )) ;
console.log(configuration);
mutate({
...values,
});
};
useSetPageTitle(t(`page_header.package`));
if(isLoadingData){
return <SpinContainer/>
}
return (
<div className="page">
<FormikForm
handleSubmit={handleSubmit}
initialValues={getInitialValues(data?.data)}
validationSchema={getValidationSchema}
>
<header>
{t("header.edit_package")}
</header>
<main className="w-100 exercise_Edit_main">
<ModelForm />
<div className="exercise_Edit_buttons">
<button disabled={isLoading} className="relative button center" type="submit">
{t("practical.edit")}
{isLoading && (
<span className="Spinier_Div">
<Spin />
</span>
)}
</button>
</div>
</main>
</FormikForm>
</div>
);
};
export default EditModel;

View File

@ -0,0 +1,238 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
import { useGetAllGrade } from "../../../../api/grade";
import { useGetAllSubject } from "../../../../api/subject";
import { useGetAllUnit } from "../../../../api/unit";
import { useGetAllCurriculum } from "../../../../api/curriculum";
import { useGetAllLesson } from "../../../../api/lesson";
import { useFormikContext } from "formik";
import { PackageInitialValues } from "../../../../types/Package";
import { useValidationValidationParamState } from "../../../../Components/ValidationField/state/ValidationValidationParamState";
import { useEffect } from "react";
import ConfigurationField from "../../Field/ConfigurationField";
import FieldGroup from "../../Field/FieldGroup";
const Form = () => {
const { values, setFieldValue } = useFormikContext<PackageInitialValues>();
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
SubjectName, SubjectCurrentPage,
UnitName, UnitCurrentPage,
CurriculumName, CurriculumCurrentPage,
LessonName, LessonCurrentPage
} = ValidationParamState;
const { curriculums_ids, grade_id, subjects_ids, units_ids, lessons_ids } = values;
/// grade_id
const GradeDisabled = !!grade_id;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
/// subjects_ids
const SubjectDisabled = !!subjects_ids && subjects_ids?.length > 0;
const { data: Subject, isLoading: isLoadingSubject } = useGetAllSubject({
grade_id: grade_id,
name: SubjectName,
page: SubjectCurrentPage
}, { enabled: GradeDisabled });
const SubjectOption = Subject?.data ?? []
const canChangeSubjectPage = !!Subject?.links?.next;
const SubjectPage = Subject?.meta?.currentPage;
/// units_ids
const UnitDisabled = !!units_ids && units_ids?.length > 0;
const { data: Unit, isLoading: isLoadingUnit } = useGetAllUnit({
subjects_ids: subjects_ids,
name: UnitName,
page: UnitCurrentPage
}, { enabled: SubjectDisabled });
const UnitOption = Unit?.data ?? []
const canChangeUnitPage = !!Unit?.links?.next;
const UnitPage = Unit?.meta?.currentPage;
/// curriculums_ids
const CurriculumDisabled = !!curriculums_ids && curriculums_ids?.length > 0;
const { data: Curriculum, isLoading: isLoadingCurriculum } = useGetAllCurriculum({
units_ids: units_ids,
name: CurriculumName,
page: CurriculumCurrentPage
}, { enabled: UnitDisabled });
const CurriculumOption = Curriculum?.data ?? []
const canChangeCurriculumPage = !!Curriculum?.links?.next;
const CurriculumPage = Curriculum?.meta?.currentPage;
/// lessons_ids
const { data: Lesson, isLoading: isLoadingLesson } = useGetAllLesson({
curriculums_ids: curriculums_ids,
name: LessonName,
page: LessonCurrentPage
}, { enabled: CurriculumDisabled });
const LessonOption = Lesson?.data ?? []
const canChangeLessonPage = !!Lesson?.links?.next;
const LessonPage = Lesson?.meta?.currentPage;
useEffect(() => {
const GradeChildren = (subjects_ids && subjects_ids?.length > 0 || units_ids && units_ids?.length > 0 || curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0);
if (!grade_id && GradeChildren) {
setFieldValue("subjects_ids", []);
setFieldValue("units_ids", []);
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
console.log(1);
const SubjectChildren = (units_ids && units_ids?.length > 0 || curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0)
console.log( subjects_ids && subjects_ids?.length < 1 && SubjectChildren);
if (subjects_ids && subjects_ids?.length < 1 && SubjectChildren) {
console.log(1);
setFieldValue("units_ids", []);
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
const UnitChildren = (curriculums_ids && curriculums_ids?.length > 0 || lessons_ids && lessons_ids?.length > 0)
if (units_ids && units_ids?.length < 1 && UnitChildren) {
setFieldValue("curriculums_ids", []);
setFieldValue("lessons_ids", []);
return;
}
const CurriculumChildren = (lessons_ids && lessons_ids?.length > 0)
if (curriculums_ids && curriculums_ids?.length < 1 && CurriculumChildren) {
setFieldValue("lessons_ids", []);
return;
}
}, [grade_id, subjects_ids, units_ids, curriculums_ids])
return (
<Row className="w-100">
<Col>
<ValidationField placeholder="name" label="name" name="name" />
<ValidationField placeholder="price" label="price" name="price" type="number" />
<FieldGroup array_name='configuration' title='configuration' />
</Col>
<Col>
{/*
grade_id
*/}
<ValidationField
searchBy="GradeName"
name="grade_id"
label="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
{/*
subjects_ids
*/}
<ValidationField
searchBy="SubjectName"
name="subjects_ids"
label="subject"
type="Search"
option={SubjectOption}
isMulti
isLoading={isLoadingSubject}
canChangePage={canChangeSubjectPage}
PageName={"SubjectCurrentPage"}
page={SubjectPage}
disabled={!GradeDisabled}
/>
{/*
units_ids
*/}
<ValidationField
searchBy="UnitName"
name="units_ids"
label="unit"
type="Search"
option={UnitOption}
isMulti
isLoading={isLoadingUnit}
canChangePage={canChangeUnitPage}
PageName={"UnitCurrentPage"}
page={UnitPage}
disabled={!SubjectDisabled}
/>
{/*
curriculums_ids
*/}
<ValidationField
searchBy="CurriculumName"
name="curriculums_ids"
label="curriculum"
type="Search"
option={CurriculumOption}
isMulti
isLoading={isLoadingCurriculum}
canChangePage={canChangeCurriculumPage}
PageName={"CurriculumCurrentPage"}
page={CurriculumPage}
disabled={!UnitDisabled}
/>
{/*
lessons_ids
*/}
<ValidationField
searchBy="LessonName"
name="lessons_ids"
label="lesson"
type="Search"
option={LessonOption}
isMulti
isLoading={isLoadingLesson}
canChangePage={canChangeLessonPage}
PageName={"LessonCurrentPage"}
page={LessonPage}
disabled={!CurriculumDisabled}
/>
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,33 @@
import * as Yup from "yup";
import { Package, PackageInitialValues } from "../../../../types/Package";
import { arrayToObject } from "../../../../utils/arrayToObject";
import { objectToArray } from "../../../../utils/objectToArray";
export const getInitialValues = (objectToEdit: Partial<Package>): any => {
console.log(objectToEdit,"objectToEdit");
const configuration = Array.isArray(objectToEdit?.configuration) ? objectToEdit?.configuration : objectToArray(objectToEdit?.configuration)
return {
id: objectToEdit?.id ?? null,
name: objectToEdit?.name ?? null,
price: objectToEdit?.price ?? null,
grade_id: objectToEdit?.grade_id ?? null,
curriculums_ids: objectToEdit?.curriculums_ids ?? [],
lessons_ids: objectToEdit?.lessons_ids ?? [],
subjects_ids: objectToEdit?.subjects_ids ?? [],
units_ids: objectToEdit?.units_ids ?? [],
configuration: configuration ?? [{key:"",value:""}],
moiaed: [{key:"",value:""}],
};
};
export const getValidationSchema = () => {
return Yup.object().shape({
name: Yup.string().required("validation.required"),
price: Yup.number().required("validation.required").typeError("validation.Must_be_a_number"),
grade_id: Yup.string().required("validation.required"),
curriculums_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
lessons_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
subjects_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
units_ids: Yup.array().of(Yup.number().required()).min(1,"validation.must_have_on_item").required("validation.required"),
});
};

View File

@ -0,0 +1,59 @@
import { FaPlus } from "react-icons/fa";
import useModalHandler from "../../../utils/useModalHandler";
import { ModalEnum } from "../../../enums/Model";
import { useTranslation } from "react-i18next";
import { lazy, Suspense } from "react";
import { Spin } from "antd";
import { canAddPackage } from "../../../utils/hasAbilityFn";
import useSetPageTitle from "../../../Hooks/useSetPageTitle";
import { useDeletePackage } from "../../../api/package";
import { useNavigate } from "react-router-dom";
import { ABILITIES_ENUM } from "../../../enums/abilities";
const Table = lazy(() => import("./Table"));
const DeleteModalForm = lazy(
() => import("../../../Layout/Dashboard/DeleteModels"),
);
const SearchField = lazy(
() => import("../../../Components/DataTable/SearchField"),
);
const TableHeader = () => {
const { handel_open_model } = useModalHandler();
const [t] = useTranslation();
const navigate = useNavigate();
useSetPageTitle(t(`page_header.package`));
const deleteMutation = useDeletePackage();
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<header className="d-flex justify-content-between">
<SearchField
searchBy="name"
placeholder={t("practical.search_here")}
/>
{canAddPackage && (
<div className="Selects">
<button
onClick={() => navigate(`/${ABILITIES_ENUM?.Package}/add`)}
className="add_button"
>
{t("models.add_package")} <FaPlus />
</button>
</div>
)}
</header>
<Table />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.PACKAGE_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,21 @@
import React from "react";
import DataTable from "../../../Layout/Dashboard/Table/DataTable";
import { useColumns } from "./useTableColumns";
import useSearchQuery from "../../../api/utils/useSearchQuery";
import { useGetAllPackage } from "../../../api/package";
import { useGetAllPackageItem } from "../../../api/packageItem";
import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../../enums/params";
const App: React.FC = () => {
const [searchQuery] = useSearchQuery("name");
const {package_id} = useParams<ParamsEnum>()
const response = useGetAllPackageItem({
name: searchQuery,
package_id:package_id,
pagination: true,
});
return <DataTable response={response} useColumns={useColumns} />;
};
export default App;

View File

@ -0,0 +1,15 @@
import { useColumns } from "./useTableColumns";
import Table from "./Table";
import { FaPlus } from "react-icons/fa";
import AddModalForm from "./Model/AddModel";
import EditModalForm from "./Model/EditModel";
export {
Table,
useColumns,
AddModalForm,
EditModalForm,
FaPlus,
};

View File

@ -0,0 +1,66 @@
import { TableColumnsType } from "antd";
import { ModalEnum } from "../../../enums/Model";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useModalState } from "../../../zustand/Modal";
import { useTranslation } from "react-i18next";
import { canDeletePackage,canEditPackage } from "../../../utils/hasAbilityFn";
import ActionButtons from "../../../Components/Table/ActionButtons";
import { useNavigate } from "react-router-dom";
export const useColumns = () => {
const [t] = useTranslation();
const { setIsOpen } = useModalState((state) => state);
const navigate = useNavigate()
const { setObjectToEdit } = useObjectToEdit((state) => state);
const handelDelete = (record: any) => {
setObjectToEdit(record);
setIsOpen(ModalEnum?.PACKAGE_DELETE);
};
const handleEdit = (record: any) => {
setObjectToEdit(record);
setIsOpen(ModalEnum?.PACKAGE_EDIT);
navigate(`${record?.id}`)
};
const columns: TableColumnsType<any> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
},
{
title: t("columns.name"),
dataIndex: "name",
key: "name",
align: "center",
},
{
title: t("columns.price"),
dataIndex: "price",
key: "price",
align: "center",
},
{
title: "",
key: "actions",
align: "end",
width: "25vw",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canEditPackage}
canEdit={canDeletePackage}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
/>
);
},
},
];
return columns;
};

View File

@ -50,10 +50,8 @@ const TableHeader = () => {
<Table /> <Table />
<DeleteModalForm <DeleteModalForm
deleteMutation={deleteMutation} deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.Package_DELETE} ModelEnum={ModalEnum?.PACKAGE_DELETE}
/> />
{/* <AddModalForm /> */}
<EditModalForm />
</Suspense> </Suspense>
</div> </div>
); );

View File

@ -1,25 +1,33 @@
import { TableColumnsType } from "antd"; import { TableColumnsType } from "antd";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
import { useObjectToEdit } from "../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../zustand/ObjectToEditState";``
import { useModalState } from "../../zustand/Modal"; import { useModalState } from "../../zustand/Modal";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { canDeletePackage,canEditPackage } from "../../utils/hasAbilityFn"; import { canDeletePackage,canEditPackage, canShowPackage } from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons"; import ActionButtons from "../../Components/Table/ActionButtons";
import { useNavigate } from "react-router-dom";
import { ABILITIES_ENUM } from "../../enums/abilities";
export const useColumns = () => { export const useColumns = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const { setIsOpen } = useModalState((state) => state); const { setIsOpen } = useModalState((state) => state);
const navigate = useNavigate()
const { setObjectToEdit } = useObjectToEdit((state) => state); const { setObjectToEdit } = useObjectToEdit((state) => state);
const handelDelete = (record: any) => { const handelDelete = (record: any) => {
setObjectToEdit(record); setObjectToEdit(record);
setIsOpen(ModalEnum?.Package_DELETE); setIsOpen(ModalEnum?.PACKAGE_DELETE);
}; };
const handleEdit = (record: any) => { const handleEdit = (record: any) => {
setObjectToEdit(record); setObjectToEdit(record);
setIsOpen(ModalEnum?.Package_EDIT); navigate(`${record?.id}`)
}; };
// const handleShow = (record: any) => {
// setObjectToEdit(record);
// navigate(`${record?.id}/${ABILITIES_ENUM?.PACKAGE_ITEM}`)
// };
const columns: TableColumnsType<any> = [ const columns: TableColumnsType<any> = [
{ {
@ -50,9 +58,11 @@ export const useColumns = () => {
<ActionButtons <ActionButtons
canDelete={canEditPackage} canDelete={canEditPackage}
canEdit={canDeletePackage} canEdit={canDeletePackage}
// canShow={canShowPackage}
index={index} index={index}
onDelete={() => handelDelete(record)} onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)} onEdit={() => handleEdit(record)}
// onShow={() => handleShow(record)}
/> />
); );
}, },

View File

@ -0,0 +1,36 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import ModelForm from "./ModelForm";
import { useAddReSeller } from "../../../api/ReSeller";
const AddModel: React.FC = () => {
const { mutate, status } = useAddReSeller();
const handleSubmit = (values: any) => {
const newValue = JSON.parse(JSON.stringify({...values}))
const location = {lat:newValue?.lat,lng:newValue?.lng}
mutate({
...newValue,
location
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.RE_SELLER_ADD}
modelTitle="ReSeller"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})}
getValidationSchema={getValidationSchema}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default AddModel;

View File

@ -0,0 +1,40 @@
import React from "react";
import { getInitialValues, getValidationSchema, getValidationSchemaEdit } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import ModelForm from "./ModelForm";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useUpdateReSeller } from "../../../api/ReSeller";
import { handelImageState } from "../../../utils/DataToSendImageState";
const EditModel: React.FC = () => {
const { mutate, status } = useUpdateReSeller();
const { objectToEdit } = useObjectToEdit((state) => state);
const handleSubmit = (values: any) => {
const newValue = JSON.parse(JSON.stringify({...values}))
const location = {lat:newValue?.lat,lng:newValue?.lng}
mutate({
...newValue,
location
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.RE_SELLER_EDIT}
modelTitle="ReSeller"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchemaEdit}
isAddModal={false}
>
<ModelForm isEdit={true} />
</LayoutModel>
</>
);
};
export default EditModel;

View File

@ -0,0 +1,30 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../Components/ValidationField/ValidationField";
import MyMap from "./field/MyMap";
const Form = ({isEdit = false}:{isEdit?:boolean}) => {
return (
<Row className="w-100">
<Col>
<ValidationField name="first_name" placeholder="first_name" label="first_name" />
<ValidationField name="last_name" placeholder="last_name" label="last_name" />
<ValidationField name="username" placeholder="username" label="username" />
{!isEdit &&
<ValidationField name="password" placeholder="password" label="password" />
}
</Col>
<Col>
<ValidationField name="contact_number1" placeholder="contact_number1" label="contact_number1" />
<ValidationField name="contact_number2" placeholder="contact_number2" label="contact_number2" />
<ValidationField name="lat" placeholder="lat" label="lat" />
<ValidationField name="lng" placeholder="lng" label="lng" />
<MyMap/>
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,81 @@
import React, { useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, useMap } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import { Input, Button } from 'antd';
import { useFormikContext } from 'formik';
import { ReSellerInitialValues } from '../../../../types/ReSeller';
import { useTranslation } from 'react-i18next';
// Fix for marker icon issue
//@ts-ignore
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/leaflet/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/leaflet/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet/dist/images/marker-shadow.png',
});
const LocationMarker: React.FC = () => {
const { setFieldValue } = useFormikContext<ReSellerInitialValues>();
useMapEvents({
click(e) {
const { lat, lng } = e.latlng;
setFieldValue('lat', lat); // Update latitude in Formik
setFieldValue('lng', lng); // Update longitude in Formik
},
});
return null;
};
const CenterMapOnPosition: React.FC<{ position: [number, number] }> = ({ position }) => {
const map = useMap();
useEffect(() => {
map.setView(position, map.getZoom());
}, [position, map]);
return null;
};
const MyMap: React.FC = () => {
const [showMap, setShowMap] = useState(false); // State to control map visibility
const [currentPosition] = useState<[number, number] | null>(null); // State to hold current position
const { values } = useFormikContext<ReSellerInitialValues>();
const { lat, lng } = values as any;
const position: [number, number] = [lat, lng];
const [t] = useTranslation()
return (
<div className='mb-4'>
<div className='MapInputs '>
<Button onClick={() => setShowMap(!showMap)} type="primary">
{showMap ? `${t("practical.Hide")} ${t("practical.Map")}` : `${t("practical.Show")} ${t("practical.Map")}`}
</Button>
</div>
{showMap && (
<MapContainer
center={currentPosition || position} // Use currentPosition if available, otherwise fallback to form values
zoom={13}
style={{ height: "200px", width: "100%" }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={currentPosition || position}>
</Marker>
<LocationMarker />
<CenterMapOnPosition position={currentPosition || position} />
</MapContainer>
)}
</div>
);
};
export default MyMap;

View File

@ -0,0 +1,67 @@
import * as Yup from "yup";
import { ReSeller, ReSellerInitialValues } from "../../../types/ReSeller";
export const getInitialValues = (
objectToEdit: Partial<ReSeller>,
): ReSellerInitialValues => {
console.log(objectToEdit,"objectToEdit");
return {
id: objectToEdit?.id,
first_name: objectToEdit?.first_name ?? "",
last_name: objectToEdit?.last_name ?? "",
lat: objectToEdit?.location?.lat ?? 33.5138,
lng: objectToEdit?.location?.lng ?? 36.2765,
contact_number1:objectToEdit?.contact_number1 ?? null,
contact_number2:objectToEdit?.contact_number2 ?? null ,
username: objectToEdit?.user?.username ?? null ,
};
};
export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
first_name: Yup.string().required("validation.required"),
last_name: Yup.string().required("validation.required"),
contact_number1: Yup.string()
.matches(/^([0-9\s\-\+\(\)]*)$/, 'validation.Please enter a valid phone number (e.g., +1 123-456-7890)') // Regex for phone number
.min(10, "validation.must_be_at_least_10_characters_long") // Minimum length
.max(15, "validation.must_not_exceed_15_characters_long") // Maximum length
.required("validation.required"), // Required field
contact_number2: Yup.string()
.matches(/^([0-9\s\-\+\(\)]*)$/, 'validation.Please enter a valid phone number (e.g., +1 123-456-7890)') // Regex for phone number
.min(10, "validation.must_be_at_least_10_characters_long") // Minimum length
.max(15, "validation.must_not_exceed_15_characters_long") // Maximum length
.required("validation.required"), // Required field
password: Yup.string().min(8,"validation.Password_must_be_at_least_8_characters_long").required("validation.required"),
username: Yup.string().required("validation.required"),
lat: Yup.string().required("validation.required"),
lng: Yup.string().required("validation.required"),
});
};
export const getValidationSchemaEdit = () => {
// validate input
return Yup.object().shape({
first_name: Yup.string().required("validation.required"),
last_name: Yup.string().required("validation.required"),
contact_number1: Yup.string()
.matches(/^([0-9\s\-\+\(\)]*)$/, 'validation.Please enter a valid phone number (e.g., +1 123-456-7890)') // Regex for phone number
.min(10, "validation.must_be_at_least_10_characters_long") // Minimum length
.max(15, "validation.must_not_exceed_15_characters_long") // Maximum length
.required("validation.required"), // Required field
contact_number2: Yup.string()
.matches(/^([0-9\s\-\+\(\)]*)$/, 'validation.Please enter a valid phone number (e.g., +1 123-456-7890)') // Regex for phone number
.min(10, "validation.must_be_at_least_10_characters_long") // Minimum length
.max(15, "validation.must_not_exceed_15_characters_long") // Maximum length
.required("validation.required"), // Required field
username: Yup.string().required("validation.required"),
lat: Yup.string().required("validation.required"),
lng: Yup.string().required("validation.required"),
});
};

View File

@ -0,0 +1,44 @@
import { useTranslation } from "react-i18next";
import { lazy, Suspense } from "react";
import { Spin } from "antd";
import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { ModalEnum } from "../../enums/Model";
import { useDeleteReSeller } from "../../api/ReSeller";
import MyMap from "./Model/field/MyMap";
const Table = lazy(() => import("./Table"));
const AddModalForm = lazy(() => import("./Model/AddModel"));
const EditModalForm = lazy(() => import("./Model/EditModel"));
const DeleteModalForm = lazy(
() => import("../../Layout/Dashboard/DeleteModels"),
);
const TableHeader = () => {
const [t] = useTranslation();
const deleteMutation = useDeleteReSeller();
useSetPageTitle(t(`page_header.reSeller`));
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<header>
<h6>{t("models.ReSeller")}</h6>
</header>
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.RE_SELLER_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,12 @@
import { useColumns } from "./useTableColumns";
import React from "react";
import DataTable from "../../Layout/Dashboard/Table/DataTable";
import { useGetAllReSeller } from "../../api/ReSeller";
const App: React.FC = () => {
const response = useGetAllReSeller({ pagination: true });
return <DataTable response={response} useColumns={useColumns} />;
};
export default App;

View File

@ -0,0 +1,98 @@
import { TableColumnsType } from "antd";
import { ReSeller } from "../../types/ReSeller";
import { FaPlus } from "react-icons/fa";
import useModalHandler from "../../utils/useModalHandler";
import { ModalEnum } from "../../enums/Model";
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
import { useTranslation } from "react-i18next";
import { ABILITIES_ENUM } from "../../enums/abilities";
import { useNavigate } from "react-router-dom";
import {
canAddReSeller,
canDeleteReSeller,
canEditReSeller,
canShowReSeller,
} from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons";
import ColumnsImage from "../../Components/Columns/ColumnsImage";
export const useColumns = () => {
const { handel_open_model } = useModalHandler();
const { setObjectToEdit } = useObjectToEdit((state) => state);
const navigate = useNavigate();
const handelDelete = (data: ReSeller) => {
setObjectToEdit(data);
handel_open_model(ModalEnum?.RE_SELLER_DELETE);
};
const handleEdit = (record: ReSeller) => {
setObjectToEdit(record);
handel_open_model(ModalEnum?.RE_SELLER_EDIT);
};
const [t] = useTranslation();
const columns: TableColumnsType<ReSeller> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
render: (_text, record) => record?.id,
},
{
title: `${t("columns.first_name")}`,
dataIndex: "first_name",
key: "first_name",
align: "center",
render: (_text, record) => record?.first_name,
},
{
title: `${t("columns.last_name")}`,
dataIndex: "last_name",
key: "last_name",
align: "center",
render: (_text, record) => record?.last_name,
},
{
title: `${t("columns.username")}`,
dataIndex: "username",
key: "username",
align: "center",
render: (_text, record) => record?.user?.username,
},
{
title: canAddReSeller ? (
<button
onClick={() => handel_open_model(ModalEnum?.RE_SELLER_ADD)}
className="add_button"
>
{t("practical.add")} {t("models.ReSeller")} <FaPlus />
</button>
) : (
""
),
key: "actions",
align: "end",
width: "25vw",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canDeleteReSeller}
canEdit={canEditReSeller}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
/>
);
},
},
];
return columns;
};

View File

@ -0,0 +1,33 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import ModelForm from "./ModelForm";
import { useAddStudent } from "../../../api/student";
const AddModel: React.FC = () => {
const { mutate, status } = useAddStudent();
const handleSubmit = (values: any) => {
mutate({
...values,
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.STUDENT_ADD}
modelTitle="student"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})}
getValidationSchema={getValidationSchema}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default AddModel;

View File

@ -0,0 +1,37 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import ModelForm from "./ModelForm";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useUpdateStudent } from "../../../api/student";
import { handelImageState } from "../../../utils/DataToSendImageState";
const EditModel: React.FC = () => {
const { mutate, status } = useUpdateStudent();
const { objectToEdit } = useObjectToEdit((state) => state);
const handleSubmit = (values: any) => {
const Data_to_send = { ...values };
mutate(Data_to_send);
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.STUDENT_EDIT}
modelTitle="student"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema}
isAddModal={false}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default EditModel;

View File

@ -0,0 +1,58 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../Components/ValidationField/ValidationField";
import { useGetAllGrade } from "../../../api/grade";
import { useValidationValidationParamState } from "../../../Components/ValidationField/state/ValidationValidationParamState";
const Form = ({ isEdit = false }: { isEdit?: boolean }) => {
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
} = ValidationParamState;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
const sex = [
{name:"male" , id :"male"},
{name:"female" , id :"female"}
]
return (
<Row className="w-100">
<Col>
<ValidationField name="first_name" placeholder="first_name" label="first_name" />
<ValidationField name="last_name" placeholder="last_name" label="last_name" />
<ValidationField name="username" placeholder="username" label="username" />
{!isEdit &&
<ValidationField name="password" placeholder="password" label="password" />
}
</Col>
<Col>
<ValidationField name="phone_number" placeholder="contact_number1" label="contact_number1" />
<ValidationField
searchBy="GradeName"
name="grade_id"
label="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
<ValidationField type="Select" name="sex" option={sex} />
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,27 @@
import * as Yup from "yup";
import { Student, StudentInitialValues } from "../../../types/Student";
export const getInitialValues = (
objectToEdit: Partial<Student>,
): StudentInitialValues => {
return {
id: objectToEdit?.user_id,
first_name: objectToEdit?.first_name ?? "",
last_name: objectToEdit?.last_name ?? "",
// address: objectToEdit?.address ?? "",
// birthday: objectToEdit?.birthday ?? "",
// city: objectToEdit?.city ?? "",
grade_id: objectToEdit?.grade_id ,
// image: objectToEdit?.image ?? "",
sex: objectToEdit?.sex ,
};
};
export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
first_name: Yup.string().required("validation.required"),
last_name: Yup.string().required("validation.required"),
});
};

View File

@ -0,0 +1,39 @@
import { useTranslation } from "react-i18next";
import { lazy, Suspense } from "react";
import { Spin } from "antd";
import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { ModalEnum } from "../../enums/Model";
import { useDeleteStudent } from "../../api/student";
const Table = lazy(() => import("./Table"));
const AddModalForm = lazy(() => import("./Model/AddModel"));
const EditModalForm = lazy(() => import("./Model/EditModel"));
const DeleteModalForm = lazy(
() => import("../../Layout/Dashboard/DeleteModels"),
);
const TableHeader = () => {
const [t] = useTranslation();
const deleteMutation = useDeleteStudent();
useSetPageTitle(t(`page_header.student`));
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<header>
<h6>{t("models.student")}</h6>
</header>
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.STUDENT_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,13 @@
import { useColumns } from "./useTableColumns";
import React from "react";
import DataTable from "../../Layout/Dashboard/Table/DataTable";
import { useGetAllStudent } from "../../api/student";
const App: React.FC = () => {
const response = useGetAllStudent({ pagination: true });
return <DataTable response={response} useColumns={useColumns} />;
};
export default App;

View File

@ -0,0 +1,101 @@
import { TableColumnsType } from "antd";
import { Student } from "../../types/Student";
import { FaPlus } from "react-icons/fa";
import useModalHandler from "../../utils/useModalHandler";
import { ModalEnum } from "../../enums/Model";
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import {
canAddStudent,
canDeleteStudent,
canEditStudent,
canShowStudent,
} from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons";
export const useColumns = () => {
const { handel_open_model } = useModalHandler();
const { setObjectToEdit } = useObjectToEdit((state) => state);
const navigate = useNavigate();
const handelShow = (record: Student) => {
navigate(`${record?.user_id}`);
};
const handelDelete = (data: Student) => {
setObjectToEdit(data);
handel_open_model(ModalEnum?.STUDENT_DELETE);
};
const handleEdit = (record: Student) => {
setObjectToEdit(record);
handel_open_model(ModalEnum?.STUDENT_EDIT);
};
const [t] = useTranslation();
const columns: TableColumnsType<Student> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
render: (_text, record) => record?.user_id,
},
{
title: `${t("columns.first_name")}`,
dataIndex: "first_name",
key: "first_name",
align: "center",
render: (_text, record) => record?.first_name,
},
{
title: `${t("columns.last_name")}`,
dataIndex: "last_name",
key: "last_name",
align: "center",
render: (_text, record) => record?.last_name,
},
{
title: `${t("columns.sex")}`,
dataIndex: "sex",
key: "sex",
align: "center",
render: (_text, record) => record?.sex,
},
{
title: canAddStudent ? (
<button
onClick={() => handel_open_model(ModalEnum?.STUDENT_ADD)}
className="add_button"
>
{t("practical.add")} {t("models.student")} <FaPlus />
</button>
) : (
""
),
key: "actions",
align: "end",
width: "25vw",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canDeleteStudent}
canEdit={canEditStudent}
canShow={canShowStudent}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
onShow={() => handelShow(record)}
/>
);
},
},
];
return columns;
};

View File

@ -12,10 +12,13 @@ import {
} from "@dnd-kit/sortable"; } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import { Button, Table } from "antd"; import { Button, Table } from "antd";
import type { TableColumnsType } from "antd";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../enums/params"; import { ParamsEnum } from "../../enums/params";
import { useGetAllUnit } from "../../api/unit"; import { useGetAllUnit, useUpdateUnitOrder } from "../../api/unit";
import Loading from "../../Components/DataState/Loading";
import EmptyData from "../../Components/DataState/EmptyData";
import { useTranslation } from "react-i18next";
import { useColumns } from "./useTableColumns";
interface DataType { interface DataType {
id: string; // Unique identifier for each row id: string; // Unique identifier for each row
@ -32,7 +35,7 @@ interface RowContextProps {
const RowContext = React.createContext<RowContextProps>({}); const RowContext = React.createContext<RowContextProps>({});
const DragHandle: React.FC = () => { export const DragHandleUnit: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext); const { setActivatorNodeRef, listeners } = useContext(RowContext);
return ( return (
<Button <Button
@ -46,12 +49,6 @@ const DragHandle: React.FC = () => {
); );
}; };
const columns: TableColumnsType<DataType> = [
{ key: "sort", align: "center", width: 80, render: () => <DragHandle /> },
{ title: "Name", dataIndex: "name" },
{ title: "Age", dataIndex: "age" },
{ title: "Address", dataIndex: "address" },
];
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> { interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
"data-row-key": string; "data-row-key": string;
@ -96,13 +93,10 @@ const DrapableTable: React.FC = () => {
response?.data?.data?.map((item: any, index: number) => ({ response?.data?.data?.map((item: any, index: number) => ({
id: item.id, // Ensure this is a unique identifier id: item.id, // Ensure this is a unique identifier
order: index + 1, // Assign order based on index order: index + 1, // Assign order based on index
name: item.name, ...item
age: item.age,
address: item.address,
})) ?? []; })) ?? [];
const [dataSource, setDataSource] = React.useState<DataType[]>(data); const [dataSource, setDataSource] = React.useState<DataType[]>(data);
console.log(dataSource, "dataSource");
useEffect(() => { useEffect(() => {
// Update dataSource when the fetched data changes // Update dataSource when the fetched data changes
@ -110,28 +104,51 @@ const DrapableTable: React.FC = () => {
setDataSource(sortedData); setDataSource(sortedData);
}, [response?.data?.data]); }, [response?.data?.data]);
const {mutate:orderUnit} = useUpdateUnitOrder({},{
retry:false
})
const onDragEnd = ({ active, over }: DragEndEvent) => { const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) { if (active.id !== over?.id) {
setDataSource((prevState) => { setDataSource((prevState) => {
const activeIndex = prevState.findIndex( const activeIndex = prevState.findIndex(
(record) => record.id === active.id, (record) => record.id === active.id,
); );
const overIndex = prevState.findIndex( const overIndex = prevState.findIndex(
//@ts-ignore
(record) => record.id === over.id, (record) => record.id === over.id,
); );
// Move the items in the array // Move the items in the array
const newState = arrayMove(prevState, activeIndex, overIndex); const newState = arrayMove(prevState, activeIndex, overIndex);
const orderedNewState = newState.map((item, index) => ({
...item,
order: index + 1, // Update the order based on the new index
}));
// Update the order based on the new positions // Update the order based on the new positions
return newState.map((item, index) => ({ const orderedNewStateWithNewChape = orderedNewState?.map((item:any)=>{
...item, return {
order: index + 1, // Update the order based on the new index "unit_id":item?.id,
})); "order":item?.order
}
})
orderUnit({units: orderedNewStateWithNewChape, _method:"PUT"})
return orderedNewState
}); });
} }
}; };
const getRowClassName = (record: any, index: number): string => {
return index % 2 === 0 ? "even-row" : "odd-row";
};
const isRefetching = response?.isRefetching;
const [t] = useTranslation()
const columns = useColumns();
const sortedDataSource = dataSource.sort((a, b) => a.order - b.order) ;
console.log(sortedDataSource,"sortedDataSource");
return ( return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}> <DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext <SortableContext
@ -141,9 +158,26 @@ const DrapableTable: React.FC = () => {
<Table <Table
rowKey="id" rowKey="id"
components={{ body: { row: Row } }} components={{ body: { row: Row } }}
//@ts-ignore
columns={columns} columns={columns}
dataSource={dataSource.sort((a, b) => a.order - b.order)} // Sort by order for rendering dataSource={sortedDataSource}
pagination={false} pagination={false}
rowClassName={(record, index) => getRowClassName(record, index)}
className="DataTable"
loading={{
spinning: response?.isLoading || isRefetching,
indicator: <Loading />,
size: "large",
}}
locale={{
emptyText: (
<EmptyData
loading={response?.isLoading}
header={t("Table.header")}
info={t("Table.info")}
/>
),
}}
/> />
</SortableContext> </SortableContext>
</DndContext> </DndContext>

View File

@ -19,6 +19,7 @@ const Form = () => {
placeholder="term" placeholder="term"
label="term" label="term"
option={termsArray} option={termsArray}
fieldNames={{label:"label",value:"value"}}
/> />
</Col> </Col>
</Row> </Row>

View File

@ -60,7 +60,7 @@ const TableHeader = () => {
<header> <header>
<h6>{t("models.units")}</h6> <h6>{t("models.units")}</h6>
</header> </header>
<Table /> <DrapableTable />
<AddModalForm /> <AddModalForm />
<EditModalForm /> <EditModalForm />
<DeleteModalForm <DeleteModalForm

View File

@ -17,7 +17,7 @@ import {
} from "../../utils/hasAbilityFn"; } from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons"; import ActionButtons from "../../Components/Table/ActionButtons";
import { Unit } from "../../types/Unit"; import { Unit } from "../../types/Unit";
import { DragHandleUnit } from "./DrapableTable";
export const useColumns = () => { export const useColumns = () => {
const { handel_open_model } = useModalHandler(); const { handel_open_model } = useModalHandler();
@ -40,6 +40,7 @@ export const useColumns = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const columns: TableColumnsType<Unit> = [ const columns: TableColumnsType<Unit> = [
{ key: "sort", align: "center", width: 80, render: () => <DragHandleUnit /> },
{ {
title: t("columns.id"), title: t("columns.id"),
dataIndex: "id", dataIndex: "id",
@ -60,7 +61,13 @@ export const useColumns = () => {
dataIndex: "term", dataIndex: "term",
key: "term", key: "term",
align: "center", align: "center",
render: (text, record) => record?.term, render: (text, record) => {
console.log(record);
return (
record?.term
)
},
}, },
{ {

View File

@ -0,0 +1,185 @@
import React, { useContext, useEffect, useMemo } from "react";
import { HolderOutlined } from "@ant-design/icons";
import type { DragEndEvent } from "@dnd-kit/core";
import { DndContext } from "@dnd-kit/core";
import type { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Button, Table } from "antd";
import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../enums/params";
import { useGetAllLesson, useUpdateLessonOrder } from "../../api/lesson";
import Loading from "../../Components/DataState/Loading";
import EmptyData from "../../Components/DataState/EmptyData";
import { useTranslation } from "react-i18next";
import { useColumns } from "./useTableColumns";
interface DataType {
id: string; // Unique identifier for each row
order: number;
name: string;
age: number;
address: string;
}
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
export const DragHandleLesson: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: "move" }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
"data-row-key": string;
}
const Row: React.FC<RowProps> = (props) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: props["data-row-key"] });
const style: React.CSSProperties = {
...props.style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: "relative", zIndex: 9999 } : {}),
};
const contextValue = useMemo<RowContextProps>(
() => ({ setActivatorNodeRef, listeners }),
[setActivatorNodeRef, listeners],
);
return (
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};
const DrapableTable: React.FC = () => {
const { subject_id } = useParams<ParamsEnum>();
const response = useGetAllLesson({ subject_id: subject_id, pagination: false });
// Assuming the response contains a unique id for each item
const data =
response?.data?.data?.map((item: any, index: number) => ({
id: item.id, // Ensure this is a unique identifier
order: index + 1, // Assign order based on index
...item
})) ?? [];
const [dataSource, setDataSource] = React.useState<DataType[]>(data);
useEffect(() => {
// Update dataSource when the fetched data changes
const sortedData = data.sort((a: any, b: any) => a.order - b.order);
setDataSource(sortedData);
}, [response?.data?.data]);
const {mutate:orderLesson} = useUpdateLessonOrder()
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setDataSource((prevState) => {
const activeIndex = prevState.findIndex(
(record) => record.id === active.id,
);
const overIndex = prevState.findIndex(
//@ts-ignore
(record) => record.id === over.id,
);
// Move the items in the array
const newState = arrayMove(prevState, activeIndex, overIndex);
const orderedNewState = newState.map((item, index) => ({
...item,
order: index + 1, // Update the order based on the new index
}));
// Update the order based on the new positions
const orderedNewStateWithNewChape = orderedNewState?.map((item:any)=>{
return {
"lesson_id":item?.id,
"order":item?.order
}
})
orderLesson({lessons: orderedNewStateWithNewChape, _method:"PUT"})
return orderedNewState
});
}
};
const getRowClassName = (record: any, index: number): string => {
return index % 2 === 0 ? "even-row" : "odd-row";
};
const isRefetching = response?.isRefetching;
const [t] = useTranslation()
const columns = useColumns();
const sortedDataSource = dataSource.sort((a, b) => a.order - b.order) ;
console.log(sortedDataSource,"sortedDataSource");
return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext
items={dataSource.map((i) => i.id)}
strategy={verticalListSortingStrategy}
>
<Table
rowKey="id"
components={{ body: { row: Row } }}
//@ts-ignore
columns={columns}
dataSource={sortedDataSource}
pagination={false}
rowClassName={(record, index) => getRowClassName(record, index)}
className="DataTable"
loading={{
spinning: response?.isLoading || isRefetching,
indicator: <Loading />,
size: "large",
}}
locale={{
emptyText: (
<EmptyData
loading={response?.isLoading}
header={t("Table.header")}
info={t("Table.info")}
/>
),
}}
/>
</SortableContext>
</DndContext>
);
};
export default DrapableTable;

View File

@ -12,14 +12,14 @@ import { useModalState } from "../../../zustand/Modal";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
const AddModel: React.FC = () => { const AddModel: React.FC = () => {
const { isOpen, setIsOpen } = useModalState((state) => state); const { setIsOpen } = useModalState((state) => state);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate, isSuccess, status } = useAddLesson(); const { mutate, isSuccess, status } = useAddLesson();
const { OldObjectToEdit } = useObjectToEdit(); const { OldObjectToEdit } = useObjectToEdit();
const { unit_id } = useParams<ParamsEnum>(); const { unit_id } = useParams<ParamsEnum>();
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
setIsOpen(""); setIsOpen("isSuccess");
queryClient.invalidateQueries(["Lesson"]); queryClient.invalidateQueries(["Lesson"]);
} }
}, [setIsOpen, isSuccess, queryClient]); }, [setIsOpen, isSuccess, queryClient]);
@ -40,8 +40,9 @@ const AddModel: React.FC = () => {
ModelEnum={ModalEnum.LESSON_ADD} ModelEnum={ModalEnum.LESSON_ADD}
modelTitle="lesson" modelTitle="lesson"
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})} getInitialValues={getInitialValues({name:null})}
getValidationSchema={getValidationSchema} getValidationSchema={getValidationSchema}
width="40vw"
> >
<ModelForm /> <ModelForm />
</LayoutModel> </LayoutModel>

View File

@ -14,7 +14,7 @@ const ModalForm: React.FC = () => {
const { isOpen, setIsOpen } = useModalState((state) => state); const { isOpen, setIsOpen } = useModalState((state) => state);
const { mutate, isSuccess, status } = useUpdateLesson(); const { mutate, isSuccess, status } = useUpdateLesson();
const { objectToEdit, setObjectToEdit } = useObjectToEdit(); const { objectToEdit } = useObjectToEdit();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -43,6 +43,7 @@ const ModalForm: React.FC = () => {
getInitialValues={getInitialValues(objectToEdit)} getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema} getValidationSchema={getValidationSchema}
isAddModal={false} isAddModal={false}
width="40vw"
> >
<ModelForm /> <ModelForm />
</LayoutModel> </LayoutModel>

View File

@ -1,9 +1,11 @@
import * as Yup from "yup"; import * as Yup from "yup";
export const getInitialValues = (objectToEdit: any): any => { export const getInitialValues = (objectToEdit: any): any => {
console.log(objectToEdit,"objectToEdit");
return { return {
id: objectToEdit?.id ?? null, id: objectToEdit?.id ?? null,
name: objectToEdit?.name ?? "", name: objectToEdit?.name ?? null,
unit_id: objectToEdit?.term ?? null, unit_id: objectToEdit?.term ?? null,
}; };
}; };

View File

@ -11,7 +11,7 @@ import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum"; import { useGetAllCurriculum } from "../../api/curriculum";
import { useGetAllSubject } from "../../api/subject"; import { useGetAllSubject } from "../../api/subject";
const Table = lazy(() => import("./Table")); const Table = lazy(() => import("./DrapableTable"));
const AddModalForm = lazy(() => import("./Model/AddModel")); const AddModalForm = lazy(() => import("./Model/AddModel"));
const EditModalForm = lazy(() => import("./Model/EditModel")); const EditModalForm = lazy(() => import("./Model/EditModel"));
const DeleteModelsForm = lazy( const DeleteModelsForm = lazy(

View File

@ -14,6 +14,7 @@ import {
canShowLesson, canShowLesson,
} from "../../utils/hasAbilityFn"; } from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons"; import ActionButtons from "../../Components/Table/ActionButtons";
import { DragHandleLesson } from "./DrapableTable";
export const useColumns = () => { export const useColumns = () => {
const { handel_open_model } = useModalHandler(); const { handel_open_model } = useModalHandler();
@ -37,6 +38,7 @@ export const useColumns = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const columns: TableColumnsType<Lesson> = [ const columns: TableColumnsType<Lesson> = [
{ key: "sort", align: "center", width: 80, render: () => <DragHandleLesson /> },
{ {
title: t("columns.id"), title: t("columns.id"),
dataIndex: "id", dataIndex: "id",

View File

@ -0,0 +1,33 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import ModelForm from "./ModelForm";
import { useAddStudentPackage } from "../../../api/StudentPackage";
const AddModel: React.FC = () => {
const { mutate, status } = useAddStudentPackage();
const handleSubmit = (values: any) => {
mutate({
...values,
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.STUDENT_PACKAGE_ADD}
modelTitle="StudentPackage"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})}
getValidationSchema={getValidationSchema}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default AddModel;

View File

@ -0,0 +1,38 @@
import React from "react";
import { getInitialValues, getValidationSchema } from "./formUtil";
import { ModalEnum } from "../../../enums/Model";
import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import ModelForm from "./ModelForm";
import { QueryStatusEnum } from "../../../enums/QueryStatus";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useUpdateStudentPackage } from "../../../api/StudentPackage";
import { handelImageState } from "../../../utils/DataToSendImageState";
const EditModel: React.FC = () => {
const { mutate, status } = useUpdateStudentPackage();
const { objectToEdit } = useObjectToEdit((state) => state);
const handleSubmit = (values: any) => {
const Data_to_send = { ...values };
const handelImage = handelImageState(Data_to_send, "icon");
mutate(handelImage);
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.STUDENT_PACKAGE_EDIT}
modelTitle="StudentPackage"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema}
isAddModal={false}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default EditModel;

View File

@ -0,0 +1,71 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../Components/ValidationField/ValidationField";
import { useGetAllPackage } from "../../../api/package";
import { useValidationValidationParamState } from "../../../Components/ValidationField/state/ValidationValidationParamState";
import { useGetAllStudent } from "../../../api/student";
const Form = () => {
const { ValidationParamState } = useValidationValidationParamState();
const {
PackageName, PackageCurrentPage,
StudentName, StudentCurrentPage,
} = ValidationParamState;
/// Package_id
const { data: Package, isLoading: isLoadingPackage } = useGetAllPackage({
name: PackageName,
page: PackageCurrentPage
});
const PackageOption = Package?.data ?? []
const canChangePackagePage = !!Package?.links?.next;
const PackagePage = Package?.meta?.currentPage;
/// Student_id
const { data: Student, isLoading: isLoadingStudent } = useGetAllStudent({
name: StudentName,
page: StudentCurrentPage
});
const StudentOption = Student?.data ?? []
const canChangeStudentPage = !!Student?.links?.next;
const StudentPage = Student?.meta?.currentPage;
return (
<Row className="w-100">
<Col>
<ValidationField
searchBy="StudentName"
name="student_id"
label="Student"
type="Search"
option={StudentOption}
isLoading={isLoadingStudent}
canChangePage={canChangeStudentPage}
PageName={"StudentCurrentPage"}
page={StudentPage}
/>
<ValidationField
searchBy="PackageName"
name="package_id"
label="Package"
type="Search"
option={PackageOption}
isLoading={isLoadingPackage}
canChangePage={canChangePackagePage}
PageName={"PackageCurrentPage"}
page={PackagePage}
/>
</Col>
<Col>
<ValidationField name="activation_date" type="Date" />
<ValidationField name="expiration_date" type="Date" />
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,23 @@
import * as Yup from "yup";
import { StudentPackage, StudentPackageInitialValues } from "../../../types/studentPackage";
import { formateDateInitialValue } from "../../../utils/formateDateInitialValue";
export const getInitialValues = (
objectToEdit: Partial<StudentPackage>,
): StudentPackageInitialValues => {
return {
id: objectToEdit?.id ?? null,
package_id: objectToEdit?.package?.id ?? null,
student_id: objectToEdit?.student?.user_id ?? null,
activation_date: formateDateInitialValue(objectToEdit?.activation_date) ,
expiration_date: formateDateInitialValue(objectToEdit?.expiration_date) ,
};
};
export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
name: Yup.string().required("validation.required"),
});
};

View File

@ -0,0 +1,39 @@
import { useTranslation } from "react-i18next";
import { lazy, Suspense } from "react";
import { Spin } from "antd";
import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { ModalEnum } from "../../enums/Model";
import { useDeleteStudentPackage } from "../../api/StudentPackage";
const Table = lazy(() => import("./Table"));
const AddModalForm = lazy(() => import("./Model/AddModel"));
const EditModalForm = lazy(() => import("./Model/EditModel"));
const DeleteModalForm = lazy(
() => import("../../Layout/Dashboard/DeleteModels"),
);
const TableHeader = () => {
const [t] = useTranslation();
const deleteMutation = useDeleteStudentPackage();
useSetPageTitle(t(`page_header.studentPackage`));
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<header>
<h6>{t("models.StudentPackage")}</h6>
</header>
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.STUDENT_PACKAGE_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,12 @@
import { useColumns } from "./useTableColumns";
import React from "react";
import DataTable from "../../Layout/Dashboard/Table/DataTable";
import { useGetAllStudentPackage } from "../../api/StudentPackage";
const App: React.FC = () => {
const response = useGetAllStudentPackage({ pagination: true });
return <DataTable response={response} useColumns={useColumns} />;
};
export default App;

View File

@ -0,0 +1,87 @@
import { TableColumnsType } from "antd";
import { StudentPackage } from "../../types/studentPackage";
import { FaPlus } from "react-icons/fa";
import useModalHandler from "../../utils/useModalHandler";
import { ModalEnum } from "../../enums/Model";
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
import { useTranslation } from "react-i18next";
import { ABILITIES_ENUM } from "../../enums/abilities";
import { useNavigate } from "react-router-dom";
import {
canAddStudentPackage,
canDeleteStudentPackage,
canEditStudentPackage,
canShowStudentPackage,
} from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons";
import ColumnsImage from "../../Components/Columns/ColumnsImage";
export const useColumns = () => {
const { handel_open_model } = useModalHandler();
const { setObjectToEdit } = useObjectToEdit((state) => state);
const navigate = useNavigate();
const handelShow = (record: StudentPackage) => {
navigate(`${record?.id}`);
};
const handelDelete = (data: StudentPackage) => {
setObjectToEdit(data);
handel_open_model(ModalEnum?.STUDENT_PACKAGE_DELETE);
};
const handleEdit = (record: StudentPackage) => {
setObjectToEdit(record);
handel_open_model(ModalEnum?.STUDENT_PACKAGE_EDIT);
};
const [t] = useTranslation();
const columns: TableColumnsType<StudentPackage> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
render: (_text, record) => record?.id,
},
{
title: `${t("columns.name")}`,
dataIndex: "name",
key: "name",
align: "center",
render: (_text, record) => record?.name,
},
{
title: canAddStudentPackage ? (
<button
onClick={() => handel_open_model(ModalEnum?.STUDENT_PACKAGE_ADD)}
className="add_button"
>
{t("practical.add")} {t("models.StudentPackage")} <FaPlus />
</button>
) : (
""
),
key: "actions",
align: "end",
width: "25vw",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canDeleteStudentPackage}
canEdit={canEditStudentPackage}
canShow={canShowStudentPackage}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
onShow={() => handelShow(record)}
/>
);
},
},
];
return columns;
};

View File

@ -1,5 +1,5 @@
import { TCrudRoute, TMenuItem } from "./types/App"; import { TCrudRoute, TMenuItem } from "./types/App";
import { FaHome, FaMoneyBill } from "react-icons/fa"; import { FaHome, FaKey, FaMoneyBill, FaSellcast, FaUserGraduate } from "react-icons/fa";
import { LuPackage } from "react-icons/lu"; import { LuPackage } from "react-icons/lu";
import React from "react"; import React from "react";
const Dummy = React.lazy(() => import("./Pages/Home/Dummy")); const Dummy = React.lazy(() => import("./Pages/Home/Dummy"));
@ -10,6 +10,10 @@ const Tags = React.lazy(() => import("./Pages/Tags/Page"));
const Grade = React.lazy(() => import("./Pages/Grade/Page")); const Grade = React.lazy(() => import("./Pages/Grade/Page"));
const Package = React.lazy(() => import("./Pages/Package/Page")); const Package = React.lazy(() => import("./Pages/Package/Page"));
const Curriculum = React.lazy(() => import("./Pages/Curriculum/Page")); const Curriculum = React.lazy(() => import("./Pages/Curriculum/Page"));
const PackageItemPage = React.lazy(() => import("./Pages/Package/PackageItem/Page"));
const ReSeller = React.lazy(() => import("./Pages/ReSeller/Page"));
const StudentPackage = React.lazy(() => import("./Pages/studentPackage/Page"));
const Student = React.lazy(() => import("./Pages/Student/Page"));
const Unit = React.lazy(() => import("./Pages/Unit/Page")); const Unit = React.lazy(() => import("./Pages/Unit/Page"));
const Lesson = React.lazy(() => import("./Pages/lesson/Page")); const Lesson = React.lazy(() => import("./Pages/lesson/Page"));
@ -19,9 +23,15 @@ const EditQuestionPage = React.lazy(() => import("./Pages/question/EditPage"));
const AddPackagePage = React.lazy(() => import("./Pages/Package/Model/AddModel")); const AddPackagePage = React.lazy(() => import("./Pages/Package/Model/AddModel"));
const EditPackagePage = React.lazy(() => import("./Pages/Package/Model/EditModel")); const EditPackagePage = React.lazy(() => import("./Pages/Package/Model/EditModel"));
const AddPackageItemPage = React.lazy(() => import("./Pages/Package/PackageItem/Model/AddModel"));
const EditPackageItemPage = React.lazy(() => import("./Pages/Package/PackageItem/Model/EditModel"));
import { hasAbility } from "./utils/hasAbility"; import { hasAbility } from "./utils/hasAbility";
import { ABILITIES_ENUM, ABILITIES_VALUES_ENUM } from "./enums/abilities"; import { ABILITIES_ENUM, ABILITIES_VALUES_ENUM } from "./enums/abilities";
import { ParamsEnum } from "./enums/params"; import { ParamsEnum } from "./enums/params";
import { BsPeople } from "react-icons/bs";
import { UserTypeEnum } from "./enums/UserType";
export const menuItems: TMenuItem[] = [ export const menuItems: TMenuItem[] = [
{ {
@ -33,12 +43,13 @@ export const menuItems: TMenuItem[] = [
abilities: ABILITIES_ENUM?.PASS, abilities: ABILITIES_ENUM?.PASS,
abilities_value: ABILITIES_VALUES_ENUM.INDEX, abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0, prevPath: 0,
type:UserTypeEnum?.PASS
}, },
{ {
header: "page_header.grade", header: "page_header.grade",
element: <Grade />, element: <Grade />,
icon: <FaMoneyBill />, icon: <FaUserGraduate />,
text: "sidebar.grade", text: "sidebar.grade",
path: `/${ABILITIES_ENUM?.GRADE}`, path: `/${ABILITIES_ENUM?.GRADE}`,
abilities: ABILITIES_ENUM?.GRADE, abilities: ABILITIES_ENUM?.GRADE,
@ -68,13 +79,45 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.tags", header: "page_header.tags",
element: <Tags />, element: <Tags />,
icon: <FaMoneyBill />, icon: <FaKey />,
text: "sidebar.tags", text: "sidebar.tags",
path: `/${ABILITIES_ENUM?.TAG}`, path: `/${ABILITIES_ENUM?.TAG}`,
abilities: ABILITIES_ENUM?.TAG, abilities: ABILITIES_ENUM?.TAG,
abilities_value: ABILITIES_VALUES_ENUM.INDEX, abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0, prevPath: 0,
}, },
{
header: "page_header.reSeller",
element: <ReSeller />,
icon: <FaSellcast />,
text: "sidebar.reSeller",
path: `/${ABILITIES_ENUM?.RE_SELLER}`,
abilities: ABILITIES_ENUM?.RE_SELLER,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0,
},
{
header: "page_header.student",
element: <Student />,
icon: <FaSellcast />,
text: "sidebar.student",
path: `/${ABILITIES_ENUM?.STUDENT}`,
abilities: ABILITIES_ENUM?.STUDENT,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0,
},
{
header: "page_header.studentPackage",
element: <StudentPackage />,
icon: <BsPeople />,
text: "sidebar.studentPackage",
path: `/${ABILITIES_ENUM?.STUDENT_PACKAGE}`,
abilities: ABILITIES_ENUM?.STUDENT_PACKAGE,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0,
type:UserTypeEnum.RE_SELLER
},
]; ];
export const CrudRoute: TCrudRoute[] = [ export const CrudRoute: TCrudRoute[] = [
@ -143,16 +186,44 @@ export const CrudRoute: TCrudRoute[] = [
path: `/${ABILITIES_ENUM?.Package}/add`, path: `/${ABILITIES_ENUM?.Package}/add`,
abilities: ABILITIES_ENUM?.Package, abilities: ABILITIES_ENUM?.Package,
abilities_value: ABILITIES_VALUES_ENUM.INDEX, abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0, prevPath: 1,
}, },
{ {
header: "page_header.edit_package", header: "page_header.edit_package",
element: <EditPackagePage />, element: <EditPackagePage />,
path: `/${ABILITIES_ENUM?.Package}/add`, path: `/${ABILITIES_ENUM?.Package}/:${ParamsEnum?.PACKAGE_ID}`,
abilities: ABILITIES_ENUM?.Package, abilities: ABILITIES_ENUM?.Package,
abilities_value: ABILITIES_VALUES_ENUM.INDEX, abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0, prevPath: 1,
}, },
{
header: "page_header.package_item",
element: <PackageItemPage />,
path: `/${ABILITIES_ENUM?.Package}/:${ParamsEnum?.PACKAGE_ID}/${ABILITIES_ENUM?.PACKAGE_ITEM}`,
abilities: ABILITIES_ENUM?.PACKAGE_ITEM,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 1,
},
{
header: "page_header.add_package_item",
element: <AddPackageItemPage />,
path: `/${ABILITIES_ENUM?.Package}/:${ParamsEnum?.PACKAGE_ID}/${ABILITIES_ENUM?.PACKAGE_ITEM}/add`,
abilities: ABILITIES_ENUM?.PACKAGE_ITEM,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 1,
},
{
header: "page_header.edit_package_item",
element: <EditPackageItemPage />,
path: `/${ABILITIES_ENUM?.Package}/:${ParamsEnum?.PACKAGE_ID}/${ABILITIES_ENUM?.PACKAGE_ITEM}/:${ParamsEnum?.PACKAGE_ITEM_ID}`,
abilities: ABILITIES_ENUM?.PACKAGE_ITEM,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 1,
},
]; ];
export const AppRoutes: Record<string, string> = Object.fromEntries( export const AppRoutes: Record<string, string> = Object.fromEntries(

View File

@ -0,0 +1,9 @@
.MapInputs{
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.leaflet-right{
display: none;
}

View File

@ -23,7 +23,7 @@
.exercise_add_main { .exercise_add_main {
background: var(--bg); background: var(--bg);
padding: 2vw; padding: 2vw;
} };
.exercise_add_buttons { .exercise_add_buttons {
display: flex; display: flex;
gap: 2%; gap: 2%;
@ -59,6 +59,7 @@
.add_new_button { .add_new_button {
margin-bottom: 0; margin-bottom: 0;
color: var(--primary) !important;
} }
} }
.tags { .tags {
@ -125,3 +126,11 @@
display: flex; display: flex;
gap: 5px; gap: 5px;
} }
.add_new_button{
margin-bottom: 20px;
svg{
color: var(--primary);
}
}

View File

@ -10,3 +10,4 @@
@import "./subject.scss"; @import "./subject.scss";
@import "./Marks.scss"; @import "./Marks.scss";
@import "./exercise.scss"; @import "./exercise.scss";
@import './Map.scss';

21
src/api/ReSeller.ts Normal file
View File

@ -0,0 +1,21 @@
import useAddMutation from "./helper/useAddMutation";
import useDeleteMutation from "./helper/useDeleteMutation";
import useGetQuery from "./helper/useGetQuery";
import useUpdateMutation from "./helper/useUpdateMutation";
const API = {
GET: "/reseller",
ADD: "/reseller",
DELETE: "/reseller",
UPDATE: "/reseller",
};
const KEY = "ReSeller";
export const useGetAllReSeller = (params?: any, options?: any) =>
useGetQuery(KEY, API.GET, params, options);
export const useAddReSeller = () => useAddMutation(KEY, API.ADD);
export const useUpdateReSeller = (params?: any) =>
useUpdateMutation(KEY, API.GET);
export const useDeleteReSeller = (params?: any) =>
useDeleteMutation(KEY, API.DELETE);

21
src/api/StudentPackage.ts Normal file
View File

@ -0,0 +1,21 @@
import useAddMutation from "./helper/useAddMutation";
import useDeleteMutation from "./helper/useDeleteMutation";
import useGetQuery from "./helper/useGetQuery";
import useUpdateMutation from "./helper/useUpdateMutation";
const API = {
GET: "/resellers/studentPackage",
ADD: "/resellers/studentPackage",
DELETE: "/resellers/studentPackage",
UPDATE: "/resellers/studentPackage",
};
const KEY = "StudentPackage";
export const useGetAllStudentPackage = (params?: any, options?: any) =>
useGetQuery(KEY, API.GET, params, options);
export const useAddStudentPackage = () => useAddMutation(KEY, API.ADD);
export const useUpdateStudentPackage = (params?: any) =>
useUpdateMutation(KEY, API.GET);
export const useDeleteStudentPackage = (params?: any) =>
useDeleteMutation(KEY, API.DELETE);

View File

@ -1,4 +1,4 @@
export const BaseURL = "http://127.0.0.1:8000/api/"; export const BaseURL = "https://nerd-back.point-dev.net/api/";
// export const BaseURL = "http://127.0.0.1:8000/api/"; // export const BaseURL = "http://127.0.0.1:8000/api/";
// export const BaseURL = "http://192.168.1.120:8000/api/"; // export const BaseURL = "http://192.168.1.120:8000/api/";

View File

@ -8,6 +8,8 @@ function useAddMutation(
key: string, key: string,
url: string, url: string,
toast: boolean = true, toast: boolean = true,
params: any = {},
options: any = {},
): UseMutationResult<AxiosResponse, unknown, any, unknown> { ): UseMutationResult<AxiosResponse, unknown, any, unknown> {
const axios = useAxios(); const axios = useAxios();
return useMutation<AxiosResponse, unknown, any, unknown>( return useMutation<AxiosResponse, unknown, any, unknown>(
@ -20,9 +22,12 @@ function useAddMutation(
["X-Custom-Message"]: toast, ["X-Custom-Message"]: toast,
[HEADER_KEY]: key, [HEADER_KEY]: key,
}, },
}); });
return data; return data;
}, },
options,
); );
} }

View File

@ -8,6 +8,7 @@ const API = {
ADD: "/lesson", ADD: "/lesson",
DELETE: "/lesson", DELETE: "/lesson",
UPDATE: "/lesson", UPDATE: "/lesson",
ORDER: "/lesson/order",
}; };
const KEY = "lesson"; const KEY = "lesson";
@ -19,3 +20,5 @@ export const useUpdateLesson = (params?: any) =>
useUpdateMutation(KEY, API.GET); useUpdateMutation(KEY, API.GET);
export const useDeleteLesson = (params?: any) => export const useDeleteLesson = (params?: any) =>
useDeleteMutation(KEY, API.DELETE); useDeleteMutation(KEY, API.DELETE);
export const useUpdateLessonOrder = (params?: any) => useAddMutation("karim", API.ORDER);

20
src/api/packageItem.ts Normal file
View File

@ -0,0 +1,20 @@
import useAddMutation from "./helper/useAddMutation";
import useDeleteMutation from "./helper/useDeleteMutation";
import useGetQuery from "./helper/useGetQuery";
import useUpdateMutation from "./helper/useUpdateMutation";
const API = {
GET: "/packageItem",
ADD: "/packageItem",
DELETE: "/packageItem",
UPDATE: "/packageItem",
};
const KEY = "PackageItem";
export const useGetAllPackageItem = (params?: any, options?: any) =>
useGetQuery(KEY, API.GET, params, options);
export const useAddPackageItem = () => useAddMutation(KEY, API.ADD);
export const useUpdatePackageItem = (params?: any) => useUpdateMutation(KEY, API.GET);
export const useDeletePackageItem = (params?: any) =>
useDeleteMutation(KEY, API.DELETE);

View File

@ -8,6 +8,7 @@ const API = {
ADD: "/unit", ADD: "/unit",
DELETE: "/unit", DELETE: "/unit",
UPDATE: "/unit", UPDATE: "/unit",
ORDER: "/unit/order",
}; };
const KEY = "unit"; const KEY = "unit";
@ -15,5 +16,6 @@ export const useGetAllUnit = (params?: any, options?: any) =>
useGetQuery(KEY, API.GET, params, options); useGetQuery(KEY, API.GET, params, options);
export const useAddUnit = () => useAddMutation(KEY, API.ADD); export const useAddUnit = () => useAddMutation(KEY, API.ADD);
export const useUpdateUnit = (params?: any) => useUpdateMutation(KEY, API.GET); export const useUpdateUnit = (params?: any) => useUpdateMutation(KEY, API.GET);
export const useUpdateUnitOrder = (params?: any,option?: any,) => useAddMutation("karim", API.ORDER,true,params,option);
export const useDeleteUnit = (params?: any) => export const useDeleteUnit = (params?: any) =>
useDeleteMutation(KEY, API.DELETE); useDeleteMutation(KEY, API.DELETE);

View File

@ -1,3 +1,4 @@
export enum DateEnum { export enum DateEnum {
FORMATE = "YYYY-MM-DD", FORMATE = "YYYY-MM-DD",
SEND_DATE_FORMAT = "YYYY-MM-DD",
} }

View File

@ -155,8 +155,17 @@ export enum ModalEnum {
///package ///package
Package_EDIT = "Package.edit", PACKAGE_EDIT = "Package.edit",
Package_ADD = "Package.add", PACKAGE_ADD = "Package.add",
Package_DELETE = "Package.delete", PACKAGE_DELETE = "Package.delete",
/// ReSeller
RE_SELLER_EDIT = "ReSeller.edit",
RE_SELLER_ADD = "ReSeller.add",
RE_SELLER_DELETE = "ReSeller.delete",
/// studentPackage
STUDENT_PACKAGE_EDIT = "studentPackage.edit",
STUDENT_PACKAGE_ADD = "studentPackage.add",
STUDENT_PACKAGE_DELETE = "studentPackage.delete",
} }

View File

@ -1,5 +1,6 @@
export enum UserTypeEnum { export enum UserTypeEnum {
ADMIN = "ADMIN", ADMIN = "admin",
RE_SELLER = "RE_SELLER", RE_SELLER = "reseller",
PASS="pass"
} }

View File

@ -7,7 +7,7 @@ export enum ABILITIES_ENUM {
EARLY_DEPARTURE = "earlyDeparture", EARLY_DEPARTURE = "earlyDeparture",
EDUCATION_CLASS = "eduClass", EDUCATION_CLASS = "eduClass",
GRADE = "grade", GRADE = "grade",
Package = "package", Package = "packages",
HOMEWORK_ATTACHMENT = "homeworkAttachment", HOMEWORK_ATTACHMENT = "homeworkAttachment",
HOMEWORK = "homework", HOMEWORK = "homework",
LATE_ARRIVAL = "lateArrival", LATE_ARRIVAL = "lateArrival",
@ -43,6 +43,9 @@ export enum ABILITIES_ENUM {
QUESTION = "Question", QUESTION = "Question",
ADMIN = "admin", ADMIN = "admin",
CURRICULUM = "curriculum", CURRICULUM = "curriculum",
PACKAGE_ITEM = "package_item",
RE_SELLER = "ReSeller",
STUDENT_PACKAGE='studentPackage'
//// ////
} }

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