From 0b44bc69fb60e57a308d93c0ca870430b6880125 Mon Sep 17 00:00:00 2001 From: karimaldeen Date: Mon, 16 Sep 2024 12:01:35 +0300 Subject: [PATCH 1/3] fix image 2mb and add delete ask --- .../ImageBoxField/ImageBoxField.tsx | 16 +++++- .../FilterField/components/useFilter.tsx | 56 +++++++++++++++++-- .../FilterField/design-system/CustomInput.tsx | 22 ++++++++ src/Components/FilterField/styles/index.scss | 33 +++++++++++ src/Components/Utils/Filter/useFilter.tsx | 5 -- src/Components/ValidationField/View/File.tsx | 14 +++++ src/Layout/Dashboard/DeleteModels.tsx | 2 - src/Pages/Admin/question/AddPage.tsx | 2 +- .../question/Model/Field/ChoiceFields.tsx | 16 +++++- .../Model/Malty/ChoiceField/ChoiceFields.tsx | 14 ++++- .../Malty/QuestionFIeld/QuestionFIeld.tsx | 14 ++++- src/Pages/Home/Dummy.tsx | 14 ++--- src/Styles/Layout/SideBar.scss | 7 +++ src/translate/ar.json | 3 +- 14 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 src/Components/FilterField/design-system/CustomInput.tsx create mode 100644 src/Components/FilterField/styles/index.scss 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/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 0a2179d..8d91c59 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": "تسجيل الطلاب", From 458319b49f49e4622388d9fe9f0610b3c79886f2 Mon Sep 17 00:00:00 2001 From: karimaldeen Date: Mon, 16 Sep 2024 12:14:19 +0300 Subject: [PATCH 2/3] fix page --- src/Pages/Admin/Grade/useTableColumns.tsx | 8 ++++++-- src/Pages/Admin/QuestionBank/useTableColumns.tsx | 4 ++++ src/Pages/Admin/Unit/useTableColumns.tsx | 4 ++++ src/Pages/Admin/lesson/useTableColumns.tsx | 4 ++++ src/Pages/Admin/subject/Table/useTableColumns.tsx | 4 +++- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Pages/Admin/Grade/useTableColumns.tsx b/src/Pages/Admin/Grade/useTableColumns.tsx index 43f29f3..437b3c8 100644 --- a/src/Pages/Admin/Grade/useTableColumns.tsx +++ b/src/Pages/Admin/Grade/useTableColumns.tsx @@ -15,16 +15,20 @@ import { import ActionButtons from "../../../Components/Table/ActionButtons"; import ColumnsImage from "../../../Components/Columns/ColumnsImage"; import { Grade } from "../../../types/Grade"; -import { CiImageOff } from "react-icons/ci"; -import { isValidImage } from "../../../utils/isValidImage"; +import { useFilterState } from "../../../Components/Utils/Filter/FilterState"; +import { useFilterStateState } from "../../../zustand/Filter"; export const useColumns = () => { const { handel_open_model } = useModalHandler(); const { setObjectToEdit } = useObjectToEdit((state) => state); const navigate = useNavigate(); + const { setFilter } = useFilterStateState(); + + const handelShow = (record: Grade) => { + setFilter({}) navigate(`${record?.id}`); }; diff --git a/src/Pages/Admin/QuestionBank/useTableColumns.tsx b/src/Pages/Admin/QuestionBank/useTableColumns.tsx index def1863..0438bd6 100644 --- a/src/Pages/Admin/QuestionBank/useTableColumns.tsx +++ b/src/Pages/Admin/QuestionBank/useTableColumns.tsx @@ -11,11 +11,13 @@ import { canEditQuestion, } from "../../../utils/hasAbilityFn"; import ActionButtons from "../../../Components/Table/ActionButtons"; +import { useFilterStateState } from "../../../zustand/Filter"; export const useColumns = () => { const { setObjectToEdit } = useObjectToEdit((state) => state); const navigate = useNavigate(); const { setIsOpen } = useModalState((state) => state); + const { setFilter } = useFilterStateState(); const handelDelete = (data: any) => { setObjectToEdit(data); @@ -28,6 +30,8 @@ export const useColumns = () => { const unit = lesson?.unit; const subject = unit?.subject; const grade = subject?.grade; + + setFilter({}) navigate(`/${ABILITIES_ENUM?.GRADE}/${grade?.id}/${ABILITIES_ENUM?.SUBJECT}/${subject?.id}/${ABILITIES_ENUM?.UNIT}/${unit?.id}/${ABILITIES_ENUM?.LESSON}/${lesson?.id}/${ABILITIES_ENUM?.QUESTION}/${record?.id}`); }; const [t] = useTranslation(); diff --git a/src/Pages/Admin/Unit/useTableColumns.tsx b/src/Pages/Admin/Unit/useTableColumns.tsx index 968b848..fff47fd 100644 --- a/src/Pages/Admin/Unit/useTableColumns.tsx +++ b/src/Pages/Admin/Unit/useTableColumns.tsx @@ -19,14 +19,18 @@ import ActionButtons from "../../../Components/Table/ActionButtons"; import { Unit } from "../../../types/Unit"; import { ConvertEnumToTranslate } from "../../../utils/ConvertEnumToTranslate"; import { DragHandleUnit } from "./DrapableTable"; +import { useFilterState } from "../../../Components/Utils/Filter/FilterState"; +import { useFilterStateState } from "../../../zustand/Filter"; export const useColumns = () => { const { handel_open_model } = useModalHandler(); + const { setFilter } = useFilterStateState(); const { setObjectToEdit } = useObjectToEdit((state) => state); const navigate = useNavigate(); const handelShow = (record: Unit) => { + setFilter({}) navigate(`${ABILITIES_ENUM?.UNIT}/${record?.id}`); }; diff --git a/src/Pages/Admin/lesson/useTableColumns.tsx b/src/Pages/Admin/lesson/useTableColumns.tsx index e9d86f4..a44817d 100644 --- a/src/Pages/Admin/lesson/useTableColumns.tsx +++ b/src/Pages/Admin/lesson/useTableColumns.tsx @@ -15,14 +15,18 @@ import { } from "../../../utils/hasAbilityFn"; import ActionButtons from "../../../Components/Table/ActionButtons"; import { DragHandleLesson } from "./DrapableTable"; +import { useFilterState } from "../../../Components/Utils/Filter/FilterState"; +import { useFilterStateState } from "../../../zustand/Filter"; export const useColumns = () => { const { handel_open_model } = useModalHandler(); const { setObjectToEdit } = useObjectToEdit((state) => state); const navigate = useNavigate(); + const { setFilter } = useFilterStateState(); const handelShow = (record: any) => { + setFilter({}) navigate(`${ABILITIES_ENUM.LESSON}/${record?.id}`); }; 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}`); }; From 251359e935443c2b2a3c4f20724e07be0e5f9575 Mon Sep 17 00:00:00 2001 From: karimaldeen Date: Mon, 16 Sep 2024 12:34:01 +0300 Subject: [PATCH 3/3] add validation question --- src/Pages/Admin/question/AddPage.tsx | 27 +++++++++++++++------------ src/Pages/Admin/question/formUtil.ts | 25 ++++++++++++++----------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Pages/Admin/question/AddPage.tsx b/src/Pages/Admin/question/AddPage.tsx index d6c8c4b..0aa7986 100644 --- a/src/Pages/Admin/question/AddPage.tsx +++ b/src/Pages/Admin/question/AddPage.tsx @@ -35,7 +35,6 @@ const AddPage: React.FC = () => { const handleSubmit = ( values: any) => { const DataToSend = structuredClone(values); setTagsSearch(null); - console.log(1); const canAnswersBeShuffled = DataToSend?.canAnswersBeShuffled ? 1 : 0; @@ -64,12 +63,7 @@ const AddPage: React.FC = () => { ...item, }; }); - console.log(answers); - if (answers?.length > 0) { - const isValidAnswers = answers?.some( - (answer: any) => answer?.isCorrect === 1, - ); - } + mutate({ ...item, parent_id: newBseQuestionId, @@ -82,8 +76,6 @@ const AddPage: React.FC = () => { console.log(newBseQuestionId, "newBseQuestionId"); }); } else { - console.log(1); - const tags = processTags(DataToSend); const answers = values?.answers?.map((item: any, index: number) => { return { @@ -112,6 +104,8 @@ 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 ) + const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + if(!haveMoreThanOneAnswer){ toast.error(t("validation.it_should_have_more_than_one_answers")) ; return false ; @@ -120,6 +114,10 @@ const AddPage: React.FC = () => { toast.error(t("validation.it_should_have_more_than_one_correct_answers")) ; return false ; } + if(haveImageOrContent){ + toast.error("validation.one_of_image_and_content_should_be_enter") + return false + } } @@ -128,9 +126,9 @@ const AddPage: React.FC = () => { 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); - + const haveOneAnswerRight = haveMoreThanOneAnswer && answers?.some((item: any) => item?.isCorrect === 1 || item.isCorrect === true); + const haveImageOrContent = haveOneAnswerRight && values?.answers?.some((item:any)=> !(item?.content) && !(item.content_image) ) + if (!haveAnswers) { toast.error(t("validation.it_should_have_more_than_one_answers")); return false; @@ -145,6 +143,11 @@ const AddPage: React.FC = () => { toast.error(t("validation.it_should_have_more_than_one_correct_answers")); return false; } + + if(haveImageOrContent){ + toast.error("validation.one_of_image_and_content_should_be_enter") + return false + } return true; }); 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 ; }, ), }),