This commit is contained in:
Moaz Dawalibi 2024-09-15 10:16:19 +03:00
commit 2949b8b4be
64 changed files with 1030 additions and 808 deletions

View File

@ -10,8 +10,10 @@
"Groupbutton", "Groupbutton",
"handelnavigate", "handelnavigate",
"Karim", "Karim",
"Popconfirm",
"queryqlent", "queryqlent",
"registraion", "registraion",
"Sellcast",
"SENDNOTIFICATION", "SENDNOTIFICATION",
"setdateparams", "setdateparams",
"szhsin", "szhsin",

View File

@ -63,9 +63,9 @@ const ImageBoxField = ({ name }: any) => {
<div className="VisibleHidden">hidden</div> <div className="VisibleHidden">hidden</div>
)} )}
</div> </div>
<div className="ImageBox"> <div className="ImageBox" onClick={handleButtonClick}>
{imagePreview ? ( {imagePreview ? (
<img src={imagePreview} alt="Preview" className="imagePreview" /> <img src={imagePreview} onClick={handleButtonClick} alt="Preview" className="imagePreview" />
) : ( ) : (
<ImageIcon onClick={handleButtonClick} className="ImageBoxIcon" /> <ImageIcon onClick={handleButtonClick} className="ImageBoxIcon" />
)} )}

View File

@ -14,10 +14,10 @@ const TextField = ({
placeholder, placeholder,
isDisabled, isDisabled,
onChange, onChange,
props,
no_label, no_label,
label_icon, label_icon,
className, className,
...props
}: any) => { }: any) => {
const { formik, isError, errorMsg, t } = useFormField(name, props); const { formik, isError, errorMsg, t } = useFormField(name, props);
const TextFilehandleChange = ( const TextFilehandleChange = (
@ -25,6 +25,7 @@ const TextField = ({
) => { ) => {
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 ?? ""} `}>
<ValidationFieldLabel <ValidationFieldLabel
@ -45,9 +46,10 @@ const TextField = ({
size="large" size="large"
showCount showCount
maxLength={1000} maxLength={1000}
autoSize={{ minRows: 4, maxRows: 10 }}
onChange={onChange || TextFilehandleChange} onChange={onChange || TextFilehandleChange}
style={{ height: 120 }}
id={name} id={name}
{...props}
/> />
</ValidationFieldContainer> </ValidationFieldContainer>
</div> </div>

View File

@ -15,6 +15,8 @@
.text, .text,
.ant-form-item { .ant-form-item {
margin-bottom: 7px !important; margin-bottom: 7px !important;
font-weight: bold;
font-size: 19px;
> span { > span {
color: transparent; color: transparent;
} }
@ -65,3 +67,15 @@
.ant-input-textarea-affix-wrapper.ant-input-affix-wrapper { .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper {
height: 120px; height: 120px;
} }
//// malty select
///
.ant-select-multiple{
height: auto !important;
min-height: 40px;
.ant-select-selector{
min-height: 40px;
}
}

View File

@ -4,14 +4,16 @@ import { useTranslation } from "react-i18next";
import { GoArrowSwitch } from "react-icons/go"; import { GoArrowSwitch } from "react-icons/go";
import { useObjectToEdit } from "../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../zustand/ObjectToEditState";
import { QUESTION_OBJECT_KEY } from "../../config/AppKey"; import { QUESTION_OBJECT_KEY } from "../../config/AppKey";
import { Popover } from "antd"; import { Popconfirm, Popover } from "antd";
import { CombinationKeyEnum } from "../../enums/CombinationKeyEnum"; import { CombinationKeyEnum } from "../../enums/CombinationKeyEnum";
import { PopconfirmProps } from "antd/lib";
const Header = () => { const Header = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const { values, setFieldValue, setValues } = useFormikContext<any>(); const { values, setFieldValue, setValues } = useFormikContext<any>();
const { isBseQuestion, setIsBseQuestion } = useObjectToEdit(); const { isBseQuestion, setIsBseQuestion } = useObjectToEdit();
const { setSavedQuestionData } = useObjectToEdit(); const { setSavedQuestionData } = useObjectToEdit();
const handleChange = () => { const handleChange = () => {
setSavedQuestionData(null); setSavedQuestionData(null);
localStorage.removeItem(QUESTION_OBJECT_KEY); localStorage.removeItem(QUESTION_OBJECT_KEY);
@ -26,6 +28,14 @@ const Header = () => {
} }
}; };
const confirm: PopconfirmProps['onConfirm'] = (e) => {
console.log(e);
setTimeout(() => {
handleChange()
}, 500);
};
const content = ( const content = (
<div> <div>
<p> <p>
@ -52,10 +62,21 @@ const Header = () => {
</div> </div>
</article> </article>
<div> <div>
<GoArrowSwitch onClick={handleChange} className="m-2" /> <Popconfirm
title={t("header.this_will_un_do_all_your_changes")}
okText={t("practical.yes")}
cancelText={t("practical.no")}
onConfirm={()=>{confirm()}}
defaultOpen={false}
>
<GoArrowSwitch className="m-2" />
{isBseQuestion || values?.isBase === 1 {isBseQuestion || values?.isBase === 1
? t("header.malty_exercise") ? t("header.malty_exercise")
: t("header.exercise")} : t("header.exercise")}
</Popconfirm>
</div> </div>
</header> </header>
); );

View File

@ -7,6 +7,7 @@ import { useEffect } from "react";
const useFormField = (name: string, props?: any) => { const useFormField = (name: string, props?: any) => {
const [field, meta] = useField({ name, ...props }); const [field, meta] = useField({ name, ...props });
const { t } = useTranslation(); const { t } = useTranslation();
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();

View File

@ -73,7 +73,7 @@ const DeleteModels: React.FC<ModalFormProps> = ({
<Modal <Modal
className="ModalForm" className="ModalForm"
centered centered
width={"40vw"} width={"500px"}
footer={null} footer={null}
open={isOpen === ModelEnum} open={isOpen === ModelEnum}
onCancel={handleCancel} onCancel={handleCancel}

View File

@ -29,6 +29,7 @@ const FormikFormModel: React.FC<FormikFormProps> = ({
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{(formik) => { {(formik) => {
useEffect(() => { useEffect(() => {

View File

@ -1,5 +1,4 @@
import React from "react";
import SearchBar from "../../Components/Ui/SearchBar/SearchBar";
import { Button } from "antd"; import { Button } from "antd";
import { BsPlusCircleFill } from "react-icons/bs"; import { BsPlusCircleFill } from "react-icons/bs";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@ -8,8 +7,7 @@ import useModalHandler from "../../utils/useModalHandler";
import { MdOutlineArrowForwardIos } from "react-icons/md"; import { MdOutlineArrowForwardIos } from "react-icons/md";
import { deletePathSegments } from "../../utils/deletePathSegments"; import { deletePathSegments } from "../../utils/deletePathSegments";
import { getPrevPathRoute } from "../../utils/getPrevPathRoute"; import { getPrevPathRoute } from "../../utils/getPrevPathRoute";
import { usePageTitleState } from "../../zustand/PageTitleState"; import PageTitleComponent from "./PageTitle";
import FillterForm from "../Ui/FillterForm";
const PageHeader = ({ const PageHeader = ({
canAdd, canAdd,
@ -35,20 +33,20 @@ const PageHeader = ({
if (PrevPath === 0) { if (PrevPath === 0) {
return; return;
} }
navigate(deletePathSegments(location.pathname, PrevPath)); // navigate(deletePathSegments(location.pathname, PrevPath));
}; };
const handleNavigateToPage = (location: string) => { const handleNavigateToPage = (location: string) => {
navigate(location); // navigate(location);
}; };
console.log(); console.log();
const { PageTitle } = usePageTitleState();
return ( return (
<div className="page_header"> <div className="page_header">
<header className="d-flex justify-content-between"> <header className="d-flex justify-content-between">
<span className="page_header_links" onClick={handelNavigate}> <span className="page_header_links" onClick={handelNavigate}>
<h1 className="page_title">{t(`PageTitle.${pageTitle}`)}</h1> <h1 className="page_title">{t(`PageTitle.${pageTitle}`)}</h1>
<span className="page_links"> <span className="page_links">
<MdOutlineArrowForwardIos /> {PageTitle} <MdOutlineArrowForwardIos /> <PageTitleComponent/>
</span> </span>
</span> </span>
{ addModal ? canAdd && ( { addModal ? canAdd && (

View File

@ -0,0 +1,30 @@
import React from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { usePageTitleState } from '../../zustand/PageTitleState'
const PageTitleComponent = () => {
const {PageTitle,setPageTitle} = usePageTitleState()
const navigate = useNavigate()
const location = useLocation()
const handleNavigate = (path:string)=>{
const currentPath = location.pathname ;
const newPath = currentPath?.split(path)?.[0] + path ;
if(newPath !== currentPath){
navigate(newPath)
}
}
return (
<div className='PageTitle'>
{(Array.isArray(PageTitle) ? PageTitle : [])?.map((item,index)=>{
return (
<div key={index} className='PageTitleItems' onClick={()=>handleNavigate(item?.path)}>
{item?.name} /
</div>
)
})}
</div>
)
}
export default PageTitleComponent

View File

@ -20,7 +20,10 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteGrade(); const deleteMutation = useDeleteGrade();
useSetPageTitle(t(`page_header.grade`)); useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.grade`)}`, path:"grade"}
]);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -18,7 +18,11 @@ const DeleteModalForm = lazy(
const TableHeader = () => { const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
useSetPageTitle(t(`page_header.param`));
useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.param`)}`, path:"param"}
]);
const deleteMutation = useDeleteParam(); const deleteMutation = useDeleteParam();
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

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 { useAddGrade } from "../../../../api/grade";
const AddModel: React.FC = () => {
const { mutate, status } = useAddGrade();
const handleSubmit = (values: any) => {
mutate({
...values,
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.GRADE_ADD}
modelTitle="grade"
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 { useUpdateQuestion } from "../../../../api/Question";
import { handelImageState } from "../../../../utils/DataToSendImageState";
const EditModel: React.FC = () => {
const { mutate, status } = useUpdateQuestion();
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.QUESTION_BANK_EDIT}
modelTitle="QuestionBank"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema}
isAddModal={false}
>
<ModelForm />
</LayoutModel>
</>
);
};
export default EditModel;

View File

@ -0,0 +1,20 @@
import React from "react";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
import { Col, Row } from "reactstrap";
import { useFormikContext } from "formik";
const FilterForm = () => {
const formik = useFormikContext();
return (
<div>
<Row>
<Col>
<ValidationField placeholder="name" label="name" name="name" />
</Col>
</Row>
</div>
);
};
export default FilterForm;

View File

@ -0,0 +1,17 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
const Form = () => {
return (
<Row className="w-100">
<Col>
<ValidationField name="name" placeholder="name" label="name" />
</Col>
<Col>
<ValidationField name="icon" type="File" />
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,19 @@
import * as Yup from "yup";
import { Grade, GradeInitialValues } from "../../../../types/Grade";
export const getInitialValues = (
objectToEdit: Partial<Grade>,
): GradeInitialValues => {
return {
id: objectToEdit?.id,
name: objectToEdit?.name ?? "",
icon: objectToEdit?.icon ?? "",
};
};
export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
name: Yup.string().required("validation.required"),
});
};

View File

@ -0,0 +1,49 @@
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 { useDeleteQuestion } from "../../../api/Question";
import PageHeader from "../../../Layout/Dashboard/PageHeader";
import FilterLayout from "../../../Layout/Dashboard/FilterLayout";
import FilterForm from "./Model/FilterForm";
import { canAddQuestionBank } from "../../../utils/hasAbilityFn";
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 = useDeleteQuestion();
useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.Question`)}`, path:"Question"}
]);
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<PageHeader
pageTitle="QuestionBank"
ModelAbility={ModalEnum?.QUESTION_BANK_ADD}
canAdd={canAddQuestionBank}
/>
<FilterLayout sub_children={<FilterForm />} filterTitle="table.QuestionBank" />
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.QUESTION_BANK_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

@ -0,0 +1,18 @@
import { useColumns } from "./useTableColumns";
import React from "react";
import DataTable from "../../../Layout/Dashboard/Table/DataTable";
import { useGetAllQuestion } from "../../../api/Question";
import { useFilterState } from "../../../Components/Utils/Filter/FilterState";
const App: React.FC = () => {
const { filterState } = useFilterState();
const response = useGetAllQuestion({
pagination: true,
...filterState,
});
return <DataTable response={response} useColumns={useColumns} />;
};
export default App;

View File

@ -0,0 +1,123 @@
import { TableColumnsType } from "antd";
import { Question } from "../../../types/Item";
import { FaPlus } from "react-icons/fa";
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 { useModalState } from "../../../zustand/Modal";
import {
canAddQuestion,
canDeleteQuestion,
canEditQuestion,
} from "../../../utils/hasAbilityFn";
import ActionButtons from "../../../Components/Table/ActionButtons";
export const useColumns = () => {
const { setObjectToEdit } = useObjectToEdit((state) => state);
const navigate = useNavigate();
const { setIsOpen } = useModalState((state) => state);
const handelAdd = () => {
setObjectToEdit({});
navigate(`${ABILITIES_ENUM?.QUESTION}/add`);
};
const handelDelete = (data: any) => {
setObjectToEdit(data);
setIsOpen(ModalEnum?.QUESTION_DELETE);
};
const handleEdit = (record: any) => {
setObjectToEdit(record);
navigate(`${ABILITIES_ENUM?.QUESTION}/${record?.id}`);
};
const [t] = useTranslation();
const columns: TableColumnsType<Question> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
render: (text, record) => record?.id,
},
{
title: `${t("columns.content")}`,
dataIndex: "content",
key: "content",
align: "center",
render: (text, record) => record?.content,
ellipsis: true,
},
{
title: `${t("columns.hint")}`,
dataIndex: "hint",
key: "hint",
align: "center",
render: (text, record) => {
return (
<>{record?.hint ?? "karim"}</>
);
},
ellipsis: true,
},
{
title: `${t("columns.tags")}`,
dataIndex: "tags",
key: "tags",
align: "center",
render: (text, record) => {
const tags = record?.tags?.map((item:any)=>{
return item?.name
}) ?? [];
return (
<div>
{tags.length > 0 ? (
tags.map((tag, index) => (
<span key={index}>
{tag}
{index < tags.length - 1 && ', '}
</span>
))
) : (
<span>_</span>
)}
</div>
);
},
ellipsis: true,
},
{
title: t("columns.question_type"),
dataIndex: "isBase",
key: "isBase",
align: "center",
render: (text, record) =>
record?.isBase ? t("columns.base_question") : t("columns.normal_question"),
},
{
title: "#",
key: "actions",
align: "center",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canDeleteQuestion}
canEdit={canEditQuestion}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
/>
);
},
},
];
return columns;
};

View File

@ -21,9 +21,12 @@ const SearchField = lazy(
); );
const TableHeader = () => { const TableHeader = () => {
const { handel_open_model } = useModalHandler();
const [t] = useTranslation(); const [t] = useTranslation();
useSetPageTitle(t(`page_header.report`));
useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.report`)}`, path:"report"}
]);
const deleteMutation = useDeleteTag(); const deleteMutation = useDeleteTag();
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -24,7 +24,10 @@ const SearchField = lazy(
const TableHeader = () => { const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
useSetPageTitle(t(`page_header.reseller`)); useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.reseller`)}`, path:"reseller"}
]);
const deleteMutation = useDeleteTag(); const deleteMutation = useDeleteTag();
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -20,8 +20,10 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteStudent(); const deleteMutation = useDeleteStudent();
useSetPageTitle(t(`page_header.student`)); useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.student`)}`, path:"student"}
]);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>

View File

@ -16,14 +16,15 @@ const EditModalForm = lazy(() => import("./Model/EditModel"));
const DeleteModalForm = lazy( const DeleteModalForm = lazy(
() => import("../../../Layout/Dashboard/DeleteModels"), () => import("../../../Layout/Dashboard/DeleteModels"),
); );
const SearchField = lazy(
() => import("../../../Components/DataTable/SearchField"),
);
const TableHeader = () => { const TableHeader = () => {
const { handel_open_model } = useModalHandler();
const [t] = useTranslation(); const [t] = useTranslation();
useSetPageTitle(t(`page_header.tags`));
useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.tags`)}`, path:"tag"}
]);
const deleteMutation = useDeleteTag(); const deleteMutation = useDeleteTag();
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -35,15 +35,13 @@ const TableHeader = () => {
}); });
const gradeName = grade?.data?.name ?? ""; const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? ""; const SubjectName = Subject?.data?.name ?? "";
useSetPageTitle( useSetPageTitle([
t(`page_header.grade`) + {name:`${t(`page_header.home`)}`, path:"/"},
" / " + {name:`${t(`page_header.grade`)}`, path:"grade"},
` ${t("header.subject_of_class")} (${gradeName})` + {name:` ${t("header.subject_of_class")} (${gradeName})`, path:`grade/${grade_id}`},
" / " + {name:SubjectName, path:`subject/${subject_id}`}
SubjectName, ]);
);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -18,7 +18,10 @@ const DeleteModalForm = lazy(
const TableHeader = () => { const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
useSetPageTitle(t(`page_header.user`)); useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.user`)}`, path:"user"}
]);
const deleteMutation = useDeleteUser(); const deleteMutation = useDeleteUser();
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -26,7 +26,7 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteLesson(); const deleteMutation = useDeleteLesson();
const { unit_id, curriculum_id, grade_id, subject_id } = const { unit_id, grade_id, subject_id } =
useParams<ParamsEnum>(); useParams<ParamsEnum>();
const { data: unit } = useGetAllUnit({ show: unit_id }); const { data: unit } = useGetAllUnit({ show: unit_id });
@ -41,15 +41,15 @@ const TableHeader = () => {
const SubjectName = Subject?.data?.name ?? ""; const SubjectName = Subject?.data?.name ?? "";
const unitName = unit?.data?.name ?? ""; const unitName = unit?.data?.name ?? "";
useSetPageTitle(
t(`page_header.grade`) + useSetPageTitle([
" / " + {name:`${t(`page_header.home`)}`, path:"/"},
` ${t("header.subject_of_class")} (${gradeName})` + {name:`${t(`page_header.grade`)}`, path:"grade"},
" / " + {name:` ${t("header.subject_of_class")} (${gradeName})`, path:`grade/${grade_id}`},
SubjectName + {name:SubjectName, path:`subject/${subject_id}`},
" / " + {name:unitName, path:`unit/${unit_id}`}
unitName, ]);
);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -1,16 +1,15 @@
import React, { Suspense, lazy, useEffect } from "react"; import React, { Suspense, lazy, useEffect } from "react";
import { Spin } from "antd"; import { Spin } from "antd";
import FormikForm from "../../../Layout/Dashboard/FormikFormModel";
import { import {
getInitialValues, getInitialValues,
getValidationSchema, getValidationSchema,
getInitialValuesBase, getInitialValuesBase,
getValidationSchemaBase, getValidationSchemaBase,
processTags, processTags,
} from "./Model/formUtil"; } from "./formUtil";
import { useAddQuestion, useAddQuestionAsync } from "../../../api/Question"; import { useAddQuestion, useAddQuestionAsync } from "../../../api/Question";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom"; import { useLocation, useNavigate, useParams } from "react-router-dom";
import { ParamsEnum } from "../../../enums/params"; import { ParamsEnum } from "../../../enums/params";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
@ -19,9 +18,12 @@ import { Question } from "../../../types/Item";
import BaseForm from "./Model/Malty/Form"; import BaseForm from "./Model/Malty/Form";
import ModelForm from "./Model/ModelForm"; import ModelForm from "./Model/ModelForm";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
const AcceptModal = lazy(() => import("./Model/AcceptModal")); import { Form, Formik } from "formik";
import { MdOutlineArrowForwardIos } from "react-icons/md";
const AddPage: React.FC = () => { const AddPage: React.FC = () => {
const location = useLocation();
const { mutateAsync,isLoading:LoadingAsync } = useAddQuestionAsync(); const { mutateAsync,isLoading:LoadingAsync } = useAddQuestionAsync();
const { mutate, isLoading, isSuccess } = useAddQuestion(); const { mutate, isLoading, isSuccess } = useAddQuestion();
const { isBseQuestion, setTagsSearch, objectToEdit, setSuccess } = const { isBseQuestion, setTagsSearch, objectToEdit, setSuccess } =
@ -30,12 +32,7 @@ const AddPage: React.FC = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const { subject_id, lesson_id } = useParams<ParamsEnum>(); const { subject_id, lesson_id } = useParams<ParamsEnum>();
console.log(objectToEdit, "objectToEdit"); const handleSubmit = ( values: any) => {
const handleSubmit = (
values: any,
{ resetForm }: { resetForm: () => void },
) => {
const DataToSend = structuredClone(values); const DataToSend = structuredClone(values);
setTagsSearch(null); setTagsSearch(null);
console.log(1); console.log(1);
@ -95,17 +92,6 @@ const AddPage: React.FC = () => {
}; };
}); });
if (answers?.length > 0) {
const isValidAnswers = answers?.some(
(answer: any) => answer?.isCorrect === 1,
);
console.log(!isValidAnswers);
if (!isValidAnswers) {
toast.error(t("validation.at_least_one_answer_should_be_correct"));
return;
}
}
const NewQuestion = { const NewQuestion = {
...values, ...values,
@ -122,14 +108,62 @@ const AddPage: React.FC = () => {
} }
}; };
const handleValidateSingleQuestion = (values:any)=>{
const haveMoreThanOneAnswer = values?.answers?.length > 1;
const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true )
if(!haveMoreThanOneAnswer){
toast.error(t("validation.it_should_have_more_than_one_answers")) ;
return false ;
}
if(!haveOneAnswerRight){
toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ;
return false ;
}
}
const handleValidateBaseQuestion = (values: any) => {
const haveAnswers = values?.Questions?.every((Question: any, QuestionsIndex: number) => {
const answers = Question?.answers;
const haveAnswers = answers?.length > 0;
const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1;
const haveOneAnswerRight =
haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true);
if (!haveAnswers) {
toast.error(t("validation.it_should_have_more_than_one_answers"));
return false;
}
if (!haveMoreThanOneAnswer) {
toast.error(t("validation.it_should_have_more_than_one_answers"));
return false;
}
if (!haveOneAnswerRight) {
toast.error(t("validation.it_should_have_more_than_one_correct_answers"));
return false;
}
return true;
});
console.log(haveAnswers, "haveAnswers");
};
const navigate = useNavigate(); const navigate = useNavigate();
const handleNavigateToPage = () => {
const cleanedUrl = location.pathname?.replace("/Question/add", "");
navigate(cleanedUrl);
};
const handleCancel = () => { const handleCancel = () => {
navigate(-1); navigate(-1);
}; };
const Loading = LoadingAsync || isLoading const Loading = LoadingAsync || isLoading;
useEffect(() => { useEffect(() => {
console.log("all api success");
if (isSuccess) { if (isSuccess) {
setSuccess(true); setSuccess(true);
} }
@ -137,19 +171,32 @@ const AddPage: React.FC = () => {
if (isBseQuestion) { if (isBseQuestion) {
return ( return (
<div className="QuestionPractical">
<header>
<MdOutlineArrowForwardIos onClick={handleNavigateToPage} /> {t("header.add_new_question")}
</header>
<div className="exercise_add"> <div className="exercise_add">
<FormikForm <Formik
handleSubmit={handleSubmit} onSubmit={handleSubmit}
initialValues={getInitialValuesBase(objectToEdit)} initialValues={getInitialValuesBase({} as any)}
validationSchema={getValidationSchemaBase} validationSchema={getValidationSchemaBase}
enableReinitialize
> >
{({ values,handleSubmit }) => (
<Form className="w-100">
<main className="w-100 exercise_add_main"> <main className="w-100 exercise_add_main">
<Header /> <Header />
<BaseForm /> <BaseForm />
<div className="exercise_add_buttons"> <div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div> <div onClick={handleCancel}>{t("practical.back")}</div>
<button disabled={Loading} className="relative" type="submit"> <button disabled={Loading} className="relative" type="submit"
onClick={()=>{handleValidateBaseQuestion(values) ;handleSubmit(values)}}
onSubmit={()=>{handleValidateBaseQuestion(values) ;handleSubmit(values) }}
>
{t("practical.add")} {t("practical.add")}
{Loading && ( {Loading && (
@ -160,41 +207,61 @@ const AddPage: React.FC = () => {
</button> </button>
</div> </div>
</main> </main>
</FormikForm> </Form>
<Suspense fallback={<Spin />}> )}
<AcceptModal /> </Formik>
</Suspense>
</div> </div>
</div>
); );
} }
return ( return (
<div className="exercise_add">
<FormikForm
handleSubmit={handleSubmit}
initialValues={getInitialValues(objectToEdit)}
validationSchema={getValidationSchema}
>
<main className="w-100 exercise_add_main">
<Header />
<ModelForm />
<div className="exercise_add_buttons"> <div className="QuestionPractical">
<div onClick={handleCancel}>{t("practical.back")}</div> <header>
<button disabled={Loading} className="relative" type="submit"> <MdOutlineArrowForwardIos onClick={handleNavigateToPage} /> {t("header.add_new_question")}
{t("practical.add")} </header>
<div className="exercise_add">
<Formik
enableReinitialize={true}
initialValues={getInitialValues({} as any)}
validationSchema={getValidationSchema}
onSubmit={(values) => {
handleSubmit(values);
}}
>
{({ values,handleSubmit }) => (
<Form className="w-100">
<main className="w-100 exercise_add_main">
<Header />
<ModelForm />
{Loading && ( <div className="exercise_add_buttons">
<span className="Spinier_Div"> <div onClick={handleCancel}>{t("practical.back")}</div>
<Spin /> <button
</span> disabled={Loading}
)} className="relative"
</button> onClick={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values)}}
</div> onSubmit={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values) }}
</main> type="submit"
</FormikForm> >
<Suspense fallback={<Spin />}> {t("practical.add")}
<AcceptModal />
</Suspense> {Loading && (
<span className="Spinier_Div">
<Spin />
</span>
)}
</button>
</div>
</main>
</Form>
)}
</Formik>
</div>
</div> </div>
); );
}; };

View File

@ -1,13 +1,12 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Modal, Spin } from "antd"; import { Modal, Spin } from "antd";
import FormikForm from "../../../Layout/Dashboard/FormikFormModel";
import { import {
getInitialValues, getInitialValues,
getValidationSchema, getValidationSchema,
getInitialValuesBase, getInitialValuesBase,
getValidationSchemaBase, getValidationSchemaBase,
processTags, processTags,
} from "./Model/formUtil"; } from "./formUtil";
import { import {
useAddQuestion, useAddQuestion,
useDeleteQuestion, useDeleteQuestion,
@ -25,6 +24,8 @@ import BaseForm from "./Model/Malty/Form";
import { Question } from "../../../types/Item"; import { Question } from "../../../types/Item";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { deletePathSegments } from "../../../utils/deletePathSegments"; import { deletePathSegments } from "../../../utils/deletePathSegments";
import { Form, Formik } from "formik";
import { MdOutlineArrowForwardIos } from "react-icons/md";
const EditPage: React.FC = () => { const EditPage: React.FC = () => {
const { subject_id, lesson_id, question_id } = useParams<ParamsEnum>(); const { subject_id, lesson_id, question_id } = useParams<ParamsEnum>();
@ -187,6 +188,56 @@ const EditPage: React.FC = () => {
}; };
const handleValidateSingleQuestion = (values:any)=>{
const haveMoreThanOneAnswer = values?.answers?.length > 1;
const haveOneAnswerRight = haveMoreThanOneAnswer && values?.answers?.some((item:any)=> item?.isCorrect === 1 || item.isCorrect === true )
if(!haveMoreThanOneAnswer){
toast.error(t("validation.it_should_have_more_than_one_answers")) ;
return false ;
}
if(!haveOneAnswerRight){
toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ;
return false ;
}
}
const handleValidateBaseQuestion = (values: any) => {
const haveAnswers = values?.Questions?.every((Question: any, QuestionsIndex: number) => {
const answers = Question?.answers;
const haveAnswers = answers?.length > 0;
const haveMoreThanOneAnswer = haveAnswers && answers?.length > 1;
const haveOneAnswerRight =
haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true);
if (!haveAnswers) {
toast.error(t("validation.it_should_have_more_than_one_answers"));
return false;
}
if (!haveMoreThanOneAnswer) {
toast.error(t("validation.it_should_have_more_than_one_answers"));
return false;
}
if (!haveOneAnswerRight) {
toast.error(t("validation.it_should_have_more_than_one_correct_answers"));
return false;
}
return true;
});
console.log(haveAnswers, "haveAnswers");
};
const handleNavigateToPage = () => {
const cleanedUrl = location.pathname.replace(/\/Question\/\d+$/, "");
navigate(cleanedUrl);
};
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
toast.success(t("validation.the_possess_done_successful")); toast.success(t("validation.the_possess_done_successful"));
@ -201,13 +252,24 @@ const EditPage: React.FC = () => {
} }
if (objectToEdit?.isBase) { if (objectToEdit?.isBase) {
return ( return (
<div className="QuestionPractical">
<header>
<MdOutlineArrowForwardIos onClick={handleNavigateToPage} /> {t("header.edit_question")}
</header>
<div className="exercise_add"> <div className="exercise_add">
<FormikForm <Formik
handleSubmit={handleSubmit} onSubmit={handleSubmit}
initialValues={getInitialValuesBase(objectToEdit)} initialValues={getInitialValuesBase(objectToEdit)}
validationSchema={getValidationSchemaBase} validationSchema={getValidationSchemaBase}
enableReinitialize
> >
<main className="w-100 exercise_add_main"> {({ values,handleSubmit }) => (
<Form className="w-100">
<main className="w-100 exercise_add_main">
{/* <Header/> */} {/* <Header/> */}
<header className="exercise_add_header mb-4"> <header className="exercise_add_header mb-4">
<div> <div>
@ -218,8 +280,10 @@ const EditPage: React.FC = () => {
<BaseForm /> <BaseForm />
<div className="exercise_add_buttons"> <div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div> <div onClick={handleCancel}>{t("practical.back")}</div>
<button disabled={Loading} className="relative" type="submit"> <button disabled={Loading} className="relative" type="submit"
{t("practical.edit")} onClick={()=>{handleValidateBaseQuestion(values) ;handleSubmit(values)}}
onSubmit={()=>{handleValidateBaseQuestion(values) ;handleSubmit(values) }}
> {t("practical.edit")}
{Loading && ( {Loading && (
<span className="Spinier_Div"> <span className="Spinier_Div">
@ -229,19 +293,35 @@ const EditPage: React.FC = () => {
</button> </button>
</div> </div>
</main> </main>
</FormikForm> </Form>
)}
</Formik>
</div>
</div> </div>
); );
} }
return ( return (
<div className="QuestionPractical">
<header>
<MdOutlineArrowForwardIos onClick={handleNavigateToPage} /> {t("header.edit_question")}
</header>
<div className="exercise_add"> <div className="exercise_add">
<FormikForm
handleSubmit={handleSubmit} <Formik
initialValues={getInitialValues(objectToEdit)} enableReinitialize={true}
validationSchema={getValidationSchema} initialValues={getInitialValues(objectToEdit)}
> validationSchema={getValidationSchema}
<main className="w-100 exercise_add_main"> onSubmit={(values) => {
handleSubmit(values);
}}
>
{({ values,handleSubmit }) => (
<Form className="w-100">
<main className="w-100 exercise_add_main">
{/* <Header/> */} {/* <Header/> */}
<header className="exercise_add_header mb-4"> <header className="exercise_add_header mb-4">
<div> <div>
@ -252,8 +332,13 @@ const EditPage: React.FC = () => {
<ModelForm /> <ModelForm />
<div className="exercise_add_buttons"> <div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div> <div onClick={handleCancel}>{t("practical.back")}</div>
<button disabled={Loading} className="relative" type="submit"> <button
{t("practical.edit")} disabled={Loading}
className="relative"
onClick={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values)}}
onSubmit={()=>{handleValidateSingleQuestion(values) ;handleSubmit(values) }}
type="submit"
> {t("practical.edit")}
{Loading && ( {Loading && (
<span className="Spinier_Div"> <span className="Spinier_Div">
@ -263,8 +348,14 @@ const EditPage: React.FC = () => {
</button> </button>
</div> </div>
</main> </main>
</FormikForm>
</Form>
)}
</Formik>
</div> </div>
</div>
); );
}; };

View File

@ -1,57 +0,0 @@
import React from "react";
import { Modal } from "antd";
import { useModalState } from "../../../../zustand/Modal";
import { ModalEnum } from "../../../../enums/Model";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { QUESTION_OBJECT_KEY } from "../../../../config/AppKey";
const AcceptModal: React.FC = () => {
const { isOpen, setIsOpen } = useModalState((state) => state);
const navigate = useNavigate();
const handleSubmit = () => {
localStorage.removeItem(QUESTION_OBJECT_KEY);
console.log("Handle submit clicked");
setIsOpen("");
navigate(-1);
};
const handleCancel = () => {
setIsOpen("");
};
const [t] = useTranslation();
return (
<>
<Modal
className="ModalForm"
centered
width={"40vw"}
footer={null}
open={isOpen === ModalEnum?.QUESTION_ACCEPT}
onCancel={handleCancel}
>
<header> {t("practical.accept_back")}</header>
<main className="main_modal">
<div className="ValidationField w-100 mb-5">
<label className="text h1 ">
{t(
"practical.Are you sure you want to go back and not save any changes?",
)}{" "}
</label>
</div>
<div className="buttons">
<div onClick={handleCancel}>{t("practical.cancel")}</div>
<div onClick={handleSubmit}>{t("practical.accept")}</div>
</div>
</main>
</Modal>
</>
);
};
export default AcceptModal;

View File

@ -39,6 +39,7 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
name={index} name={index}
id={`choice_${index + 1}`} id={`choice_${index + 1}`}
type="TextArea" type="TextArea"
/> />
<ImageBoxField name={`answers.${index}.content_image`} /> <ImageBoxField name={`answers.${index}.content_image`} />
@ -50,11 +51,11 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
name={index} name={index}
type="Checkbox" type="Checkbox"
/> />
<p className="delete_question_options"> <p className="delete_question_options" onClick={handleDeleteChoice}>
{t("header.delete_choice")} {t("header.delete_choice")}
<GoTrash <GoTrash
className="trash_icon" className="trash_icon"
onClick={handleDeleteChoice}
size={17} size={17}
/> />
</p> </p>
@ -66,8 +67,10 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
placeholder="_" placeholder="_"
name={`answers.${index}.hint`} name={`answers.${index}.hint`}
label="hint" label="hint"
type="text" type="TextArea"
style={{ width: "100%" }} style={{ width: "100%" , height: 60,resize:"none" }}
showCount={false}
autoSize={{ minRows: 2, maxRows: 10 }}
/> />
</div> </div>
</> </>

View File

@ -36,12 +36,21 @@ const Choices = () => {
}; };
return ( return (
<DragDropContext onDragEnd={handleDragEnd}> <>
{formik?.values?.answers?.map((item: Choice, index: number) => {
return (
<div
>
<ChoiceFields index={index} data={item} />
</div>
);
})}
{/* <DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="choices"> <Droppable droppableId="choices">
{(provided) => ( {(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}> <div {...provided.droppableProps} ref={provided.innerRef}>
{formik?.values?.answers?.map((item: Choice, index: number) => { {formik?.values?.answers?.map((item: Choice, index: number) => {
// Use a unique identifier for draggableId
const draggableId = item.name const draggableId = item.name
? item.name.toString() ? item.name.toString()
: `item-${index}`; : `item-${index}`;
@ -67,11 +76,12 @@ const Choices = () => {
</Draggable> </Draggable>
); );
})} })}
{provided.placeholder} {/* Placeholder for spacing */} {provided.placeholder}
</div> </div>
)} )}
</Droppable> </Droppable>
</DragDropContext> </DragDropContext> */}
</>
); );
}; };

View File

@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import useFormField from "../../../../../../Hooks/useFormField"; import { Checkbox } from "antd";
import { Checkbox, Form } from "antd"; import { useFormikContext } from "formik";
import { useFormik, useFormikContext } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const CheckboxField = ({ const CheckboxField = ({
name, name,

View File

@ -6,10 +6,7 @@ import { useTranslation } from "react-i18next";
import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber"; import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber";
import CheckboxField from "./CheckboxField"; import CheckboxField from "./CheckboxField";
import TextField from "./TextField"; import TextField from "./TextField";
import File from "./File";
import { FaTrash } from "react-icons/fa";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import HintField from "./HintField";
import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import { GoTrash } from "react-icons/go"; import { GoTrash } from "react-icons/go";
@ -72,11 +69,11 @@ const ChoiceFields = ({
type="Checkbox" type="Checkbox"
parent_index={parent_index} parent_index={parent_index}
/> />
<p className="delete_question_options"> <p className="delete_question_options" onClick={handleDeleteChoice}>
{t("header.delete_choice")} {t("header.delete_choice")}
<GoTrash <GoTrash
className="trash_icon" className="trash_icon"
onClick={handleDeleteChoice}
size={17} size={17}
/> />
</p> </p>
@ -89,8 +86,10 @@ const ChoiceFields = ({
placeholder="_" placeholder="_"
name={`Questions.${parent_index}.answers.${index}.hint`} name={`Questions.${parent_index}.answers.${index}.hint`}
label="hint" label="hint"
type="text" type="TextArea"
style={{ width: "100%" }} style={{ width: "100%" , height: 60,resize:"none" }}
showCount={false}
autoSize={{ minRows: 2, maxRows: 10 }}
/> />
</div> </div>
</> </>

View File

@ -44,7 +44,28 @@ const Choices = ({ parent_index }: { parent_index: number }) => {
return ( return (
<> <>
<DragDropContext onDragEnd={handleDragEnd}>
<div>
{(
(formik?.values as any)?.Questions?.[parent_index]?.answers ||
[]
).map((item: Choice, index: number) => {
return (
<div
>
<ChoiceFields
key={index}
parent_index={parent_index}
index={index}
data={item}
/>
</div>
);
})}
</div>
{/* <DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="choices"> <Droppable droppableId="choices">
{(provided) => ( {(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}> <div {...provided.droppableProps} ref={provided.innerRef}>
@ -87,7 +108,7 @@ const Choices = ({ parent_index }: { parent_index: number }) => {
</div> </div>
)} )}
</Droppable> </Droppable>
</DragDropContext> </DragDropContext> */}
</> </>
); );
}; };

View File

@ -1,88 +0,0 @@
import { Button, Upload, UploadFile } from "antd";
import useFormField from "../../../../../../Hooks/useFormField";
import { UploadOutlined } from "@ant-design/icons";
import { useMemo } from "react";
const File = ({
name,
label,
onChange,
isDisabled,
placholder,
className,
parent_index,
props,
}: any) => {
const newName = `Questions[${parent_index}].answers[${name}].answer_image`;
const { formik, t, isError, errorMsg } = useFormField(newName, props);
let imageUrl =
formik?.values?.Questions?.[parent_index]?.answers[name]?.answer_image ??
null;
// console.log(imageUrl);
const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return [];
return [
typeof imageUrl === "string"
? {
uid: "-1",
name: "uploaded-image",
status: "done",
url: imageUrl,
thumbUrl: imageUrl,
}
: {
uid: imageUrl.uid || "-1",
name: imageUrl.name || "uploaded-image",
status: "done",
originFileObj: imageUrl,
},
];
}, [imageUrl]);
// console.log(1);
const FilehandleChange = (value: any) => {
// console.log(value,"filevalue");
if (value.fileList.length === 0) {
formik.setFieldValue(newName, null);
} else {
formik.setFieldValue(
`Questions[${parent_index}].answers[${name}].answer_image`,
value?.file?.originFileObj,
);
}
};
const customRequest = async ({ onSuccess, no_label, label_icon }: any) => {
onSuccess();
};
return (
<div className={`ValidationField upload_image_button ${className ?? ""} `}>
<label htmlFor={name} className="text">
{t(`input.${label || name}`)}
</label>
<Upload
disabled={isDisabled}
listType="picture"
maxCount={1}
fileList={[...fileList]}
onChange={onChange || FilehandleChange}
customRequest={customRequest}
className={` w-100`}
>
<Button
className={isError ? "isError w-100 " : " w-100"}
icon={<UploadOutlined />}
>
{placholder ?? t("input.Click_to_upload_the_image")}
</Button>
<div className="Error_color"> {isError ? "required" : ""}</div>
{errorMsg}
</Upload>
</div>
);
};
export default File;

View File

@ -1,54 +0,0 @@
import { Form, Input } from "antd";
import React from "react";
import useFormField from "../../../../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik";
const HintField = ({
name,
label,
label2,
placeholder,
isDisabled,
onChange,
props,
parent_index,
id,
className,
}: any) => {
const newName = `Questions[${parent_index}].answers[${name}].hint`;
const { formik, isError, errorMsg, t } = useFormField(newName, props);
const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
// console.log('Change:', e.target.value);
formik.setFieldValue(newName, e.target.value);
};
return (
<div className={`ValidationField w-100 ${className ?? ""} `}>
<label htmlFor={name} className="text">
{label2 ? label2 : t(`input.${label ? label : name}`)}
</label>
<Form.Item
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field
as={Input}
placeholder={t(`input.${placeholder ? placeholder : name}`)}
name={newName}
disabled={isDisabled}
size="large"
onChange={onChange || TextFilehandleChange}
style={{ width: 200 }}
id={id}
/>
</Form.Item>
</div>
);
};
export default React.memo(HintField);

View File

@ -86,6 +86,14 @@ const Form = () => {
}, },
); );
useEffect(() => {
if (Success) {
formik.resetForm()
setSuccess(false);
}
}, [Success]);
return ( return (
<Row className="w-100 exercise_form_container"> <Row className="w-100 exercise_form_container">
<div className="exercise_form"> <div className="exercise_form">
@ -99,7 +107,7 @@ const Form = () => {
<div></div> <div></div>
</div> </div>
<div className=" flex "></div> <div className="flex"></div>
{((formik?.values as any)?.Questions || [])?.map( {((formik?.values as any)?.Questions || [])?.map(
(item: Choice, parent_index: number) => { (item: Choice, parent_index: number) => {
@ -132,8 +140,10 @@ const Form = () => {
placeholder="_" placeholder="_"
name={`Questions.${parent_index}.hint`} name={`Questions.${parent_index}.hint`}
label="hint_question" label="hint_question"
type="text" type="TextArea"
style={{ width: "100%" }} style={{ width: "100%" , height: 60,resize:"none" }}
autoSize={{ minRows: 2, maxRows: 10 }}
showCount={false}
/> />
<MaltySelectTag parent_index={parent_index} /> <MaltySelectTag parent_index={parent_index} />
</div> </div>

View File

@ -1,85 +0,0 @@
import { Button, Upload, UploadFile } from "antd";
import useFormField from "../../../../../../Hooks/useFormField";
import { UploadOutlined } from "@ant-design/icons";
import { useMemo } from "react";
const File = ({
name,
label,
onChange,
isDisabled,
placholder,
className,
props,
}: any) => {
const newName = `Questions[${name}].image`;
const { formik, t, isError, errorMsg } = useFormField(newName, props);
let imageUrl = formik?.values?.Questions?.[name]?.image ?? null;
// console.log(imageUrl);
const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return [];
return [
typeof imageUrl === "string"
? {
uid: "-1",
name: "uploaded-image",
status: "done",
url: imageUrl,
thumbUrl: imageUrl,
}
: {
uid: imageUrl.uid || "-1",
name: imageUrl.name || "uploaded-image",
status: "done",
originFileObj: imageUrl,
},
];
}, [imageUrl]);
// console.log(1);
const FilehandleChange = (value: any) => {
// console.log(value,"filevalue");
if (value.fileList.length === 0) {
formik.setFieldValue(newName, null);
} else {
formik.setFieldValue(
`Questions[${name}].image`,
value?.file?.originFileObj,
);
}
};
const customRequest = async ({ onSuccess, no_label, label_icon }: any) => {
onSuccess();
};
return (
<div className={`ValidationField upload_image_button ${className ?? ""} `}>
<label htmlFor={name} className="text">
{t(`input.${label || name}`)}
</label>
<Upload
disabled={isDisabled}
listType="picture"
maxCount={1}
fileList={[...fileList]}
onChange={onChange || FilehandleChange}
customRequest={customRequest}
className={` w-100`}
>
<Button
className={isError ? "isError w-100 " : " w-100"}
icon={<UploadOutlined />}
>
{placholder ?? t("input.Click_to_upload_the_image")}
</Button>
<div className="Error_color"> {isError ? "required" : ""}</div>
{errorMsg}
</Upload>
</div>
);
};
export default File;

View File

@ -1,52 +0,0 @@
import { Form, Input } from "antd";
import React from "react";
import useFormField from "../../../../../../Hooks/useFormField";
import { MdOutlineEdit } from "react-icons/md";
import { Field } from "formik";
const HintField = ({
name,
label,
label2,
placeholder,
isDisabled,
onChange,
props,
id,
className,
}: any) => {
const newName = `Questions[${name}].hint`;
const { formik, isError, errorMsg, t } = useFormField(newName, props);
const TextFilehandleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
// console.log('Change:', e.target.value);
formik.setFieldValue(newName, e.target.value);
};
return (
<div className={`ValidationField w-100 ${className ?? ""} `}>
<label htmlFor={name} className="text">
{label2 ? label2 : t(`input.${label ? label : name}`)}
</label>
<Form.Item
hasFeedback
validateStatus={isError ? "error" : ""}
help={isError ? errorMsg : ""}
>
<Field
as={Input}
placeholder={t(`input.${placeholder ? placeholder : name}`)}
name={newName}
disabled={isDisabled}
size="large"
onChange={onChange || TextFilehandleChange}
style={{ width: 200 }}
id={id}
/>
</Form.Item>
</div>
);
};
export default React.memo(HintField);

View File

@ -1,16 +1,10 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Choice } from "../../../../../../types/Item"; import { Choice } from "../../../../../../types/Item";
import ValidationField from "../../../../../../Components/ValidationField/ValidationField";
import { useFormikContext } from "formik"; import { useFormikContext } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber"; import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber";
import TextField from "./TextField"; import TextField from "./TextField";
import File from "./File";
import { FaTrash } from "react-icons/fa";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import { toast } from "react-toastify";
import CheckboxField from "./CheckboxField";
import HintField from "./HintField";
import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import { GoTrash } from "react-icons/go"; import { GoTrash } from "react-icons/go";
@ -53,12 +47,12 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
<ImageBoxField name={`Questions.${index}.content_image`} /> <ImageBoxField name={`Questions.${index}.content_image`} />
<div className="answer_status"> <div className="answer_status" onClick={handleDeleteQuestion}>
<p className="delete_question_options"> <p className="delete_question_options">
{t("header.delete_question")} {t("header.delete_question")}
<GoTrash <GoTrash
className="trash_icon" className="trash_icon"
onClick={handleDeleteQuestion}
size={17} size={17}
/> />
</p> </p>

View File

@ -1,107 +0,0 @@
import { useFormikContext } from "formik";
import React from "react";
import { useTranslation } from "react-i18next";
import { FaCirclePlus } from "react-icons/fa6";
import Tag from "./Tag";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import { useGetAllTag } from "../../../../../../api/tags";
const DynamicTags = ({ parent_index }: { parent_index: number }) => {
const formik = useFormikContext<any>();
const [t] = useTranslation();
const { TagsSearch, setTagsSearch, currentTag, currentParentIndex } =
useObjectToEdit();
const { data } = useGetAllTag({
name: TagsSearch,
});
const suggests = data?.data;
const handleAddChoice = () => {
const length = formik?.values?.Questions?.[parent_index]?.tags.length;
const lastElement =
formik?.values?.Questions?.[parent_index]?.tags[length - 1]?.name;
setTagsSearch(null);
if (lastElement !== "") {
formik.setFieldValue(`Questions.[${parent_index}].tags`, [
...((formik?.values as any)?.Questions?.[parent_index]?.tags as any[]),
{
id: length + "_new",
name: "",
key: length,
},
]);
} else {
}
};
// console.log(formik?.values);
// console.log(currentTag);
const handleChoice = (item: any) => {
const length = formik?.values?.Questions?.[parent_index]?.tags?.length;
console.log(currentTag);
formik.setFieldValue(`Questions.[${parent_index}].tags[${currentTag}]`, {
...item,
key: length,
});
setTagsSearch(null);
};
// console.log(formik?.values?.tags?.length);
return (
<div className="DynamicTags">
{formik?.values?.Questions?.[parent_index]?.tags?.length < 1 && (
<p className="add_new_button">
<FaCirclePlus size={23} onClick={handleAddChoice} />{" "}
{t("header.add_tag")}
</p>
)}
<div className="tag_container">
<div className="tags">
{(
((formik?.values as any)?.Questions?.[parent_index]
?.tags as any[]) || []
).map((item: any, index: number) => {
return (
<Tag
key={index}
parent_index={parent_index}
index={index}
data={item}
/>
);
})}
</div>
{formik?.values?.Questions?.[parent_index]?.tags?.length > 0 && (
<p className="add_new_button">
<FaCirclePlus onClick={handleAddChoice} size={20} />
</p>
)}
</div>
{TagsSearch && currentParentIndex === parent_index && (
<div className="suggests">
{suggests?.map((item: any, index: number) => {
console.log(currentParentIndex === parent_index);
return (
<div
className="suggested"
key={index}
onClick={() => handleChoice(item)}
>
{item?.name}
</div>
);
})}
</div>
)}
</div>
);
};
export default DynamicTags;

View File

@ -41,14 +41,10 @@ const MaltySelectTag = ({ parent_index }: { parent_index: number }) => {
? [{ id: searchValue, name: searchValue }] ? [{ id: searchValue, name: searchValue }]
: []; : [];
console.log(options);
const value = const value =
formik?.values?.Questions[parent_index]?.tags?.map( formik?.values?.Questions[parent_index]?.tags?.map(
(item: any) => item?.id ?? item, (item: any) => item?.id ?? item,
) ?? []; ) ?? [];
console.log(formik?.values?.Questions[parent_index]);
console.log(value);
const AllOptions = [...options, ...additionalData]; const AllOptions = [...options, ...additionalData];

View File

@ -1,80 +0,0 @@
import { useFormikContext } from "formik";
import React, { useRef, useEffect } from "react";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import { FaTrash } from "react-icons/fa";
const Tag = ({
data,
index,
parent_index,
}: {
data: any;
index: number;
parent_index: number;
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const DEBOUNCE_DELAY = 500;
const formik = useFormikContext<any>();
const { setTagsSearch, setCurrentTag, setCurrentParentIndex } =
useObjectToEdit();
console.log(formik?.values?.Questions);
useEffect(() => {
if (inputRef.current) {
inputRef.current.style.width = `${(formik?.values?.Questions?.[parent_index]?.tags[index]?.name?.length + 1) * 8}px`;
}
}, [formik?.values?.Questions?.[parent_index]?.tags[index]?.name]);
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// console.log(e.target.value);
formik.setFieldValue(`Questions.[${parent_index}].tags[${index}]`, {
key: parent_index,
name: e.target.value,
id: `${parent_index}_key`,
});
setCurrentTag(index);
setCurrentParentIndex(parent_index);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setTagsSearch(e.target.value);
}, DEBOUNCE_DELAY);
};
const handleDeleteChoice = () => {
console.log(data);
// Create a copy of current tags array
const currentTags = [...formik.values.tags];
// Remove the item at the specified index from the array
currentTags.splice(index, 1);
console.log(currentTags); // Log the updated tags array
// Update formik field value with the updated tags array
formik.setFieldValue(`Questions.[${parent_index}].tags`, currentTags);
// Reset search state if needed
setTagsSearch(null);
};
return (
<div className="tag">
<input
ref={inputRef}
className="tagInput"
type="text"
value={formik?.values?.Questions?.[parent_index]?.tags[index]?.name}
onChange={handleEditInputChange}
/>
<FaTrash onClick={handleDeleteChoice} />
</div>
);
};
export default Tag;

View File

@ -20,7 +20,7 @@ const Form = () => {
const handleAddChoice = (fromKeyCombination: boolean = false) => { const handleAddChoice = (fromKeyCombination: boolean = false) => {
formik.setFieldValue("answers", [ formik.setFieldValue("answers", [
...((formik?.values as any)?.answers as Choice[]), ...(formik?.values?.answers ?? []) ,
{ {
content: null, content: null,
content_image: null, content_image: null,
@ -42,13 +42,12 @@ const Form = () => {
useEffect(() => { useEffect(() => {
if (Success) { if (Success) {
formik?.setValues({}); formik.resetForm()
formik.setErrors({});
setSuccess(false); setSuccess(false);
console.log(formik.errors);
} }
}, [Success]); }, [Success]);
return ( return (
<Row className="w-100 exercise_form_container"> <Row className="w-100 exercise_form_container">
<div className="exercise_form"> <div className="exercise_form">
@ -62,7 +61,7 @@ const Form = () => {
</div> </div>
<Choices /> <Choices />
{formik?.values?.answers?.length < 5 && ( {(formik?.values?.answers === null || formik?.values?.answers === undefined || formik?.values?.answers?.length < 5) && (
<p className="add_new_button"> <p className="add_new_button">
<FaCirclePlus onClick={() => handleAddChoice()} size={23} />{" "} <FaCirclePlus onClick={() => handleAddChoice()} size={23} />{" "}
{t("header.add_new_choice")} {t("header.add_new_choice")}
@ -75,8 +74,11 @@ const Form = () => {
placeholder="_" placeholder="_"
name="hint" name="hint"
label="hint_question" label="hint_question"
type="text" type="TextArea"
style={{ width: "100%" }} style={{ width: "100%" , height: 60,resize:"none" }}
showCount={false}
autoSize={{ minRows: 2, maxRows: 10 }}
/> />
<SelectTag /> <SelectTag />
</div> </div>

View File

@ -11,7 +11,6 @@ import DeleteModels from "../../../Layout/Dashboard/DeleteModels";
import { ModalEnum } from "../../../enums/Model"; import { ModalEnum } from "../../../enums/Model";
import { useGetAllSubject } from "../../../api/subject"; import { useGetAllSubject } from "../../../api/subject";
import { useGetAllGrade } from "../../../api/grade"; import { useGetAllGrade } from "../../../api/grade";
import { useGetAllCurriculum } from "../../../api/curriculum";
import PageHeader from "../../../Layout/Dashboard/PageHeader"; import PageHeader from "../../../Layout/Dashboard/PageHeader";
import { ABILITIES_ENUM } from "../../../enums/abilities"; import { ABILITIES_ENUM } from "../../../enums/abilities";
import { canAddQuestion } from "../../../utils/hasAbilityFn"; import { canAddQuestion } from "../../../utils/hasAbilityFn";
@ -43,17 +42,15 @@ const TableHeader = () => {
const unitName = unit?.data?.name ?? ""; const unitName = unit?.data?.name ?? "";
const LessonName = Lesson?.data?.name ?? ""; const LessonName = Lesson?.data?.name ?? "";
useSetPageTitle( useSetPageTitle([
t(`page_header.grade`) + {name:`${t(`page_header.home`)}`, path:"/"},
" / " + {name:`${t(`page_header.grade`)}`, path:"grade"},
` ${t("header.subject_of_class")} (${gradeName})` + {name:` ${t("header.subject_of_class")} (${gradeName})`, path:`grade/${grade_id}`},
" / " + {name:SubjectName, path:`subject/${subject_id}`},
SubjectName + {name:unitName, path:`unit/${unit_id}`},
" / " + {name:LessonName, path:`lesson/${lesson_id }`}
unitName + ]);
" / " +
LessonName,
);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -11,7 +11,7 @@ const App: React.FC = () => {
const { filterState } = useFilterState(); const { filterState } = useFilterState();
const response = useGetAllQuestion({ const response = useGetAllQuestion({
lesson_id: lesson_id, lessonsIds: [lesson_id],
pagination: true, pagination: true,
...filterState, ...filterState,
}); });

View File

@ -1,12 +1,12 @@
import * as Yup from "yup"; import * as Yup from "yup";
import { Question } from "../../../../types/Item"; import { Question } from "../../../types/Item";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
export const getInitialValues = (objectToEdit: Question): any => { export const getInitialValues = (objectToEdit: Question): any => {
const tags = objectToEdit?.tags?.map((item: any, index: number) => { const tags = objectToEdit?.tags?.map((item: any, index: number) => {
return { ...item }; return { ...item };
}); });
return { return {
id: objectToEdit?.id ?? null, id: objectToEdit?.id ?? null,
content: objectToEdit?.content ?? "", content: objectToEdit?.content ?? "",
@ -16,7 +16,7 @@ export const getInitialValues = (objectToEdit: Question): any => {
hint: objectToEdit?.hint ?? null, hint: objectToEdit?.hint ?? null,
isBase: 0, isBase: 0,
parent_id: objectToEdit?.parent_id ?? "", parent_id: objectToEdit?.parent_id ?? "",
answers: objectToEdit?.answers ?? [], answers: objectToEdit?.answers ?? null,
tags: tags ?? [], tags: tags ?? [],
}; };
}; };
@ -32,6 +32,15 @@ export const getValidationSchema = () => {
content_image: Yup.string().nullable(), content_image: Yup.string().nullable(),
isCorrect: Yup.boolean(), isCorrect: Yup.boolean(),
}), }),
).min(2).test(
"at-least-one-correct",
"At least one answer must be correct",
(answers: any) => {
return answers?.some(
(answer: any) =>
answer?.isCorrect === true || answer?.isCorrect === 1,
);
},
), ),
}); });
}; };
@ -42,10 +51,18 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
id: tag?.id, id: tag?.id,
name: tag?.name, name: tag?.name,
})); }));
console.log(item, "item"); const newAnswers = item?.answers?.map((item:any)=>{
return {
...item,
content : item?.content ?? null
}
})
console.log(newAnswers,"newAnswers");
return { return {
...item, ...item,
answer:newAnswers,
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ? 1 : 0, canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ? 1 : 0,
hint: objectToEdit?.hint ?? "", hint: objectToEdit?.hint ?? "",
isBase: 0, isBase: 0,
@ -53,7 +70,7 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
}; };
}); });
const questions = newQuestions ?? []; const questions = newQuestions ?? [{answers:[]}];
return { return {
id: objectToEdit?.id ?? null, id: objectToEdit?.id ?? null,
@ -84,13 +101,12 @@ export const getValidationSchemaBase = () => {
answer_image: Yup.string().nullable(), answer_image: Yup.string().nullable(),
isCorrect: Yup.boolean(), isCorrect: Yup.boolean(),
}), }),
) ).min(2)
.test( .test(
"at-least-one-correct", "at-least-one-correct",
"At least one answer must be correct", "At least one answer must be correct",
(answers: any) => { (answers: any) => {
console.log(answers, "answers");
return answers.some( return answers.some(
(answer: any) => (answer: any) =>
answer.isCorrect === true || answer.isCorrect === 1, answer.isCorrect === true || answer.isCorrect === 1,
@ -98,7 +114,7 @@ export const getValidationSchemaBase = () => {
}, },
), ),
}), }),
), ).min(1),
}); });
}; };

View File

@ -45,19 +45,59 @@ export const useColumns = () => {
}, },
{ {
title: `${t("columns.content")}`, title: `${t("columns.content")}`,
dataIndex: "name", dataIndex: "content",
key: "name", key: "content",
align: "center", align: "center",
render: (text, record) => record?.content, render: (text, record) => record?.content,
ellipsis: true, ellipsis: true,
}, },
{ {
title: t("columns.isBase"), title: `${t("columns.hint")}`,
dataIndex: "hint",
key: "hint",
align: "center",
render: (text, record) => {
return (
<>{record?.hint ?? "_"}</>
);
},
ellipsis: true,
},
{
title: `${t("columns.tags")}`,
dataIndex: "tags",
key: "tags",
align: "center",
render: (text, record) => {
const tags = record?.tags?.map((item:any)=>{
return item?.name
}) ?? [];
return (
<div>
{tags.length > 0 ? (
tags.map((tag, index) => (
<span key={index}>
{tag}
{index < tags.length - 1 && ', '}
</span>
))
) : (
<span>_</span>
)}
</div>
);
},
ellipsis: true,
},
{
title: t("columns.question_type"),
dataIndex: "isBase", dataIndex: "isBase",
key: "isBase", key: "isBase",
align: "center", align: "center",
render: (text, record) => render: (text, record) =>
record?.isBase ? t("practical.yes") : t("practical.no"), record?.isBase ? t("columns.base_question") : t("columns.normal_question"),
}, },
{ {

View File

@ -32,11 +32,15 @@ const TableWithHeader = () => {
}); });
const gradeName = grade?.data?.name ?? ""; const gradeName = grade?.data?.name ?? "";
useSetPageTitle(
t(`page_header.grade`) +
" / " + useSetPageTitle([
` ${t("header.subject_of_class")} (${gradeName})`, {name:`${t(`page_header.home`)}`, path:"/"},
); {name:`${t(`page_header.grade`)}`, path:"grade"},
{name:` ${t("header.subject_of_class")} (${gradeName})`, path:`grade/${grade_id}`}
]);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -1,19 +1,7 @@
import React, { useEffect, useState } from "react"; import { Form, useFormikContext } from "formik";
import { Form, Field, useFormikContext } from "formik";
import Image from "../../Components/Ui/Image";
import { Input } from "antd";
import AuthSelect from "../../Components/Ui/Custom/AuthSelect";
// import { useGetAllCycle } from "../../api/cycle";
// import { useGetAllBranch } from "../../api/branch";
import { useGetAllTerm } from "../../api/term";
import { FormValues } from "../../types/Auth";
import {
BRANCH_OBJECT_KEY,
CYCLE_OBJECT_KEY,
TERM_OBJECT_KEY,
} from "../../config/AppKey";
import useFormatAuthDataToSelect from "../../utils/useFormatAuthDataToSelect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ValidationField from "../../Components/ValidationField/ValidationField";
type FormFieldType = { type FormFieldType = {
isLoading: boolean; isLoading: boolean;
@ -21,26 +9,18 @@ type FormFieldType = {
const FormField = ({ isLoading }: FormFieldType) => { const FormField = ({ isLoading }: FormFieldType) => {
const [t] = useTranslation(); const [t] = useTranslation();
const {isValid} = useFormikContext();
console.log(isValid,"isValid");
return ( return (
<Form className="AuthForm"> <Form className="AuthForm">
{/* <Image style={{background:"#000"}} src="../App/Logo.png" /> */} {/* <Image style={{background:"#000"}} src="../App/Logo.png" /> */}
<h2>{t("تسجيل الدخول إلى حسابك")}</h2> <h2>{t("تسجيل الدخول إلى حسابك")}</h2>
<div className="AuthInput">
<label className="form-label" htmlFor="username">
{t("input.Username")}
</label>
<Field
placeholder={t("input.Username")}
as={Input}
type="text"
id="username"
name="username"
className="Input"
size="large"
/>
</div>
<div className="AuthInput"> <ValidationField name="username" label="username" />
<ValidationField name="password" label="password" />
{/* <div className="AuthInput">
<label className="form-label" htmlFor="password"> <label className="form-label" htmlFor="password">
{t("input.Password")} {t("input.Password")}
</label> </label>
@ -53,9 +33,9 @@ const FormField = ({ isLoading }: FormFieldType) => {
className="passwordInput" className="passwordInput"
size="large" size="large"
/> />
</div> </div> */}
<button disabled={isLoading} type="submit" className="auth_submit_button"> <button disabled={ !isValid || isLoading} type="submit" className="auth_submit_button">
{t("practical.login")} {t("practical.login")}
</button> </button>
</Form> </Form>

View File

@ -4,7 +4,7 @@ import useAuthState from "../../zustand/AuthState";
import useNavigateOnSuccess from "../../Hooks/useNavigateOnSuccess"; import useNavigateOnSuccess from "../../Hooks/useNavigateOnSuccess";
import { useLoginAdmin } from "../../api/auth"; import { useLoginAdmin } from "../../api/auth";
import FormField from "./FormField"; import FormField from "./FormField";
import { initialValues } from "./formutils"; import { initialValues, validationSchema } 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";
@ -39,7 +39,7 @@ const LoginForm = () => {
return ( return (
<div className="LoginForm"> <div className="LoginForm">
<Formik initialValues={initialValues} onSubmit={handelSubmit}> <Formik initialValues={initialValues} onSubmit={handelSubmit} isInitialValid={false} validationSchema={validationSchema} >
{(formikProps) => <FormField isLoading={isLoading} />} {(formikProps) => <FormField isLoading={isLoading} />}
</Formik> </Formik>
</div> </div>

View File

@ -2,8 +2,8 @@ import * as Yup from "yup";
import { FormValues } from "../../types/Auth"; import { FormValues } from "../../types/Auth";
export const validationSchema = Yup.object().shape({ export const validationSchema = Yup.object().shape({
username: Yup.string().required("Username is required"), username: Yup.string().required("validation.required"),
password: Yup.string().required("Password is required"), password: Yup.string().required("validation.required").min(8,"validation.Password_must_be_at_least_8_characters_long"),
}); });
export const initialValues: FormValues = { export const initialValues: FormValues = {

View File

@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next";
import { ABILITIES_ENUM } from "../../enums/abilities"; import { ABILITIES_ENUM } from "../../enums/abilities";
import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useSetPageTitle from "../../Hooks/useSetPageTitle";
import useFilter from "../../Components/FilterField/components/useFilter"; import useFilter from "../../Components/FilterField/components/useFilter";
import { Button, Popconfirm } from "antd";
import PageTitle from "../../Layout/Dashboard/PageTitle";
const Dummy = () => { const Dummy = () => {
const [t] = useTranslation(); const [t] = useTranslation();
@ -10,8 +12,8 @@ const Dummy = () => {
const { FilterButton, FilterBody } = useFilter(); const { FilterButton, FilterBody } = useFilter();
return ( return (
<div className="DummyHomePage"> <div className="DummyHomePage">
{/* <FilterButton /> <PageTitle/>
<FilterBody>karim</FilterBody> */}
</div> </div>
); );
}; };

View File

@ -1,5 +1,5 @@
import { TCrudRoute, TMenuItem } from "./types/App"; import { TCrudRoute, TMenuItem } from "./types/App";
import { FaHome, FaMoneyBill, FaSellcast } from "react-icons/fa"; import { FaCashRegister, FaHome, FaMoneyBill, FaPaperclip, FaSellcast, FaTag, FaUser } from "react-icons/fa";
import React from "react"; import React from "react";
const Dummy = React.lazy(() => import("./Pages/Home/Dummy")); const Dummy = React.lazy(() => import("./Pages/Home/Dummy"));
@ -33,6 +33,7 @@ const EditReSeller = React.lazy(
const User = React.lazy(() => import("./Pages/Admin/User/Page")); const User = React.lazy(() => import("./Pages/Admin/User/Page"));
const Param = React.lazy(() => import("./Pages/Admin/Param/Page")); const Param = React.lazy(() => import("./Pages/Admin/Param/Page"));
const QuestionBank = React.lazy(() => import("./Pages/Admin/QuestionBank/Page"));
/// RESELLER /// /// RESELLER ///
const Student_Package = React.lazy( const Student_Package = React.lazy(
@ -44,12 +45,14 @@ import { ABILITIES_ENUM, ABILITIES_VALUES_ENUM } from "./enums/abilities";
import { ParamsEnum } from "./enums/params"; import { ParamsEnum } from "./enums/params";
import { TbCategory } from "react-icons/tb"; import { TbCategory } from "react-icons/tb";
import { UserTypeEnum } from "./enums/UserType"; import { UserTypeEnum } from "./enums/UserType";
import { FaTags } from "react-icons/fa6";
import { MdGrade } from "react-icons/md";
export const menuItems: TMenuItem[] = [ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.dashboard", header: "page_header.dashboard",
element: <Dummy />, element: <Dummy />,
icon: <TbCategory />, icon: <FaHome />,
text: "sidebar.dashboard", text: "sidebar.dashboard",
path: "/", path: "/",
abilities: ABILITIES_ENUM?.PASS, abilities: ABILITIES_ENUM?.PASS,
@ -60,7 +63,7 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.grade", header: "page_header.grade",
element: <Grade />, element: <Grade />,
icon: <FaMoneyBill />, icon: <TbCategory />,
text: "sidebar.grade", text: "sidebar.grade",
path: `/${ABILITIES_ENUM?.GRADE}`, path: `/${ABILITIES_ENUM?.GRADE}`,
abilities: ABILITIES_ENUM?.GRADE, abilities: ABILITIES_ENUM?.GRADE,
@ -70,7 +73,7 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.tags", header: "page_header.tags",
element: <Tags />, element: <Tags />,
icon: <FaMoneyBill />, icon: <FaTags />,
text: "sidebar.tags", text: "sidebar.tags",
path: `/${ABILITIES_ENUM?.TAG}`, path: `/${ABILITIES_ENUM?.TAG}`,
abilities: ABILITIES_ENUM?.TAG, abilities: ABILITIES_ENUM?.TAG,
@ -78,9 +81,9 @@ export const menuItems: TMenuItem[] = [
prevPath: 0, prevPath: 0,
}, },
{ {
header: "page_header.tags", header: "page_header.report",
element: <Report />, element: <Report />,
icon: <FaMoneyBill />, icon: <FaPaperclip />,
text: "sidebar.report", text: "sidebar.report",
path: `/${ABILITIES_ENUM?.Report}`, path: `/${ABILITIES_ENUM?.Report}`,
abilities: ABILITIES_ENUM?.Report, abilities: ABILITIES_ENUM?.Report,
@ -90,7 +93,7 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.student", header: "page_header.student",
element: <Student />, element: <Student />,
icon: <FaMoneyBill />, icon: <MdGrade />,
text: "sidebar.student", text: "sidebar.student",
path: `/${ABILITIES_ENUM?.STUDENT}`, path: `/${ABILITIES_ENUM?.STUDENT}`,
abilities: ABILITIES_ENUM?.STUDENT, abilities: ABILITIES_ENUM?.STUDENT,
@ -100,7 +103,7 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.reSeller", header: "page_header.reSeller",
element: <ReSeller />, element: <ReSeller />,
icon: <FaSellcast />, icon: <FaCashRegister />,
text: "sidebar.reseller", text: "sidebar.reseller",
path: `/${ABILITIES_ENUM?.RE_SELLER}`, path: `/${ABILITIES_ENUM?.RE_SELLER}`,
abilities: ABILITIES_ENUM?.RE_SELLER, abilities: ABILITIES_ENUM?.RE_SELLER,
@ -110,7 +113,7 @@ export const menuItems: TMenuItem[] = [
{ {
header: "page_header.user", header: "page_header.user",
element: <User />, element: <User />,
icon: <FaSellcast />, icon: <FaUser />,
text: "sidebar.user", text: "sidebar.user",
path: `/${ABILITIES_ENUM?.USER}`, path: `/${ABILITIES_ENUM?.USER}`,
abilities: ABILITIES_ENUM?.USER, abilities: ABILITIES_ENUM?.USER,
@ -128,6 +131,18 @@ export const menuItems: TMenuItem[] = [
prevPath: 0, prevPath: 0,
}, },
{
header: "page_header.questionBank",
element: <QuestionBank />,
icon: <FaSellcast />,
text: "sidebar.questionBank",
path: `/${ABILITIES_ENUM?.QUESTION}`,
abilities: ABILITIES_ENUM?.QUESTION,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0,
},
/// RESELLER ///// /// RESELLER /////
{ {

View File

@ -10,12 +10,7 @@
} }
} }
} }
x
.Popover {
.ant-popover-inner {
padding: 0 !important;
}
}
.Color_type_checkbox.false { .Color_type_checkbox.false {
.ant-checkbox-checked .ant-checkbox-inner { .ant-checkbox-checked .ant-checkbox-inner {

View File

@ -9,6 +9,9 @@
} }
.page_links { .page_links {
color: var(--opacity); color: var(--opacity);
display: flex;
align-items: center;
gap: 10px;
} }
} }
} }
@ -16,3 +19,12 @@
.filter_header_top { .filter_header_top {
color: #202c4b; color: #202c4b;
} }
.PageTitle{
display: flex;
gap: 10px;
.PageTitleItems{
cursor: pointer;
}
}

View File

@ -41,7 +41,7 @@
// padding: 2vw; // padding: 2vw;
.exercise_add_main { .exercise_add_main {
background: var(--bg); background: var(--bg);
padding: 2vw; padding: 10px 2vw;
} }
.exercise_add_buttons { .exercise_add_buttons {
display: flex; display: flex;
@ -127,12 +127,14 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
padding: 14px 10px; padding: 14px 20px;
background: #f2f4f8; background: #f2f4f8;
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
margin: 0 !important; margin: 0 !important;
box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.1); box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.1);
div{
margin-left: 25px;
}
img { img {
cursor: pointer; cursor: pointer;
} }
@ -144,7 +146,8 @@
.delete_question_options { .delete_question_options {
margin-top: 25px; margin-top: 25px;
color: var(--warning); color: var(--warning);
z-index: 9999;
cursor: pointer;
padding-left: 10px; padding-left: 10px;
.trash_icon { .trash_icon {
margin-right: 10px !important; margin-right: 10px !important;
@ -211,3 +214,44 @@
transform: translateY(55px); transform: translateY(55px);
z-index: -1; z-index: -1;
} }
.ant-popconfirm .ant-popconfirm-buttons{
display: flex;
align-items: center;
justify-content: center;
>{
flex: 1;
min-height: auto;
min-width: 30px;
}
.ant-btn{
min-height: 30px !important;
max-height: 30px !important;
min-width: 50px;
padding: 5px !important;
display: flex;
align-items: center;
justify-content: center ;
}
}
.QuestionPractical{
display: flex;
flex-direction: column;
background: var(--bg);
>header{
padding: 30px 2vw 10px 2vw;
}
}
.SelectTag{
label{
font-weight: bold;
font-size: 19px;
}
}

View File

@ -5,4 +5,4 @@ const API = {
LOGIN: `login`, LOGIN: `login`,
LOGOUT: `logout`, LOGOUT: `logout`,
}; };
export const useLoginAdmin = () => useAddMutation(KEY, API.LOGIN, false); export const useLoginAdmin = () => useAddMutation(KEY, API.LOGIN, true);

View File

@ -50,6 +50,8 @@ function useAxios() {
return response; return response;
}, },
function (error) { function (error) {
console.log(error?.response);
const status = error?.request?.status; const status = error?.request?.status;
const errorMsg = error?.response?.data?.message; const errorMsg = error?.response?.data?.message;
const errorField = error?.response?.data; const errorField = error?.response?.data;

View File

@ -186,4 +186,11 @@ export enum ModalEnum {
Student_Package_EDIT = "Student_Package.edit", Student_Package_EDIT = "Student_Package.edit",
Student_Package_ADD = "Student_Package.add", Student_Package_ADD = "Student_Package.add",
Student_Package_DELETE = "Student_Package.delete", Student_Package_DELETE = "Student_Package.delete",
///QuestionBank
QUESTION_BANK_ADD = "QuestionBank.add",
QUESTION_BANK_EDIT = "QuestionBank.edit",
QUESTION_BANK_DELETE = "QuestionBank.delete",
} }

View File

@ -47,6 +47,7 @@ export enum ABILITIES_ENUM {
User = "user", User = "user",
RE_SELLER = "reseller", RE_SELLER = "reseller",
Student_Package = "student_package", Student_Package = "student_package",
QUESTION_BANK = "QuestionBank"
//// ////
} }

View File

@ -46,7 +46,9 @@
"grade_to_pass_must_be_less_than_max_grade": "يجب أن تكون درجة النجاح أقل من الحد الأقصى للدرجة", "grade_to_pass_must_be_less_than_max_grade": "يجب أن تكون درجة النجاح أقل من الحد الأقصى للدرجة",
"max_mark_must_be_greater_than_min_mark_to_pass": "يجب ان تكون اكبر من علامة النجاح", "max_mark_must_be_greater_than_min_mark_to_pass": "يجب ان تكون اكبر من علامة النجاح",
"Sorry, the question must have at least one option": "عذرًا، يجب أن يحتوي السؤال على خيار واحد على الأقل", "Sorry, the question must have at least one option": "عذرًا، يجب أن يحتوي السؤال على خيار واحد على الأقل",
"at_least_one_answer_should_be_correct": "يجب أن تكون إجابة واحدة صحيحة" "at_least_one_answer_should_be_correct": "يجب أن تكون إجابة واحدة صحيحة",
"it_should_have_more_than_one_answers":"يجب أن يحتوي على أكثر من إجابة",
"it_should_have_more_than_one_correct_answers":"يجب أن يحتوي على إجابة صحيحة"
}, },
"header": { "header": {
"register_students": "تسجيل الطلاب", "register_students": "تسجيل الطلاب",
@ -123,7 +125,9 @@
"personal_information": "المعلومات الشخصية", "personal_information": "المعلومات الشخصية",
"address": "العنوان", "address": "العنوان",
"attachment": "المرفق", "attachment": "المرفق",
"subject_of_class": "مواد الصف" "subject_of_class": "مواد الصف",
"this_will_un_do_all_your_changes":"سوف يؤدي هذا إلى إلغاء جميع تغييراتك",
"edit_question":"تعديل سؤال"
}, },
"columns": { "columns": {
"id": "الرقم التعريفي", "id": "الرقم التعريفي",
@ -183,7 +187,12 @@
"subject":"المادة", "subject":"المادة",
"quiz_status":"حالة الاختبار", "quiz_status":"حالة الاختبار",
"creator_name":"اسم المنشئ", "creator_name":"اسم المنشئ",
"created_by":"أنشئ بواسطة" "created_by":"أنشئ بواسطة",
"question_type": "نوع تمرين",
"base_question": " تمرين متعدد ",
"normal_question": " تمرين عادي",
"hint":"شرح ",
"tags":"كلمات مفتاحية"
}, },
"practical": { "practical": {
"to_confirm_deletion_please_re_enter": "لتأكيد الحذف، يرجى إعادة الإدخال", "to_confirm_deletion_please_re_enter": "لتأكيد الحذف، يرجى إعادة الإدخال",
@ -749,7 +758,8 @@
"reseller": "البائعين", "reseller": "البائعين",
"param": "معامل", "param": "معامل",
"student_package": "حزمة الطالب", "student_package": "حزمة الطالب",
"quiz":"الاختبارات" "quiz":"الاختبارات",
"questionBank":"بنك الأسئلة"
}, },
"message": { "message": {
"some_thing_went_wrong": "حدث خطأ ما", "some_thing_went_wrong": "حدث خطأ ما",
@ -784,17 +794,18 @@
"grade": "الصفوف", "grade": "الصفوف",
"report": "تقرير", "report": "تقرير",
"tags": "كلمات مفتاحية", "tags": "كلمات مفتاحية",
"reseller":"البائعين" "reseller":"البائعين",
"QuestionBank":"بنك الأسئلة"
}, },
"page_header": { "page_header": {
"dashboard": "لوحة القيادة / الصفحة الرئيسية", "home": "لوحة القيادة",
"course": " لوحة القيادة / الصفوف ", "course": " الصفوف ",
"teacher": " لوحة القيادة / المعلمون", "teacher": " المعلمون",
"payment": " لوحة القيادة / الدفعات", "payment": " الدفعات",
"branch": " لوحة القيادة / الفروع", "branch": " الفروع",
"role": " لوحة القيادة / الادوار", "role": " الادوار",
"student": " لوحة القيادة / قائمة الطلاب ", "student": " قائمة الطلاب ",
"admin": " لوحة القيادة / المسؤولون", "admin": " المسؤولون",
"student_details": "تفاصيل الطالب", "student_details": "تفاصيل الطالب",
"create_student": "إنشاء طالب", "create_student": "إنشاء طالب",
"course_details": "تفاصيل الصف", "course_details": "تفاصيل الصف",
@ -802,32 +813,34 @@
"student_payment": "دفع الطالب", "student_payment": "دفع الطالب",
"student_note": "ملاحظات الطالب", "student_note": "ملاحظات الطالب",
"student_status": "حالة الطالب", "student_status": "حالة الطالب",
"education_class": "لوحة القيادة / الصفوف / الشعب", "education_class": "الصفوف / الشعب",
"education_class_details": " لوحة القيادة / الصفوف / الشعب / تفاصيل الشعبة", "education_class_details": " الصفوف / الشعب / تفاصيل الشعبة",
"subject_details": " لوحة القيادة / تفاصيل المادة ", "subject_details": " تفاصيل المادة ",
"cycle": "لوحة القيادة / السنة دراسية ", "cycle": " السنة دراسية ",
"term": "لوحة القيادة / الفصل ", "term": "الفصل ",
"unit_details": "لوحة القيادة / تفاصيل المادة / تفاصيل الوحدة", "unit_details": "تفاصيل المادة / تفاصيل الوحدة",
"lesson_details": "لوحة القيادة / تفاصيل المادة / تفاصيل الوحدة / تفاصيل الدرس", "lesson_details": "تفاصيل المادة / تفاصيل الوحدة / تفاصيل الدرس",
"exercise_add": "لوحة القيادة / تفاصيل الدرس / إضافة تمارين ", "exercise_add": "تفاصيل الدرس / إضافة تمارين ",
"subject": "لوحة القيادة / المادة", "subject": "المادة",
"tags": "لوحة القيادة / كلمات مفتاحية", "tags": "كلمات مفتاحية",
"Question": "لوحة القيادة /اسئلة ", "Question": "سئلة ",
"add_Question": "لوحة القيادة /إضافة اسئلة ", "add_Question": "ضافة اسئلة ",
"edit_Question": "لوحة القيادة /تعديل اسئلة ", "edit_Question": "عديل اسئلة ",
"grade": "لوحة القيادة / الصفوف", "grade": "الصفوف",
"report": "تقرير", "report": "تقرير",
"users": "لوحة القيادة / المستخدمون", "users": "المستخدمون",
"reseller": " لوحة القيادة / البائعين", "reseller": " البائعين",
"add_reseller": " لوحة القيادة / البائعين / إضافة بائع ", "add_reseller": " البائعين / إضافة بائع ",
"param": "معامل", "param": "معامل",
"student_package": "حزمة الطالب" "student_package": "حزمة الطالب",
"QuestionBank":"بنك الأسئلة"
}, },
"table": { "table": {
"student": "قائمة الطلاب", "student": "قائمة الطلاب",
"reseller": "البائعين", "reseller": "البائعين",
"grade": "قائمة الصفوف", "grade": "قائمة الصفوف",
"subjects": "مواد الصف" "subjects": "مواد الصف",
"QuestionBank":"بنك الأسئلة"
}, },
"alphabet": { "alphabet": {
"A": "A", "A": "A",

View File

@ -677,3 +677,25 @@ export const canDeleteStudent_Package = hasAbility(
); );
/// QuestionBank
export const canAddQuestionBank = hasAbility(
ABILITIES_ENUM.QUESTION_BANK,
ABILITIES_VALUES_ENUM.STORE,
);
export const canEditQuestionBank = hasAbility(
ABILITIES_ENUM.QUESTION_BANK,
ABILITIES_VALUES_ENUM.UPDATE,
);
export const canDeleteQuestionBank = hasAbility(
ABILITIES_ENUM.QUESTION_BANK,
ABILITIES_VALUES_ENUM.DELETE,
);
export const canShowQuestionBank = hasAbility(
ABILITIES_ENUM.QUESTION_BANK,
ABILITIES_VALUES_ENUM.SHOW,
);