diff --git a/src/Components/CustomFields/ImageBoxField/ImageBoxField.tsx b/src/Components/CustomFields/ImageBoxField/ImageBoxField.tsx index 27b6787..65ef83e 100644 --- a/src/Components/CustomFields/ImageBoxField/ImageBoxField.tsx +++ b/src/Components/CustomFields/ImageBoxField/ImageBoxField.tsx @@ -5,6 +5,7 @@ import ImageIcon from "./ImageIcon"; import ImageCancelIcon from "./ImageCancelIcon"; import { getNestedValue } from "../../../utils/getNestedValue"; import { generateImagePreview } from "./generateImagePreview"; +import { useTranslation } from "react-i18next"; // Helper function to generate image preview from a File @@ -13,7 +14,7 @@ const ImageBoxField = ({ name }: any) => { const value = getNestedValue(formik?.values, name); const [imagePreview, setImagePreview] = useState(null); const fileInputRef = useRef(null); - + const [t] = useTranslation() useEffect(() => { if (value instanceof File) { generateImagePreview(value, setImagePreview); @@ -26,6 +27,19 @@ const ImageBoxField = ({ name }: any) => { const handleFileChange = (event: any) => { const file = event.target.files[0]; + if (file) { + const maxSize = 2 * 1024 * 1024; + + if (file.size > maxSize) { + alert(t('validation.File_size_exceeds_2_MB_limit.')); + event.target.value = ''; + return; + } + + // Process the file + console.log('File selected:', file); + } + if (file) { generateImagePreview(file, setImagePreview); formik.setFieldValue(name, file); diff --git a/src/Components/FilterField/components/useFilter.tsx b/src/Components/FilterField/components/useFilter.tsx index 232a226..c0919c1 100644 --- a/src/Components/FilterField/components/useFilter.tsx +++ b/src/Components/FilterField/components/useFilter.tsx @@ -1,14 +1,62 @@ -import React from "react"; +import React, { useCallback, useState } from "react"; +import '../styles/index.scss'; +import CustomInput from "../design-system/CustomInput"; +import { Button } from "antd"; +import { useTranslation } from "react-i18next"; interface IFilterBody { children: React.ReactNode; } + const useFilter = () => { - const FilterButton = () => { - return
FilterButton
; + const [isBodyVisible, setIsBodyVisible] = useState(true); + + const toggleBodyVisibility = () => { + setIsBodyVisible((prev) => !prev); }; + + const FilterButton = () => { + return ( + + ); + }; + const FilterBody = ({ children }: IFilterBody) => { - return
FilterBody
; + const [values, setValues] = useState({ name1: '', name2: '' }); + const handleChange = useCallback((e: React.ChangeEvent) => { + const { name, value } = e.target; + setValues((prev) => ({ ...prev, [name]: value })); + }, []); + + const handleSubmit = (event:React.FormEvent) => { + event.preventDefault(); + console.log(values,"values"); + + }; + + + const [t] = useTranslation() + return ( +
+
+ {children} + + + + + +
+ ); }; return { diff --git a/src/Components/FilterField/design-system/CustomInput.tsx b/src/Components/FilterField/design-system/CustomInput.tsx new file mode 100644 index 0000000..6a3ce24 --- /dev/null +++ b/src/Components/FilterField/design-system/CustomInput.tsx @@ -0,0 +1,22 @@ +import { Input } from 'antd'; +import React from 'react'; + +interface CustomInputProps { + name: string; + value: string; + onChange: (e: React.ChangeEvent) => void; +} + +const CustomInput: React.FC = React.memo(({ name, value, onChange }) => { + console.log(`Rendering ${name}`); // For debugging purposes + return ( + + ); +}); + +export default CustomInput; diff --git a/src/Components/FilterField/styles/index.scss b/src/Components/FilterField/styles/index.scss new file mode 100644 index 0000000..7f8934a --- /dev/null +++ b/src/Components/FilterField/styles/index.scss @@ -0,0 +1,33 @@ +.filter_body { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease-out, opacity 0.3s ease-out, transform 0.3s ease-out; + opacity: 0; + transform: translateY(-20px); + display: flex; + flex-wrap: wrap; + gap: 20px; + + } + + .filter_body.visible { + max-height: 200px; + opacity: 1; + transform: translateY(0); + } + + .filter_body.hidden { + max-height: 0; + opacity: 0; + transform: translateY(-20px); + } + + .DummyHomePage { + display: flex; + flex-direction: column; + align-items: center; + gap: 40px; + width: 70%; + padding: 50px; + } + \ No newline at end of file diff --git a/src/Components/Utils/Filter/useFilter.tsx b/src/Components/Utils/Filter/useFilter.tsx index 7ae32fa..a27e6ff 100644 --- a/src/Components/Utils/Filter/useFilter.tsx +++ b/src/Components/Utils/Filter/useFilter.tsx @@ -37,19 +37,14 @@ interface FormikFormProps extends Omit, OmitFormikProps> { setIsOpen: any; } -interface SubmitButtonProps extends Omit {} const useFilter = () => { const { setIsOpen, isOpen } = useModalState((state) => state); const { filterState, setFilterState, clearFilterState } = useFilterState(); const [t] = useTranslation(); const [formValues, setFormValues] = useState({}); - const formik = useFormikContext(); // Define the type for the callback type SubmitCallback = () => void; - // console.log(formik?.values); - // console.log(InitialValue); - const FilterButton = () => { const handleState = () => { if (isOpen === ModalEnum?.FILTER) { diff --git a/src/Components/ValidationField/View/File.tsx b/src/Components/ValidationField/View/File.tsx index 7b8d4d1..6c681cf 100644 --- a/src/Components/ValidationField/View/File.tsx +++ b/src/Components/ValidationField/View/File.tsx @@ -48,6 +48,18 @@ const File = ({ const customRequest = async ({ onSuccess, no_label, label_icon }: any) => { onSuccess(); }; + + const beforeUpload = (file: File) => { + const maxSize = 2 * 1024 * 1024; // 2 MB in bytes + + if (file.size > maxSize) { + alert(t('validation.File_size_exceeds_2_MB_limit.')); + return Upload.LIST_IGNORE; // Prevent the file from being uploaded + } + return true; // Allow the file to be uploaded + }; + + return (
diff --git a/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx b/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx index 46dac53..35940e2 100644 --- a/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx +++ b/src/Pages/Admin/question/Model/Malty/ChoiceField/ChoiceFields.tsx @@ -9,6 +9,7 @@ import TextField from "./TextField"; import { toast } from "react-toastify"; import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import { GoTrash } from "react-icons/go"; +import { Popconfirm } from "antd"; const ChoiceFields = ({ index, @@ -69,7 +70,16 @@ const ChoiceFields = ({ type="Checkbox" parent_index={parent_index} /> -

+ +{handleDeleteChoice()}} + defaultOpen={false} + + > +

{t("header.delete_choice")}

+ +
diff --git a/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx index 907b975..13b7aed 100644 --- a/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx +++ b/src/Pages/Admin/question/Model/Malty/QuestionFIeld/QuestionFIeld.tsx @@ -7,6 +7,7 @@ import TextField from "./TextField"; import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState"; import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField"; import { GoTrash } from "react-icons/go"; +import { Popconfirm } from "antd"; const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => { const formik = useFormikContext(); @@ -47,7 +48,15 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => { -
+
+ {handleDeleteQuestion()}} + defaultOpen={false} + + >

{t("header.delete_question")} { size={17} />

+
+ +
diff --git a/src/Pages/Admin/question/formUtil.ts b/src/Pages/Admin/question/formUtil.ts index 1a4f553..f8f43ed 100644 --- a/src/Pages/Admin/question/formUtil.ts +++ b/src/Pages/Admin/question/formUtil.ts @@ -25,10 +25,10 @@ export const getValidationSchema = () => { // validate input return Yup.object().shape({ content_image: Yup.string().nullable(), - content: Yup.string().required("validation.required"), + content: Yup.string().required("validation.required").nullable(), answers: Yup.array().of( Yup.object().shape({ - content: Yup.string().required("validation.required"), + content: Yup.string().nullable(), content_image: Yup.string().nullable(), isCorrect: Yup.boolean(), }), @@ -36,10 +36,12 @@ export const getValidationSchema = () => { "at-least-one-correct", "At least one answer must be correct", (answers: any) => { - return answers?.some( - (answer: any) => - answer?.isCorrect === true || answer?.isCorrect === 1, - ); + + const hasCorrectAnswer = answers?.some((answer:any) => answer?.isCorrect === true || answer?.isCorrect === 1); + + const haveImageOrContent = answers?.some((item:any)=> !(item?.content) && !(item.content_image)); + + return hasCorrectAnswer && !haveImageOrContent ; }, ), }); @@ -106,11 +108,12 @@ export const getValidationSchemaBase = () => { "at-least-one-correct", "At least one answer must be correct", (answers: any) => { - - return answers.some( - (answer: any) => - answer.isCorrect === true || answer.isCorrect === 1, - ); + + const hasCorrectAnswer = answers?.some((answer:any) => answer?.isCorrect === true || answer?.isCorrect === 1); + + const haveImageOrContent = answers?.some((item:any)=> !(item?.content) && !(item.content_image)); + + return hasCorrectAnswer && !haveImageOrContent ; }, ), }), diff --git a/src/Pages/Admin/subject/Table/useTableColumns.tsx b/src/Pages/Admin/subject/Table/useTableColumns.tsx index 2f3315b..b73808a 100644 --- a/src/Pages/Admin/subject/Table/useTableColumns.tsx +++ b/src/Pages/Admin/subject/Table/useTableColumns.tsx @@ -14,12 +14,13 @@ import { } from "../../../../utils/hasAbilityFn"; import { ABILITIES_ENUM } from "../../../../enums/abilities"; import { Subject } from "../../../../types/Subject"; -import { CiImageOff } from "react-icons/ci"; +import { useFilterStateState } from "../../../../zustand/Filter"; export const useColumns = () => { const navigate = useNavigate(); const { setObjectToEdit } = useObjectToEdit((state) => state); const { setIsOpen } = useModalState((state) => state); + const { setFilter } = useFilterStateState(); const handelDelete = (record: Subject) => { setObjectToEdit(record); @@ -31,6 +32,7 @@ export const useColumns = () => { }; const handelShow = (record: Subject) => { + setFilter({}) navigate(`${ABILITIES_ENUM?.SUBJECT}/${record?.id}`); }; diff --git a/src/Pages/Home/Dummy.tsx b/src/Pages/Home/Dummy.tsx index 0776ba5..6b178d0 100644 --- a/src/Pages/Home/Dummy.tsx +++ b/src/Pages/Home/Dummy.tsx @@ -1,19 +1,19 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { ABILITIES_ENUM } from "../../enums/abilities"; -import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useFilter from "../../Components/FilterField/components/useFilter"; -import { Button, Popconfirm } from "antd"; -import PageTitle from "../../Layout/Dashboard/PageTitle"; const Dummy = () => { const [t] = useTranslation(); - useSetPageTitle(`${t(ABILITIES_ENUM?.MAIN_PAGE)} / ${t("dashboard")}`); const { FilterButton, FilterBody } = useFilter(); return (
- - + + + + karim + + + karim2
); }; diff --git a/src/Styles/Layout/SideBar.scss b/src/Styles/Layout/SideBar.scss index b0ef496..c127335 100644 --- a/src/Styles/Layout/SideBar.scss +++ b/src/Styles/Layout/SideBar.scss @@ -10,6 +10,7 @@ height: 100vh; overflow-y: auto; overflow-x: hidden; + background: var(--bgSideBar); color: var(--textSideBar); position: absolute; @@ -19,6 +20,12 @@ flex-direction: column; z-index: 2; + &::-webkit-scrollbar { + display: none; + } + + + .side_bar_header { height: var(--navBarHeight); display: flex; diff --git a/src/translate/ar.json b/src/translate/ar.json index 9f8633d..58b5100 100644 --- a/src/translate/ar.json +++ b/src/translate/ar.json @@ -48,7 +48,8 @@ "Sorry, the question must have at least one option": "عذرًا، يجب أن يحتوي السؤال على خيار واحد على الأقل", "at_least_one_answer_should_be_correct": "يجب أن تكون إجابة واحدة صحيحة", "it_should_have_more_than_one_answers": "يجب أن يحتوي على أكثر من إجابة", - "it_should_have_more_than_one_correct_answers": "يجب أن يحتوي على إجابة صحيحة" + "it_should_have_more_than_one_correct_answers": "يجب أن يحتوي على إجابة صحيحة", + "File_size_exceeds_2_MB_limit.":"حجم الملف يتجاوز الحد الأقصى البالغ 2 ميجابايت" }, "header": { "register_students": "تسجيل الطلاب",