Compare commits

..

5 Commits

Author SHA1 Message Date
karimaldeen
3ff4e0f846 Merge branch 'dev' of https://git.point-dev.net/Karimaldeen/Quiz_dashboard into dev 2024-09-30 16:25:06 +03:00
karimaldeen
3525d8f667 add coupon 2024-09-30 16:20:58 +03:00
karimaldeen
862380b2b3 #187 && #189 2024-09-30 11:30:58 +03:00
karimaldeen
0d3856d725 fix 2024-09-29 12:20:44 +03:00
karimaldeen
80dffd913e tyay math 2024-09-26 12:59:39 +03:00
43 changed files with 3866 additions and 2786 deletions

View File

@ -24,6 +24,7 @@
"toastify", "toastify",
"Viewelement", "Viewelement",
"webp", "webp",
"Xmark",
"zustand", "zustand",
"مطلوب" "مطلوب"
], ],

1444
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,53 +3,40 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.5.1",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@types/katex": "^0.16.7", "@types/katex": "^0.16.7",
"@uiw/react-markdown-preview": "^5.1.3", "@uiw/react-markdown-preview": "^5.1.3",
"antd": "^5.17.4", "antd": "^5.21.1",
"axios": "^1.7.2", "axios": "^1.7.7",
"better-react-mathjax": "^2.0.3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"dayjs": "^1.11.11", "dayjs": "^1.11.13",
"formik": "^2.4.6", "formik": "^2.4.6",
"i18next": "^23.11.5", "i18next": "^23.15.1",
"install": "^0.13.0",
"katex": "^0.16.11", "katex": "^0.16.11",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"lottie-react": "^2.4.0", "lottie-react": "^2.4.0",
"mathjax": "^3.2.2",
"mathjax-full": "^3.2.2",
"mathjs": "^13.1.1", "mathjs": "^13.1.1",
"mathml-to-latex": "^1.4.1", "mathml-to-latex": "^1.4.1",
"mathml2latex": "^1.1.3",
"mml2tex": "^0.0.2",
"npm": "^10.8.3", "npm": "^10.8.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-intersection-observer": "^9.13.1",
"react-katex": "^3.0.1", "react-katex": "^3.0.1",
"react-latex": "^2.0.0",
"react-latex-next": "^3.0.0",
"react-leaflet": "^4.2.1", "react-leaflet": "^4.2.1",
"react-mathjax": "^1.0.1",
"react-mathjax-preview": "^2.2.6",
"react-mathjax2": "^0.0.2",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.26.2",
"react-toastify": "^9.1.3", "react-toastify": "^9.1.3",
"react-window": "^1.8.10", "react-window": "^1.8.10",
"react-window-dynamic": "^1.8.0-alpha.2", "reactstrap": "^9.2.3",
"reactstrap": "^9.2.2", "sass": "^1.79.4",
"sass": "^1.77.4",
"yup": "^1.4.0", "yup": "^1.4.0",
"zustand": "^4.5.2" "zustand": "^4.5.5"
}, },
"scripts": { "scripts": {
"start": "vite --port=3000", "start": "vite --port=3000",
@ -81,26 +68,26 @@
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/node": "^20.14.0", "@types/node": "^20.16.10",
"@types/react": "^18.3.3", "@types/react": "^18.3.10",
"@types/react-beautiful-dnd": "^13.1.8", "@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-katex": "^3.0.4", "@types/react-katex": "^3.0.4",
"@types/react-latex": "^2.0.3", "@types/react-latex": "^2.0.3",
"@types/react-window": "^1.8.8", "@types/react-window": "^1.8.8",
"@vitejs/plugin-legacy": "^5.4.1", "@vitejs/plugin-legacy": "^5.4.2",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.1",
"jest": "^29.7.0", "jest": "^29.7.0",
"jsdom": "^24.1.0", "jsdom": "^24.1.3",
"prettier": "^3.3.0", "prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"ts-jest": "^29.1.4", "ts-jest": "^29.2.5",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^5.2.12", "vite": "^5.4.8",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"webpack": "^5.93.0", "webpack": "^5.95.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,16 +2,18 @@ import React from 'react';
import { BlockMath } from 'react-katex'; import { BlockMath } from 'react-katex';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
const LatexPreview = ({ latex }: { latex: string }) => { const LatexPreview = ({ latex }: { latex: string }) => {
console.log(latex); // console.log(latex);
const sanitizedLatex = latex.replace(/\\_/g, '_'); // const sanitizedLatex = latex.replace(/\\_/g, '_');
return ( return (
<div> <div>
<BlockMath> <BlockMath>
{sanitizedLatex} {latex}
</BlockMath> </BlockMath>

View File

@ -22,13 +22,15 @@ const SelectTagV2: React.FC = () => {
const NewShapeTags = CurrentTags?.map((item:any)=> {return item?.name ?? item }) const NewShapeTags = CurrentTags?.map((item:any)=> {return item?.name ?? item })
const handleChange = (_value: any[],option:any) => { const handleChange = (_value: any[],option:any) => {
console.log(option,"option"); // console.log(option,"option");
const NewShapeOption = option?.map((item:any)=> {return ({name:item?.name,id:item?.id})}) console.log(_value);
console.log(NewShapeOption);
formik.setFieldValue("tags", NewShapeOption); // const NewShapeOption = option?.map((item:any)=> {return ({name:item?.name,id:item?.id})})
setSearchValue(""); // console.log(NewShapeOption);
setFieldValue("");
// formik.setFieldValue("tags", NewShapeOption);
// setSearchValue("");
// setFieldValue("");
}; };
const handleSearch = useDebounce((value: string) => { const handleSearch = useDebounce((value: string) => {

View File

@ -29,8 +29,10 @@ const AddLaTexModal = ({name,setLatex,Latex,setIsModalOpen,isModalOpen,setCurren
setLatex("") setLatex("")
setIsModalOpen(false); setIsModalOpen(false);
}else{ }else{
setLatex("") setFieldValue(name, oldValue + " $$ " +Latex +" $$ ");
toast.error(t("validation.that_is_not_a_valid_mml")) setCurrentValue(oldValue + " $$ " +Latex +" $$ ")
setLatex("")
setIsModalOpen(false);
} }
}; };

View File

@ -13,10 +13,7 @@ import { useModalState } from "./Modal";
import { useFilterState } from "./FilterState"; import { useFilterState } from "./FilterState";
import { ModalEnum } from "../../../enums/Model"; import { ModalEnum } from "../../../enums/Model";
import { QueryStatusEnum } from "../../../enums/QueryStatus"; import { QueryStatusEnum } from "../../../enums/QueryStatus";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import SpinContainer from "../../Layout/SpinContainer"; import SpinContainer from "../../Layout/SpinContainer";
import { TbFlagCancel } from "react-icons/tb";
import { XFilled } from "@ant-design/icons";
import { FaXmark } from "react-icons/fa6"; import { FaXmark } from "react-icons/fa6";
type OmitFormikProps = "children" | "initialValues" | "onSubmit"; type OmitFormikProps = "children" | "initialValues" | "onSubmit";
@ -158,8 +155,8 @@ const useFilter = () => {
return ( return (
<div className="filter-submit-buttons buttons"> <div className="filter-submit-buttons buttons">
<Button <Button
type="primary"
className="back_button filter_modal_cancel_button" className="back_button filter_modal_cancel_button"
type="default"
htmlType="reset" htmlType="reset"
> >
{t("practical.reset")} {t("practical.reset")}

View File

@ -12,12 +12,12 @@ const Date = ({
label, label,
picker = "date", picker = "date",
isDisabled, isDisabled,
props,
onChange, onChange,
placeholder, placeholder,
className, className,
no_label, no_label,
label_icon, label_icon,
...props
}: any) => { }: any) => {
const { errorMsg, isError, t, formik } = useFormField(name, props); const { errorMsg, isError, t, formik } = useFormField(name, props);
@ -25,6 +25,7 @@ const Date = ({
const onCalendarChange = (value: any) => { const onCalendarChange = (value: any) => {
formik.setFieldValue(name, value); formik.setFieldValue(name, value);
}; };
console.log(props);
console.log(FormikValue); console.log(FormikValue);
const Formatter = [DateEnum?.FORMATE]; const Formatter = [DateEnum?.FORMATE];
@ -49,8 +50,10 @@ const Date = ({
size="large" size="large"
onChange={onChange || onCalendarChange} onChange={onChange || onCalendarChange}
disabled={isDisabled} disabled={isDisabled}
format={Formatter} format={props?.Format ?? Formatter}
id={name} id={name}
needConfirm={false}
{...props}
/> />
{/* <DatePicker onChange={onChange} /> */} {/* <DatePicker onChange={onChange} /> */}
</ValidationFieldContainer> </ValidationFieldContainer>

View File

@ -40,8 +40,9 @@ export type SearchFieldProps = BaseFieldProps &
type DateFieldProps = BaseFieldProps & { type DateFieldProps = BaseFieldProps & {
type: "DataRange" | "Date" | "Time"; type: "DataRange" | "Date" | "Time";
Format?: "YYYY/MM/DD" | "MM/DD" | "YYYY/MM" | "YYYY-MM-DD HH:mm:ss.SSS"; Format?: "YYYY/MM/DD" | "MM/DD" | "YYYY/MM" | "YYYY-MM-DD HH:mm:ss.SSS" | "YYYY-MM-DD HH:mm:ss";
picker?: "data" | "week" | "month" | "quarter" | "year"; picker?: "data" | "week" | "month" | "quarter" | "year";
showTime?:boolean
}; };
type FileFieldProps = BaseFieldProps & { type FileFieldProps = BaseFieldProps & {

View File

@ -33,7 +33,7 @@ const TableHeader = () => {
ModelAbility={ModalEnum?.CITY_ADD} ModelAbility={ModalEnum?.CITY_ADD}
canAdd={canAddCity} canAdd={canAddCity}
/> />
<FilterLayout sub_children={<FilterForm />} filterTitle="table.City" /> <FilterLayout sub_children={<FilterForm />} haveFilter={false} filterTitle="table.City" />
<Table /> <Table />
<AddModalForm /> <AddModalForm />
<EditModalForm /> <EditModalForm />

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 { QueryStatusEnum } from "../../../../enums/QueryStatus";
import ModelForm from "./ModelForm";
import { useAddCoupon } from "../../../../api/Coupon";
import dayjs from "dayjs";
const AddModel: React.FC = () => {
const { mutate, status } = useAddCoupon();
const handleSubmit = (values: any) => {
console.log(values?.due_to,"values?.due_to");
const due_to = values?.due_to.format("YYYY-MM-DD HH:mm:ss")
console.log(due_to);
mutate({
...values,
due_to
});
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.COUPON_ADD}
modelTitle="Coupon"
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 { useUpdateCoupon } from "../../../../api/Coupon";
import { handelImageState } from "../../../../utils/DataToSendImageState";
const EditModel: React.FC = () => {
const { mutate, status } = useUpdateCoupon();
const { objectToEdit } = useObjectToEdit((state) => state);
const handleSubmit = (values: any) => {
const due_to = typeof values?.due_to === "string" ? values?.due_to : values?.due_to.format("YYYY-MM-DD HH:mm:ss")
const Data_to_send = { ...values , due_to };
mutate(Data_to_send);
};
return (
<>
<LayoutModel
status={status as QueryStatusEnum}
ModelEnum={ModalEnum.COUPON_EDIT}
modelTitle="Coupon"
handleSubmit={handleSubmit}
getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema}
isAddModal={false}
>
<ModelForm Hide={true} />
</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,62 @@
import { Col, Row } from "reactstrap";
import ValidationField from "../../../../Components/ValidationField/ValidationField";
import { useValidationValidationParamState } from "../../../../Components/ValidationField/state/ValidationValidationParamState";
import { useGetAllGrade } from "../../../../api/grade";
const Form = ({Hide = false}:{Hide?:boolean }) => {
const { ValidationParamState } = useValidationValidationParamState();
const {
GradeName, GradeCurrentPage,
} = ValidationParamState;
const { data: Grade, isLoading: isLoadingGrade } = useGetAllGrade({
name: GradeName,
page: GradeCurrentPage
});
const GradeOption = Grade?.data ?? []
const canChangeGradePage = !!Grade?.links?.next;
const GradePage = Grade?.meta?.currentPage;
return (
<Row className="w-100">
<Col>
<ValidationField name="name" placeholder="name" label="name" />
<ValidationField name="amount" type="number" placeholder="amount" label="amount" />
</Col>
<Col>
<ValidationField
name="due_to" type="Date"
Format="YYYY-MM-DD HH:mm:ss"
placeholder="due_to" label="due_to"
showTime
/>
<ValidationField name="code" placeholder="code" label="code" />
{/*
grade_id
*/}
{!Hide &&
<ValidationField
searchBy="GradeName"
name="grade_id"
label="grade"
placeholder="grade"
type="Search"
option={GradeOption}
isLoading={isLoadingGrade}
canChangePage={canChangeGradePage}
PageName={"GradeCurrentPage"}
page={GradePage}
/>
}
</Col>
</Row>
);
};
export default Form;

View File

@ -0,0 +1,27 @@
import * as Yup from "yup";
import { Coupon, CouponInitialValues } from "../../../../types/Coupon";
import dayjs from "dayjs";
export const getInitialValues = (
objectToEdit: Partial<Coupon>,
): CouponInitialValues => {
return {
id: objectToEdit?.id,
name: objectToEdit?.name ?? "",
amount: objectToEdit?.amount ?? "",
code: objectToEdit?.code ?? "",
due_to: objectToEdit?.due_to ? dayjs(objectToEdit?.due_to,"YYYY-MM-DD HH:mm:ss") : "",
grade_id: objectToEdit?.grade_id ?? "",
};
};
export const getValidationSchema = () => {
// validate input
return Yup.object().shape({
name: Yup.string().required("validation.required"),
due_to: Yup.string().required("validation.required"),
code: Yup.string().required("validation.required").min(6,"validation.must_be_at_least_6_characters_long").max(6,"validation.must_be_at_least_6_characters_long"),
amount: Yup.number().required("validation.required").typeError("validation.Must_be_a_number"),
});
};

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 { useDeleteCoupon } from "../../../api/Coupon";
import PageHeader from "../../../Layout/Dashboard/PageHeader";
import FilterLayout from "../../../Layout/Dashboard/FilterLayout";
import FilterForm from "./Model/FilterForm";
import { canAddCoupon } 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 = useDeleteCoupon();
useSetPageTitle([
{name:`${t(`page_header.home`)}`, path:"/"},
{name:`${t(`page_header.Coupon`)}`, path:"Coupon"}
]);
return (
<div className="TableWithHeader">
<Suspense fallback={<Spin />}>
<PageHeader
pageTitle="Coupon"
ModelAbility={ModalEnum?.COUPON_ADD}
canAdd={canAddCoupon}
/>
<FilterLayout sub_children={<FilterForm />} haveFilter={false} filterTitle="table.Coupon" />
<Table />
<AddModalForm />
<EditModalForm />
<DeleteModalForm
deleteMutation={deleteMutation}
ModelEnum={ModalEnum?.COUPON_DELETE}
/>
</Suspense>
</div>
);
};
export default TableHeader;

View File

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

View File

@ -0,0 +1,92 @@
import { TableColumnsType } from "antd";
import useModalHandler from "../../../utils/useModalHandler";
import { ModalEnum } from "../../../enums/Model";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import {
canDeleteCoupon,
canEditCoupon,
canShowCoupon,
} from "../../../utils/hasAbilityFn";
import ActionButtons from "../../../Components/Table/ActionButtons";
import ColumnsImage from "../../../Components/Columns/ColumnsImage";
import { Coupon } from "../../../types/Coupon";
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: Coupon) => {
setFilter({})
navigate(`${record?.id}`);
};
const handelDelete = (data: Coupon) => {
setObjectToEdit(data);
handel_open_model(ModalEnum?.COUPON_DELETE);
};
const handleEdit = (record: Coupon) => {
setObjectToEdit(record);
handel_open_model(ModalEnum?.COUPON_EDIT);
};
const [t] = useTranslation();
const columns: TableColumnsType<Coupon> = [
{
title: t("columns.id"),
dataIndex: "id",
key: "id",
align: "center",
render: (_text, record) => record?.id,
},
{
title: `${t("columns.name")}`,
dataIndex: "name",
key: "name",
align: "center",
render: (_text, record) => record?.name,
ellipsis:true
},
{
title: t("columns.image"),
dataIndex: "image",
key: "image",
align: "center",
render: (_text: any, record: Coupon) => {
let str = record?.amount;
return <ColumnsImage src={str}/> ;
},
},
{
title: t("columns.procedure"),
key: "actions",
align: "center",
width: "25vw",
render: (_text, record, index) => {
return (
<ActionButtons
canDelete={canDeleteCoupon}
canEdit={canEditCoupon}
canShow={canShowCoupon}
index={index}
onDelete={() => handelDelete(record)}
onEdit={() => handleEdit(record)}
onShow={() => handelShow(record)}
/>
);
},
},
];
return columns;
};

View File

@ -7,6 +7,7 @@ import { useValidationValidationParamState } from "../../../../Components/Valida
import { useGetAllUnit } from "../../../../api/unit"; import { useGetAllUnit } from "../../../../api/unit";
import { useGetAllSubject } from "../../../../api/subject"; import { useGetAllSubject } from "../../../../api/subject";
import { useGetAllLesson } from "../../../../api/lesson"; import { useGetAllLesson } from "../../../../api/lesson";
import { useGetAllTag } from "../../../../api/tags";
const FilterForm = () => { const FilterForm = () => {
@ -15,7 +16,8 @@ const FilterForm = () => {
GradeName, GradeCurrentPage, GradeName, GradeCurrentPage,
SubjectName, SubjectCurrentPage, SubjectName, SubjectCurrentPage,
UnitName, UnitCurrentPage, UnitName, UnitCurrentPage,
LessonName, LessonCurrentPage LessonName, LessonCurrentPage,
TagName , TagCurrentPage
} = ValidationParamState; } = ValidationParamState;
@ -58,6 +60,17 @@ const FilterForm = () => {
const canChangeLessonPage = !!Lesson?.links?.next; const canChangeLessonPage = !!Lesson?.links?.next;
const LessonPage = Lesson?.meta?.currentPage; const LessonPage = Lesson?.meta?.currentPage;
/// TagsIds
const { data: Tag, isLoading: isLoadingTag } = useGetAllTag({
name: TagName,
page: TagCurrentPage
});
const TagOption = Tag?.data ?? []
const canChangeTagPage = !!Tag?.links?.next;
const TagPage = Tag?.meta?.currentPage;
return ( return (
<div> <div>
<Row> <Row>
@ -97,7 +110,23 @@ const FilterForm = () => {
/> />
{/*
TagsIds
*/}
<ValidationField
searchBy="TagName"
name="tagsIds"
label="tag"
type="Search"
option={TagOption}
isMulti
isLoading={isLoadingTag}
canChangePage={canChangeTagPage}
PageName={"TagCurrentPage"}
page={TagPage}
/>
</Col> </Col>
@ -137,6 +166,9 @@ const FilterForm = () => {
page={LessonPage} page={LessonPage}
/> />
</Col> </Col>
</Row> </Row>
</div> </div>

View File

@ -29,7 +29,7 @@ const TableHeader = () => {
<PageHeader <PageHeader
pageTitle="QuestionBank" pageTitle="QuestionBank"
ModelAbility={ModalEnum?.QUESTION_BANK_ADD} ModelAbility={ModalEnum?.QUESTION_BANK_ADD}
canAdd={canAddQuestionBank} canAdd={false}
/> />
<FilterLayout width="700px" search_by="content" sub_children={<FilterForm />} filterTitle="table.QuestionBank" /> <FilterLayout width="700px" search_by="content" sub_children={<FilterForm />} filterTitle="table.QuestionBank" />
<Table /> <Table />

View File

@ -76,9 +76,8 @@ export const useColumns = () => {
key: "hint", key: "hint",
align: "center", align: "center",
render: (text, record) => { render: (text, record) => {
return ( return (
<>{record?.hint ?? "karim"}</> <>{record?.hint ?? "_"}</>
); );
}, },
ellipsis: true, ellipsis: true,

View File

@ -54,7 +54,7 @@ const PersonalDetailsForm = ({isEdit}:{isEdit?:boolean}) => {
placeholder={"_"} placeholder={"_"}
label={"ID Number"} label={"ID Number"}
/> />
<ValidationField {/* <ValidationField
name={"location_lat"} name={"location_lat"}
placeholder={"_"} placeholder={"_"}
label={"lat"} label={"lat"}
@ -63,7 +63,7 @@ const PersonalDetailsForm = ({isEdit}:{isEdit?:boolean}) => {
name={"location_lng"} name={"location_lng"}
placeholder={"_"} placeholder={"_"}
label={"lng"} label={"lng"}
/> /> */}
{/* <ValidationField {/* <ValidationField
name={"id_number"} name={"id_number"}
placeholder={"_"} placeholder={"_"}

View File

@ -37,6 +37,7 @@ const TableHeader = () => {
<FilterLayout <FilterLayout
sub_children={<FilterForm />} sub_children={<FilterForm />}
filterTitle="sidebar.tags" filterTitle="sidebar.tags"
haveFilter={false}
/> />
<Table /> <Table />
<DeleteModalForm <DeleteModalForm

View File

@ -99,7 +99,7 @@ const DrapableTable: React.FC = () => {
const sort_by = Filter?.sort_by ; const sort_by = Filter?.sort_by ;
const response = useGetAllUnit({ const response = useGetAllUnit({
subject_id: subject_id, subject_id: subject_id,
pagination: false, isPaginated: false,
name, name,
sort_by, sort_by,
...filterState, ...filterState,

View File

@ -101,7 +101,7 @@ const DrapableTable: React.FC = () => {
const response = useGetAllLesson({ const response = useGetAllLesson({
unit_id: unit_id, unit_id: unit_id,
pagination: false, isPaginated: false,
name, name,
sort_by, sort_by,
...filterState, ...filterState,

View File

@ -278,9 +278,13 @@ const EditPage: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
console.log(location.pathname);
console.log(deletePathSegments(location.pathname, 2));
if (isSuccess) { if (isSuccess) {
toast.success(t("validation.the_possess_done_successful")); toast.success(t("validation.the_possess_done_successful"));
navigate(deletePathSegments(location.pathname, 2)); navigate(-1);
} }
}, [isSuccess]); }, [isSuccess]);

View File

@ -1,14 +1,9 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Choice } from "../../../../../../types/Item";
import { Field, useFormikContext } from "formik"; import { Field, useFormikContext } from "formik";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getCharFromNumber } from "../../../../../../utils/getCharFromNumber";
import TextField from "./TextField";
import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../../zustand/ObjectToEditState";
import ImageBoxField from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxField";
import { GoTrash } from "react-icons/go"; import { GoTrash } from "react-icons/go";
import { Popconfirm } from "antd"; import { Popconfirm } from "antd";
import LaTeXInput from "../../../../../../Components/LatextInput/LaTeXInput";
import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo"; import LaTeXInputMemo from "../../../../../../Components/LatextInput/LaTeXInputMemo";
import ImageBoxFieldMemo from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo"; import ImageBoxFieldMemo from "../../../../../../Components/CustomFields/ImageBoxField/ImageBoxFieldMemo";

View File

@ -6,6 +6,7 @@ import { FaCirclePlus } from "react-icons/fa6";
import ValidationField from "../../../../../../Components/ValidationField/ValidationField"; import ValidationField from "../../../../../../Components/ValidationField/ValidationField";
import MaltySelectTag from "../Tags/MaltySelectTag"; import MaltySelectTag from "../Tags/MaltySelectTag";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import SelectTagV2 from "../../../../../../Components/CustomFields/SelectTagV2";
export const Question: React.FC<any> = React.memo(({ index, data }) => { export const Question: React.FC<any> = React.memo(({ index, data }) => {
const { values, setFieldValue, ShowHint, t } = data; const { values, setFieldValue, ShowHint, t } = data;
@ -61,6 +62,7 @@ export const Question: React.FC<any> = React.memo(({ index, data }) => {
/> />
)} )}
<MaltySelectTag parent_index={index} /> <MaltySelectTag parent_index={index} />
</div> </div>
</div> </div>
); );

View File

@ -95,6 +95,7 @@ const Form = () => {
/> />
} }
<SelectTag /> <SelectTag />
</div> </div>
</Row> </Row>
); );

View File

@ -1,23 +1,8 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import MathMLPreview from "./MathMLPreview";
import { Field, Form, Formik } from "formik";
import Test from "./Test";
import MarkdownPreview from '@uiw/react-markdown-preview';
import { BlockMath } from "react-katex";
const Dummy = () => { const Dummy = () => {
const [t] = useTranslation();
const [markdown, setMarkdown] = useState<string>(' \nV_{sphere} = \\frac{4}{3}\\pi r^3\n');
return ( return (
<div className="DummyHomePage"> <div className="DummyHomePage">
karim is op
<MarkdownPreview style={{background:"transparent" ,color:"black"}} source={"This is an <u>underlined</u> text."} />
<BlockMath>
{markdown}
</BlockMath>
</div> </div>
); );
}; };

View File

@ -1,31 +0,0 @@
import React, { useState, useEffect } from 'react';
import { MathJax, MathJaxContext } from 'better-react-mathjax';
const MathMLPreview = () => {
const [mathML, setMathML] = useState('<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi><mo>=</mo><mn>5</mn></math>');
useEffect(() => {
// Any logic to handle input updates and preview refresh can be added here
}, [mathML]);
return (
<div>
<textarea
value={mathML}
onChange={(e) => setMathML(e.target.value)}
rows={5}
style={{ width: '100%' }}
/>
<MathJaxContext>
<div>
<h3>Preview:</h3>
<MathJax dynamic>
<div dangerouslySetInnerHTML={{ __html: mathML }} />
</MathJax>
</div>
</MathJaxContext>
</div>
);
};
export default MathMLPreview;

View File

@ -1,5 +1,5 @@
import { TCrudRoute, TMenuItem } from "./types/App"; import { TCrudRoute, TMenuItem } from "./types/App";
import { FaCashRegister, FaCity, FaHome, FaMoneyBill, FaPaperclip, FaSellcast, FaTag, FaUser, FaUserShield } from "react-icons/fa"; import { FaCashRegister, FaCity, FaHome, FaMonero, FaMoneyBill, FaPaperclip, FaSellcast, FaTag, FaUser, FaUserShield } from "react-icons/fa";
import { GoDotFill } from "react-icons/go"; import { GoDotFill } from "react-icons/go";
import { MdOutlineSell } from "react-icons/md"; import { MdOutlineSell } from "react-icons/md";
import { CgProfile } from "react-icons/cg"; import { CgProfile } from "react-icons/cg";
@ -48,6 +48,8 @@ const Setting = React.lazy(() => import("./Pages/Admin/Setting/Page"));
const Permissions = React.lazy(() => import("./Pages/Admin/Roles/Permissions/Page")); const Permissions = React.lazy(() => import("./Pages/Admin/Roles/Permissions/Page"));
const Roles = React.lazy(() => import("./Pages/Admin/Roles/Page")); const Roles = React.lazy(() => import("./Pages/Admin/Roles/Page"));
const Coupon = React.lazy(() => import("./Pages/Admin/Coupon/Page"));
const Report = React.lazy(() => import("./Pages/Admin/Report/Page")); const Report = React.lazy(() => import("./Pages/Admin/Report/Page"));
const Param = React.lazy(() => import("./Pages/Admin/Param/Page")); const Param = React.lazy(() => import("./Pages/Admin/Param/Page"));
@ -66,6 +68,7 @@ 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 { FaTags } from "react-icons/fa6";
import { CiSquareQuestion } from "react-icons/ci";
export const menuItems: TMenuItem[] = [ export const menuItems: TMenuItem[] = [
{ {
@ -175,16 +178,16 @@ export const menuItems: TMenuItem[] = [
// prevPath: 0, // prevPath: 0,
// }, // },
// { {
// header: "page_header.questionBank", header: "page_header.questionBank",
// element: <QuestionBank />, element: <QuestionBank />,
// icon: <CiSquareQuestion />, icon: <CiSquareQuestion />,
// text: "sidebar.questionBank", text: "sidebar.questionBank",
// path: `/${ABILITIES_ENUM?.QUESTION}`, path: `/${ABILITIES_ENUM?.QUESTION}`,
// abilities: ABILITIES_ENUM?.QUESTION, abilities: ABILITIES_ENUM?.QUESTION,
// abilities_value: ABILITIES_VALUES_ENUM.INDEX, abilities_value: ABILITIES_VALUES_ENUM.INDEX,
// prevPath: 0, prevPath: 0,
// }, },
{ {
header: "page_header.roles", header: "page_header.roles",
element: <Roles />, element: <Roles />,
@ -196,6 +199,17 @@ export const menuItems: TMenuItem[] = [
prevPath: 0, prevPath: 0,
}, },
{ {
header: "page_header.coupon",
element: <Coupon />,
icon: <FaMoneyBill />,
text: "sidebar.coupon",
path: `/${ABILITIES_ENUM?.COUPON}`,
abilities: ABILITIES_ENUM?.COUPON,
abilities_value: ABILITIES_VALUES_ENUM.INDEX,
prevPath: 0,
},
{
header: "page_header.financial_collection", header: "page_header.financial_collection",
element: <FinancialCollection />, element: <FinancialCollection />,
icon: <FaMoneyBill />, icon: <FaMoneyBill />,

View File

@ -2,7 +2,9 @@
padding: 20px 20px; padding: 20px 20px;
border-radius: 10px 10px 0 0; border-radius: 10px 10px 0 0;
box-shadow: 0px 0px 32px 2px #080F3414; box-shadow: 0px 0px 32px 2px #080F3414;
// max-width: 85vw;
overflow-x: hidden;
min-height: fit-content;
>div { >div {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -31,11 +31,14 @@
.PageTitle { .PageTitle {
display: flex; display: flex;
gap: 10px; gap: 10px;
flex-wrap: wrap;
margin-block: 10px; margin-block: 10px;
.PageTitleItems { .PageTitleItems {
cursor: pointer; cursor: pointer;
color: var(--value); color: var(--value);
max-width: 65vw;
overflow: hidden;
} }
} }

View File

@ -8,7 +8,17 @@
position: relative; position: relative;
margin-bottom: 10px; margin-bottom: 10px;
// &::after{
// position: absolute;
// content: "";
// top: 40%;
// left: 59%;
// transform: translate(-50%,-50%);
// width: 3px;
// height: 78%;
// background-color: red;
// z-index: 99999;
// }
} }
.showPreviewInput{ .showPreviewInput{
background-color: var(--bg); background-color: var(--bg);
@ -19,7 +29,7 @@
height: calc(100% - 44px); height: calc(100% - 44px);
word-wrap: break-word; word-wrap: break-word;
display: flex; display: flex;
gap: 20px; gap: 5px;
align-items: baseline; align-items: baseline;
align-content: flex-start; align-content: flex-start;
flex-wrap: wrap; flex-wrap: wrap;
@ -73,13 +83,13 @@
} }
} }
.katex .msupsub{ // .katex .msupsub{
text-align: end !important; // text-align: end !important;
} // }
.mtight{ // .mtight{
font-size: 10px !important; // font-size: 10px !important;
} // }
.katex .delimcenter, .katex .op-symbol{ // .katex .delimcenter, .katex .op-symbol{
display: none; // display: none;
} // }

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

@ -0,0 +1,20 @@
import useAddMutation from "./helper/useAddMutation";
import useDeleteMutation from "./helper/useDeleteMutation";
import useGetQuery from "./helper/useGetQuery";
import useUpdateMutation from "./helper/useUpdateMutation";
const API = {
GET: "/coupon",
ADD: "/coupon",
DELETE: "/coupon",
UPDATE: "/coupon",
};
const KEY = "Coupon";
export const useGetAllCoupon = (params?: any, options?: any) =>
useGetQuery(KEY, API.GET, params, options);
export const useAddCoupon = () => useAddMutation(KEY, API.ADD);
export const useUpdateCoupon = (params?: any) => useUpdateMutation(KEY, API.GET);
export const useDeleteCoupon = (params?: any) =>
useDeleteMutation(KEY, API.DELETE);

View File

@ -74,6 +74,12 @@ export enum ModalEnum {
HOME_WORK_ADD = "homework.add", HOME_WORK_ADD = "homework.add",
HOME_WORK_DELETE = "homework.delete", HOME_WORK_DELETE = "homework.delete",
//// Coupon
COUPON_EDIT = "Coupon.edit",
COUPON_ADD = "Coupon.add",
COUPON_DELETE = "Coupon.delete",
///// note ///// note
NOTES_EDIT = "notes.edit", NOTES_EDIT = "notes.edit",

View File

@ -59,6 +59,7 @@ export enum ABILITIES_ENUM {
SETTING = "setting", SETTING = "setting",
Email = "email", Email = "email",
Phone = "phone", Phone = "phone",
COUPON = "coupon",
CITY = "city", CITY = "city",
AREA = "area", AREA = "area",
Financial_Collection = "financial_collection" Financial_Collection = "financial_collection"

View File

@ -12,6 +12,8 @@
"Email_is_required": "البريد الإلكتروني مطلوب", "Email_is_required": "البريد الإلكتروني مطلوب",
"Password_is_required": "كلمة المرور مطلوبة", "Password_is_required": "كلمة المرور مطلوبة",
"Password_must_be_at_least_8_characters_long": "يجب أن تكون كلمة المرور مكونة من 8 أحرف على الأقل", "Password_must_be_at_least_8_characters_long": "يجب أن تكون كلمة المرور مكونة من 8 أحرف على الأقل",
"must_be_at_least_6_characters_long": "يجب أن تكون مكونة من 6 أحرف على الأقل",
"Nationality_is_required": "الجنسية مطلوبة", "Nationality_is_required": "الجنسية مطلوبة",
"Address_is_required": "العنوان مطلوب", "Address_is_required": "العنوان مطلوب",
"Place_of_birth_is_required": "مكان الميلاد مطلوب", "Place_of_birth_is_required": "مكان الميلاد مطلوب",
@ -409,6 +411,7 @@
"City":"مدينة", "City":"مدينة",
"add_sales":"إضافة عملية بيع", "add_sales":"إضافة عملية بيع",
"are_you_sure_about_sale":"هل أنت متأكد من عملية البيع ؟", "are_you_sure_about_sale":"هل أنت متأكد من عملية البيع ؟",
"Coupon":"قسيمة",
"financial_collection":"التحصيلات", "financial_collection":"التحصيلات",
"show_collection":"حصيلة" "show_collection":"حصيلة"
}, },
@ -545,7 +548,10 @@
"lat":"الطول", "lat":"الطول",
"lng":"العرض", "lng":"العرض",
"choose":"حدد", "choose":"حدد",
"amount":"مبلغ", "tag":"كلمات مفتاحية",
"code":"رمز",
"amount":"كمية",
"due_to":"صالح الى",
"reseller":"البائعين", "reseller":"البائعين",
"activation_date":"تاريخ التنشيط", "activation_date":"تاريخ التنشيط",
"expiration_date":"تاريخ الالغاء" "expiration_date":"تاريخ الالغاء"
@ -886,6 +892,7 @@
"collections": "التحصيلات", "collections": "التحصيلات",
"Area":"المنطقة", "Area":"المنطقة",
"city":"مدينة", "city":"مدينة",
"coupon":"قسيمة",
"financial_collection":"التحصيلات" "financial_collection":"التحصيلات"
}, },
"message": { "message": {
@ -936,6 +943,7 @@
"Area":"المنطقة", "Area":"المنطقة",
"setting":"الإعدادات", "setting":"الإعدادات",
"edit_reseller":"تعديل البائع", "edit_reseller":"تعديل البائع",
"Coupon":"قسيمة",
"financial_collection":"التحصيلات", "financial_collection":"التحصيلات",
"show_collection":"حصيلة" "show_collection":"حصيلة"
}, },
@ -987,6 +995,7 @@
"setting":"الإعدادات", "setting":"الإعدادات",
"City":"مدينة", "City":"مدينة",
"Area":"المنطقة", "Area":"المنطقة",
"Coupon":"قسيمة",
"financial_collection":"التحصيلات", "financial_collection":"التحصيلات",
"show_collection":"حصيلة" "show_collection":"حصيلة"
}, },
@ -1005,6 +1014,7 @@
"security_setting":"إعدادات الأمان", "security_setting":"إعدادات الأمان",
"Area":"المنطقة", "Area":"المنطقة",
"City":"مدينة", "City":"مدينة",
"Coupon":"قسيمة",
"notification":"الاشعارات", "notification":"الاشعارات",
"upload_your_photo_and_personal_data_here":"قم بتحميل صورتك وبياناتك الشخصية هنا", "upload_your_photo_and_personal_data_here":"قم بتحميل صورتك وبياناتك الشخصية هنا",
"get_notified_of_whats_happening_now_you_can_turn_it_off_at_any_time":"احصل على إشعار بما يحدث الآن ، يمكنك إيقاف تشغيله في أي وقت", "get_notified_of_whats_happening_now_you_can_turn_it_off_at_any_time":"احصل على إشعار بما يحدث الآن ، يمكنك إيقاف تشغيله في أي وقت",

21
src/types/Coupon.ts Normal file
View File

@ -0,0 +1,21 @@
import { Nullable } from "./App";
// Define the Teacher interface
export interface InitialValues {
id: number; // Unique identifier for the user
name: string; // Name of the user
amount: string; // URL of the user's amount
due_to: any; // URL of the user's amount
code: string; // URL of the user's amount
grade_id: string; // URL of the user's amount
}
export type Coupon = {
id: number; // Unique identifier for the user
name: string; // Name of the user
amount: string; // URL of the user's amount
due_to: string; // URL of the user's amount
code: string; // URL of the user's amount
grade_id: string; // URL of the user's amount
};
export type CouponInitialValues = Partial<Nullable<InitialValues>>;

View File

@ -24,6 +24,31 @@ export const canIndexEduClass = hasAbility(
ABILITIES_VALUES_ENUM.INDEX, ABILITIES_VALUES_ENUM.INDEX,
); );
///// Coupon
export const canAddCoupon = hasAbility(
ABILITIES_ENUM.EDUCATION_CLASS,
ABILITIES_VALUES_ENUM.STORE,
);
export const canEditCoupon = hasAbility(
ABILITIES_ENUM.EDUCATION_CLASS,
ABILITIES_VALUES_ENUM.UPDATE,
);
export const canDeleteCoupon = hasAbility(
ABILITIES_ENUM.EDUCATION_CLASS,
ABILITIES_VALUES_ENUM.DELETE,
);
export const canShowCoupon = hasAbility(
ABILITIES_ENUM.EDUCATION_CLASS,
ABILITIES_VALUES_ENUM.SHOW,
);
export const canIndexCoupon = hasAbility(
ABILITIES_ENUM.EDUCATION_CLASS,
ABILITIES_VALUES_ENUM.INDEX,
);
///// City ///// City
export const canAddCity = hasAbility( export const canAddCity = hasAbility(

View File

@ -7,6 +7,13 @@ export default defineConfig(() => {
return { return {
build: { build: {
outDir: "build", outDir: "build",
rollupOptions: {
output: {
entryFileNames: '[name].[hash].js',
chunkFileNames: '[name].[hash].js',
assetFileNames: '[name].[hash][extname]',
},
},
}, },
plugins: [ plugins: [