Compare commits

..

3 Commits

Author SHA1 Message Date
karimaldeen
a05bb8b46d Merge branch 'dev' of https://git.point-dev.net/Karimaldeen/Quiz_dashboard into dev 2024-09-15 14:45:28 +03:00
karimaldeen
d6c8ff6468 fix image 2024-09-15 14:44:33 +03:00
karimaldeen
e8e50d0526 add filter 2024-09-15 12:53:32 +03:00
24 changed files with 366 additions and 194 deletions

View File

@ -1,43 +1,33 @@
import {
DownloadOutlined,
RotateLeftOutlined,
RotateRightOutlined,
SwapOutlined,
ZoomInOutlined,
ZoomOutOutlined,
} from "@ant-design/icons";
import React from "react";
import React, { useState } from "react";
import { Image, Space } from "antd";
import useImageError from "../../Hooks/useImageError";
import { ErrorImage } from "../../Layout/app/Const";
import { CiImageOff } from "react-icons/ci"; // Assuming you're using this icon from react-icons
const ColumnsImage = ({ src }: any) => {
const imageUrl = src || ErrorImage;
const [hasError, setHasError] = useState(false); // Track if the image has an error
const handleError = useImageError;
// or you can download flipped and rotated image
// https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp
const onDownload = () => {
fetch(src)
.then((response) => response.blob())
.then((blob) => {
const url = URL.createObjectURL(new Blob([blob]));
const link = document.createElement("a");
link.href = url;
link.download = "image.png";
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
link.remove();
});
};
const imageUrl = src;
if (hasError) {
// If there is an error, display the fallback icon
return <CiImageOff />;
}
if(!imageUrl){
return <CiImageOff />;
}
return (
<Image
width={45}
height={45}
src={imageUrl}
className="p-1 mb-1 columnImage"
onError={() => setHasError(true)} // Triggered when image fails to load
preview={{
toolbarRender: (
_,
@ -54,7 +44,6 @@ const ColumnsImage = ({ src }: any) => {
},
) => (
<Space size={12} className="toolbar-wrapper">
{/* <DownloadOutlined onClick={onDownload} /> */}
<SwapOutlined rotate={90} onClick={onFlipY} />
<SwapOutlined onClick={onFlipX} />
<RotateLeftOutlined onClick={onRotateLeft} />
@ -64,19 +53,8 @@ const ColumnsImage = ({ src }: any) => {
</Space>
),
}}
onError={handleError}
/>
);
};
export default ColumnsImage;
// {
// name: t("image"),
// center: "true",
// cell: (row: any) => {
// return (
// <ColumnsImage src={row?.image} />
// )
// }
// },

View File

@ -1,7 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { IoSearch } from "react-icons/io5";
import { useLocation, useNavigate } from "react-router-dom";
import { useFilterStateState } from "../../zustand/Filter";
import { useDebounce } from "../../utils/useDebounce";
@ -15,15 +13,16 @@ const SearchField: React.FC<Props> = ({ placeholder, searchBy }) => {
const [searchQuery, setSearchQuery] = useState<string>("");
const inputRef = useRef<HTMLInputElement>(null);
const { setFilter } = useFilterStateState();
const { setFilter,Filter } = useFilterStateState();
const handleInputChange = (value: string) => {
setSearchQuery(value);
};
console.log(searchBy,"searchBy");
const handleInputChangeWithDebounce = useDebounce((value: string) => {
setFilter({
name: value,
[searchBy]: value,
});
});

View File

@ -7,7 +7,7 @@
margin-bottom: 10px;
position: relative;
min-height: 80px;
padding-inline: 20px;
// padding-inline: 20px;
> * {
width: 100% !important;
min-width: 150px;

View File

@ -14,9 +14,15 @@ import useFilter from "../../Components/Utils/Filter/useFilter";
const FilterLayout = ({
filterTitle,
sub_children,
search_by = "name",
width = "500px",
haveFilter=true
}: {
filterTitle: string;
sub_children: any;
search_by?:string
width?:string
haveFilter?:boolean
}) => {
const { t } = useTranslation();
const translateArray = translateOptions(search_array, t);
@ -37,10 +43,13 @@ const FilterLayout = ({
ModelClassName="filter_model_direction"
isOpen={isOpen}
setIsOpen={setIsOpen}
width={width}
>
<div className="model_sub_children">{sub_children}</div>
<FilterSubmit />
</FilterBody>
{haveFilter &&
<div className="filter_button" onClick={() => setIsOpen(true)}>
<span>
<BiFilterAlt className="addition_select_icon" />
@ -48,6 +57,9 @@ const FilterLayout = ({
</span>
<MdKeyboardArrowDown />
</div>
}
</span>
<span>
@ -66,7 +78,7 @@ const FilterLayout = ({
</span>
<div className="header_search">
<SearchField searchBy="" placeholder={t("practical.search_here")} />
<SearchField searchBy={search_by} placeholder={t("practical.search_here")} />
</div>
</div>
</div>

View File

@ -35,9 +35,6 @@ const FormikFormModel: React.FC<FormikFormProps> = ({
useEffect(() => {
if (isOpen === "") {
formik.setErrors({});
}
if (isOpen === "isSuccess") {
formik.setErrors({});
formik.resetForm();
}
}, [isOpen]);

View File

@ -27,12 +27,6 @@ const PageHeader = ({
const { t } = useTranslation();
const PrevPath = getPrevPathRoute(location.pathname);
const handelNavigate = () => {
if (PrevPath === 0) {
return;
}
navigate(deletePathSegments(location.pathname, PrevPath));
};
const handleNavigateToPage = (location: string) => {
navigate(location);
};
@ -41,7 +35,7 @@ const PageHeader = ({
return (
<div className="page_header">
<header className="d-flex justify-content-between">
<span className="page_header_links" onClick={handelNavigate}>
<span className="page_header_links" >
<h1 className="page_title">{t(`PageTitle.${pageTitle}`)}</h1>
<span className="page_links">
<PageTitleComponent/>

View File

@ -15,6 +15,8 @@ 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";
export const useColumns = () => {
const { handel_open_model } = useModalHandler();
@ -59,20 +61,11 @@ export const useColumns = () => {
align: "center",
render: (_text: any, record: Grade) => {
let str = record?.icon;
return <ColumnsImage src={str}/> ;
},
},
{
// canAddGrade ? (
// <button
// onClick={() => handel_open_model(ModalEnum?.GRADE_ADD)}
// className="add_button"
// >
// {t("practical.add")} {t("models.grade")} <FaPlus />
// </button>
// ) : (
// ""
// ),
title: t("columns.procedure"),
key: "actions",

View File

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

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

@ -2,15 +2,141 @@ import React from "react";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
import { Col, Row } from "reactstrap";
import { useFormikContext } from "formik";
import { useGetAllGrade } from "../../../../api/grade";
import { useValidationValidationParamState } from "../../../../Components/ValidationField/state/ValidationValidationParamState";
import { useGetAllUnit } from "../../../../api/unit";
import { useGetAllSubject } from "../../../../api/subject";
import { useGetAllLesson } from "../../../../api/lesson";
const FilterForm = () => {
const formik = useFormikContext();
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
SubjectName, SubjectCurrentPage,
UnitName, UnitCurrentPage,
LessonName, LessonCurrentPage
} = ValidationParamState;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
/// subject_id
const { data: Subject, isLoading: isLoadingSubject } = useGetAllSubject({
name: SubjectName,
page: SubjectCurrentPage
});
const SubjectOption = Subject?.data ?? []
const canChangeSubjectPage = !!Subject?.links?.next;
const SubjectPage = Subject?.meta?.currentPage;
/// unit_id
const { data: Unit, isLoading: isLoadingUnit } = useGetAllUnit({
name: UnitName,
page: UnitCurrentPage
});
const UnitOption = Unit?.data ?? []
const canChangeUnitPage = !!Unit?.links?.next;
const UnitPage = Unit?.meta?.currentPage;
/// lessonsIds
const { data: Lesson, isLoading: isLoadingLesson } = useGetAllLesson({
name: LessonName,
page: LessonCurrentPage
});
const LessonOption = Lesson?.data ?? []
const canChangeLessonPage = !!Lesson?.links?.next;
const LessonPage = Lesson?.meta?.currentPage;
return (
<div>
<Row>
<Col>
<ValidationField placeholder="name" label="name" name="name" />
{/*
grade_id
*/}
<ValidationField
searchBy="GradeName"
name="grade_id"
label="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
{/*
subject_id
*/}
<ValidationField
searchBy="SubjectName"
name="subject_id"
label="subject"
type="Search"
option={SubjectOption}
isLoading={isLoadingSubject}
canChangePage={canChangeSubjectPage}
PageName={"SubjectCurrentPage"}
page={SubjectPage}
/>
</Col>
<Col>
{/*
unit_id
*/}
<ValidationField
searchBy="UnitName"
name="unit_id"
label="unit"
type="Search"
option={UnitOption}
isLoading={isLoadingUnit}
canChangePage={canChangeUnitPage}
PageName={"UnitCurrentPage"}
page={UnitPage}
/>
{/*
lessonsIds
*/}
<ValidationField
searchBy="LessonName"
name="lessonsIds"
label="lesson"
type="Search"
option={LessonOption}
isMulti
isLoading={isLoadingLesson}
canChangePage={canChangeLessonPage}
PageName={"LessonCurrentPage"}
page={LessonPage}
/>
</Col>
</Row>
</div>

View File

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

@ -10,8 +10,6 @@ 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"),
);
@ -33,10 +31,8 @@ const TableHeader = () => {
ModelAbility={ModalEnum?.QUESTION_BANK_ADD}
canAdd={canAddQuestionBank}
/>
<FilterLayout sub_children={<FilterForm />} filterTitle="table.QuestionBank" />
<FilterLayout width="700px" search_by="content" sub_children={<FilterForm />} filterTitle="table.QuestionBank" />
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.QUESTION_BANK_DELETE}

View File

@ -3,13 +3,20 @@ import React from "react";
import DataTable from "../../../Layout/Dashboard/Table/DataTable";
import { useGetAllQuestion } from "../../../api/Question";
import { useFilterState } from "../../../Components/Utils/Filter/FilterState";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useFilterStateState } from "../../../zustand/Filter";
const App: React.FC = () => {
const { filterState } = useFilterState();
const { setFilter,Filter } = useFilterStateState();
console.log(filterState,"filterState");
const response = useGetAllQuestion({
pagination: true,
...filterState,
content:Filter?.content
});
return <DataTable response={response} useColumns={useColumns} />;

View File

@ -1,6 +1,5 @@
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";
@ -8,7 +7,6 @@ import { ABILITIES_ENUM } from "../../../enums/abilities";
import { useNavigate } from "react-router-dom";
import { useModalState } from "../../../zustand/Modal";
import {
canAddQuestion,
canDeleteQuestion,
canEditQuestion,
} from "../../../utils/hasAbilityFn";
@ -19,19 +17,18 @@ export const useColumns = () => {
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}`);
console.log(record,"record");
const lesson = record?.lessons?.[0] ;
const unit = lesson?.unit;
const subject = unit?.subject;
const grade = subject?.grade;
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();
@ -51,6 +48,24 @@ export const useColumns = () => {
render: (text, record) => record?.content,
ellipsis: true,
},
{
title: `${t("columns.course")}`,
dataIndex: "lessons",
key: "lessons",
align: "center",
render: (text, record) => {
const lesson = record?.lessons?.[0] ;
const unit = lesson?.unit;
const subject = unit?.subject;
const grade = subject?.grade;
return (
<> {grade?.name} </>
)
},
ellipsis: true,
},
{
title: `${t("columns.hint")}`,
dataIndex: "hint",

View File

@ -1,27 +1,143 @@
import React from "react";
import ValidationField from "../../../Components/ValidationField/ValidationField";
import { Col, Row } from "reactstrap";
import useFormatDataToSelect from "../../../utils/useFormatDataToSelect";
import { useFormikContext } from "formik";
import { useGetAllGrade } from "../../../api/grade";
import { useValidationValidationParamState } from "../../../Components/ValidationField/state/ValidationValidationParamState";
import { useGetAllUnit } from "../../../api/unit";
import { useGetAllSubject } from "../../../api/subject";
import { useGetAllLesson } from "../../../api/lesson";
const FilterForm = () => {
const yesNoArray = [
{ id: "لا", name: "لا" },
{ id: "نعم", name: "نعم" },
];
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
SubjectName, SubjectCurrentPage,
UnitName, UnitCurrentPage,
LessonName, LessonCurrentPage
} = ValidationParamState;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
/// subject_id
const { data: Subject, isLoading: isLoadingSubject } = useGetAllSubject({
name: SubjectName,
page: SubjectCurrentPage
});
const SubjectOption = Subject?.data ?? []
const canChangeSubjectPage = !!Subject?.links?.next;
const SubjectPage = Subject?.meta?.currentPage;
/// unit_id
const { data: Unit, isLoading: isLoadingUnit } = useGetAllUnit({
name: UnitName,
page: UnitCurrentPage
});
const UnitOption = Unit?.data ?? []
const canChangeUnitPage = !!Unit?.links?.next;
const UnitPage = Unit?.meta?.currentPage;
/// lessonsIds
const { data: Lesson, isLoading: isLoadingLesson } = useGetAllLesson({
name: LessonName,
page: LessonCurrentPage
});
const LessonOption = Lesson?.data ?? []
const canChangeLessonPage = !!Lesson?.links?.next;
const LessonPage = Lesson?.meta?.currentPage;
return (
<div>
<Row>
<Col>
{/*
grade_id
*/}
<ValidationField
placeholder="content"
label="content"
name="content"
searchBy="GradeName"
name="grade_id"
label="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
{/*
subject_id
*/}
<ValidationField
searchBy="SubjectName"
name="subject_id"
label="subject"
type="Search"
option={SubjectOption}
isLoading={isLoadingSubject}
canChangePage={canChangeSubjectPage}
PageName={"SubjectCurrentPage"}
page={SubjectPage}
/>
</Col>
<Col>
{/*
unit_id
*/}
<ValidationField
searchBy="UnitName"
name="unit_id"
label="unit"
type="Search"
option={UnitOption}
isLoading={isLoadingUnit}
canChangePage={canChangeUnitPage}
PageName={"UnitCurrentPage"}
page={UnitPage}
/>
{/*
lessonsIds
*/}
<ValidationField
searchBy="LessonName"
name="lessonsIds"
label="lesson"
type="Search"
option={LessonOption}
isMulti
isLoading={isLoadingLesson}
canChangePage={canChangeLessonPage}
PageName={"LessonCurrentPage"}
page={LessonPage}
/>
{/* <ValidationField type="Select" option={yesNoArray} placeholder="isBase" label="isBase" name="isBase" /> */}
</Col>
{/* <Col>
<ValidationField type="Select" option={yesNoArray} placeholder="canAnswersBeShuffled" label="canAnswersBeShuffled" name="canAnswersBeShuffled" />
</Col> */}
</Row>
</div>
);

View File

@ -65,6 +65,8 @@ const TableHeader = () => {
<FilterLayout
sub_children={<FilterForm />}
filterTitle={` ${unitName} (${LessonName}) `}
search_by="content"
haveFilter={false}
/>
<Table />
</Suspense>

View File

@ -5,15 +5,18 @@ import { useGetAllQuestion } from "../../../api/Question";
import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../../enums/params";
import { useFilterState } from "../../../Components/Utils/Filter/FilterState";
import { useFilterStateState } from "../../../zustand/Filter";
const App: React.FC = () => {
const { lesson_id } = useParams<ParamsEnum>();
const { filterState } = useFilterState();
const { setFilter,Filter } = useFilterStateState();
const response = useGetAllQuestion({
lessonsIds: [lesson_id],
pagination: true,
...filterState,
content:Filter?.content
});
return <DataTable response={response} useColumns={useColumns} />;
};

View File

@ -14,6 +14,7 @@ import {
} from "../../../../utils/hasAbilityFn";
import { ABILITIES_ENUM } from "../../../../enums/abilities";
import { Subject } from "../../../../types/Subject";
import { CiImageOff } from "react-icons/ci";
export const useColumns = () => {
const navigate = useNavigate();

View File

@ -61,6 +61,9 @@
border-radius: var(--border-radius);
transition: 1s ease-in-out;
animation: fadeInRight 1s ease-in-out;
max-height: 90vh;
overflow-y: scroll;
@include Scrollbar;
> header {
display: flex;

View File

@ -85,3 +85,8 @@
font-size: 8px !important;
}
}
.model_sub_children{
padding-bottom: 30px;
}

View File

@ -209,12 +209,12 @@
}
.SelectTag {
padding-inline: 20px;
// padding-inline: 20px;
}
.exercise_add {
.add_new_button {
padding-inline: 20px !important;
// padding-inline: 20px !important;
}
}
@ -229,12 +229,12 @@
display: flex;
align-items: center;
justify-content: center;
>{
flex: 1;
min-height: auto;
min-width: 30px;
// >{
// flex: 1;
// min-height: auto;
// min-width: 30px;
}
// }
.ant-btn{
min-height: 30px !important;
max-height: 30px !important;

View File

@ -193,6 +193,7 @@
"normal_question": " تمرين عادي",
"hint":"شرح ",
"tags":"كلمات مفتاحية",
"course":" الصفوف",
"student_full_name":"اسم الطالب الثلاثي",
"amount_paid":"المبلغ المدفوع",
"sale_date":"تاريخ البيع",
@ -444,6 +445,10 @@
"city": "المحافظة",
"personal_image": "صورة شخصية",
"id_image": "صورة الهوية",
"grade":"الصفوف",
"subject":"المادة",
"unit":"الوحدة",
"lesson":"الدرس",
"date_of_receipt":"تاريخ الاستلام",
"amount_value":"قيمة المبلغ"
},

View File

@ -315,7 +315,8 @@ export interface Question {
question_options_count?: any;
answers: QuestionOption[];
hint?: string;
tags: tags[]; // Assuming tags are strings, adjust as per actual data type
tags: tags[];
lessons:any
}
export type report = {

View File

@ -0,0 +1,9 @@
export const isValidImage = (url: string): Promise<boolean> => {
return new Promise((resolve) => {
const img = new Image();
img.src = url;
img.onload = () => resolve(true); // Image loaded successfully
img.onerror = () => resolve(false); // Error loading image
});
};