work on question

This commit is contained in:
karimaldeen 2024-08-17 11:07:53 +03:00
parent e8cc73ecc3
commit aece39ae8f
44 changed files with 4375 additions and 5682 deletions

View File

@ -4,17 +4,26 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.3.7", "@ant-design/icons": "^5.3.7",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dragula": "^1.1.3",
"antd": "^5.17.4", "antd": "^5.17.4",
"axios": "^1.7.2", "axios": "^1.7.2",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"dragula": "^3.7.3",
"formik": "^2.4.6", "formik": "^2.4.6",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"i18next": "^23.11.5", "i18next": "^23.11.5",
"path-to-regexp": "^6.2.2", "path-to-regexp": "^6.2.2",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-dragula": "^1.1.17",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-query": "^3.39.3", "react-query": "^3.39.3",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
import { HolderOutlined } from "@ant-design/icons";
import { SyntheticListenerMap } from "@dnd-kit/core/dist/hooks/utilities";
import { Button } from "antd";
import React, { useContext } from "react";
export const DragHandle: React.FC = () => {
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};

View File

@ -0,0 +1,128 @@
import React, { useContext, useMemo } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Table } from 'antd';
import type { TableColumnsType } from 'antd';
interface DataType {
key: string;
order: number;
name: string;
age: number;
address: string;
}
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};
const columns: TableColumnsType<DataType> = [
{ key: 'sort', align: 'center', width: 80, render: () => <DragHandle /> },
{ title: 'Name', dataIndex: 'name' },
{ title: 'Age', dataIndex: 'age' },
{ title: 'Address', dataIndex: 'address' },
];
const initialData: DataType[] = [
{ key: '1', order: 1, name: '1', age: 32, address: 'Long text Long' },
{ key: '4', order: 4, name: '4', age: 42, address: 'London No. 1 Lake Park' },
{ key: '3', order: 3, name: '3', age: 32, address: 'Sidney No. 1 Lake Park' },
].sort((a, b) => a.order - b.order);
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
const Row: React.FC<RowProps> = (props) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: props['data-row-key'] });
const style: React.CSSProperties = {
...props.style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const contextValue = useMemo<RowContextProps>(
() => ({ setActivatorNodeRef, listeners }),
[setActivatorNodeRef, listeners],
);
return (
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};
const DrapableTable: React.FC = () => {
const [dataSource, setDataSource] = React.useState<DataType[]>(initialData);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
// Log the old state before reordering
console.log("Old Data:", dataSource);
setDataSource((prevState) => {
const activeIndex = prevState.findIndex((record) => record.key === active?.id);
const overIndex = prevState.findIndex((record) => record.key === over?.id);
const newState = arrayMove(prevState, activeIndex, overIndex);
// Log the new state after reordering
console.log("New Data:", newState);
return newState;
});
}
};
return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext items={dataSource.map((i) => i.key)} strategy={verticalListSortingStrategy}>
<Table
rowKey="key"
components={{ body: { row: Row } }}
columns={columns}
dataSource={dataSource}
/>
</SortableContext>
</DndContext>
);
};
export default DrapableTable;

View File

@ -1,3 +1,4 @@
import React from "react"; import React from "react";
import useFormField from "../../../Hooks/useFormField"; import useFormField from "../../../Hooks/useFormField";
import { Checkbox, Form } from "antd"; import { Checkbox, Form } from "antd";
@ -13,7 +14,7 @@ const CheckboxField = ({
}: any) => { }: any) => {
const { t, formik, isError, errorMsg } = useFormField(name, props); const { t, formik, isError, errorMsg } = useFormField(name, props);
const CheckboxhandleChange = (value: any) => { const CheckboxhandleChange = (value: any) => {
formik.setFieldValue(name, value?.target?.checked); formik.setFieldValue(name, value?.target?.checked ? 1 : 0);
}; };
return ( return (
@ -26,7 +27,7 @@ const CheckboxField = ({
<Checkbox <Checkbox
onChange={onChange || CheckboxhandleChange} onChange={onChange || CheckboxhandleChange}
disabled={isDisabled} disabled={isDisabled}
checked={formik.values?.[name] ?? false} checked={formik.values?.[name] === 1}
className={className} className={className}
> >
{t(`input.${label ? label : name}`)} {t(`input.${label ? label : name}`)}

View File

@ -14,8 +14,6 @@ const File = ({
}: any) => { }: any) => {
const { formik, t, isError, errorMsg } = useFormField(name, props); const { formik, t, isError, errorMsg } = useFormField(name, props);
let imageUrl = formik?.values?.[name] ?? null; let imageUrl = formik?.values?.[name] ?? null;
console.log(imageUrl);
console.log(typeof imageUrl === "string");
const fileList: UploadFile[] = useMemo(() => { const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return []; if (!imageUrl) return [];

View File

@ -0,0 +1,128 @@
import React, { useContext, useMemo } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Table } from 'antd';
import type { TableColumnsType } from 'antd';
interface DataType {
key: string;
order: number;
name: string;
age: number;
address: string;
}
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};
const columns: TableColumnsType<DataType> = [
{ key: 'sort', align: 'center', width: 80, render: () => <DragHandle /> },
{ title: 'Name', dataIndex: 'name' },
{ title: 'Age', dataIndex: 'age' },
{ title: 'Address', dataIndex: 'address' },
];
const initialData: DataType[] = [
{ key: '1', order: 1, name: '1', age: 32, address: 'Long text Long' },
{ key: '4', order: 4, name: '4', age: 42, address: 'London No. 1 Lake Park' },
{ key: '3', order: 3, name: '3', age: 32, address: 'Sidney No. 1 Lake Park' },
].sort((a, b) => a.order - b.order);
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
const Row: React.FC<RowProps> = (props) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: props['data-row-key'] });
const style: React.CSSProperties = {
...props.style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const contextValue = useMemo<RowContextProps>(
() => ({ setActivatorNodeRef, listeners }),
[setActivatorNodeRef, listeners],
);
return (
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};
const App: React.FC = () => {
const [dataSource, setDataSource] = React.useState<DataType[]>(initialData);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
// Log the old state before reordering
console.log("Old Data:", dataSource);
setDataSource((prevState) => {
const activeIndex = prevState.findIndex((record) => record.key === active?.id);
const overIndex = prevState.findIndex((record) => record.key === over?.id);
const newState = arrayMove(prevState, activeIndex, overIndex);
// Log the new state after reordering
console.log("New Data:", newState);
return newState;
});
}
};
return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext items={dataSource.map((i) => i.key)} strategy={verticalListSortingStrategy}>
<Table
rowKey="key"
components={{ body: { row: Row } }}
columns={columns}
dataSource={dataSource}
/>
</SortableContext>
</DndContext>
);
};
export default App;

View File

@ -7,6 +7,7 @@ import { useGetAllSubject } from "../../api/subject";
import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
import { useDeleteCurriculum } from "../../api/curriculum"; import { useDeleteCurriculum } from "../../api/curriculum";
import { useGetAllGrade } from "../../api/grade";
const Table = lazy(() => import('./Table')); const Table = lazy(() => import('./Table'));
const AddModalForm = lazy(() => import('./Model/AddModel')); const AddModalForm = lazy(() => import('./Model/AddModel'));
@ -17,22 +18,28 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteCurriculum(); const deleteMutation = useDeleteCurriculum();
const { subject_id} = useParams<ParamsEnum>(); const { subject_id,grade_id} = useParams<ParamsEnum>();
const { data: Subject } = useGetAllSubject({ const { data: Subject } = useGetAllSubject({
show: subject_id, show: subject_id,
}); });
const { data: grade } = useGetAllGrade({
show: grade_id,
});
const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? ""; const SubjectName = Subject?.data?.name ?? "";
useSetPageTitle(t(`page_header.curriculum`) + "/" + t(`${SubjectName}`)); useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum"));
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<header> <header>
<h6> <h6>
{t("models.curriculum")} {SubjectName} {t("models.curriculum")}
</h6> </h6>
</header> </header>
<Table /> <Table />

View File

@ -14,8 +14,6 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteGrade(); const deleteMutation = useDeleteGrade();
useSetPageTitle(t(`page_header.grade`)); useSetPageTitle(t(`page_header.grade`));
return ( return (

View File

@ -0,0 +1,145 @@
import React, { useContext, useEffect, useMemo } from 'react';
import { HolderOutlined } from '@ant-design/icons';
import type { DragEndEvent } from '@dnd-kit/core';
import { DndContext } from '@dnd-kit/core';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
arrayMove,
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Table } from 'antd';
import type { TableColumnsType } from 'antd';
import { useParams } from 'react-router-dom';
import { ParamsEnum } from '../../enums/params';
import { useGetAllUnit } from '../../api/unit';
interface DataType {
id: string; // Unique identifier for each row
order: number;
name: string;
age: number;
address: string;
}
interface RowContextProps {
setActivatorNodeRef?: (element: HTMLElement | null) => void;
listeners?: SyntheticListenerMap;
}
const RowContext = React.createContext<RowContextProps>({});
const DragHandle: React.FC = () => {
const { setActivatorNodeRef, listeners } = useContext(RowContext);
return (
<Button
type="text"
size="small"
icon={<HolderOutlined />}
style={{ cursor: 'move' }}
ref={setActivatorNodeRef}
{...listeners}
/>
);
};
const columns: TableColumnsType<DataType> = [
{ key: 'sort', align: 'center', width: 80, render: () => <DragHandle /> },
{ title: 'Name', dataIndex: 'name' },
{ title: 'Age', dataIndex: 'age' },
{ title: 'Address', dataIndex: 'address' },
];
interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
'data-row-key': string;
}
const Row: React.FC<RowProps> = (props) => {
const {
attributes,
listeners,
setNodeRef,
setActivatorNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: props['data-row-key'] });
const style: React.CSSProperties = {
...props.style,
transform: CSS.Translate.toString(transform),
transition,
...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
};
const contextValue = useMemo<RowContextProps>(
() => ({ setActivatorNodeRef, listeners }),
[setActivatorNodeRef, listeners],
);
return (
<RowContext.Provider value={contextValue}>
<tr {...props} ref={setNodeRef} style={style} {...attributes} />
</RowContext.Provider>
);
};
const DrapableTable: React.FC = () => {
const { subject_id } = useParams<ParamsEnum>();
const response = useGetAllUnit({ subject_id: subject_id, pagination: false });
// Assuming the response contains a unique id for each item
const data = response?.data?.data?.map((item: any, index: number) => ({
id: item.id, // Ensure this is a unique identifier
order: index + 1, // Assign order based on index
name: item.name,
age: item.age,
address: item.address,
})) ?? [];
const [dataSource, setDataSource] = React.useState<DataType[]>(data);
console.log(dataSource,"dataSource");
useEffect(() => {
// Update dataSource when the fetched data changes
const sortedData = data.sort((a:any, b:any) => a.order - b.order);
setDataSource(sortedData);
}, [response?.data?.data]);
const onDragEnd = ({ active, over }: DragEndEvent) => {
if (active.id !== over?.id) {
setDataSource((prevState) => {
const activeIndex = prevState.findIndex((record) => record.id === active.id);
const overIndex = prevState.findIndex((record) => record.id === over.id);
// Move the items in the array
const newState = arrayMove(prevState, activeIndex, overIndex);
// Update the order based on the new positions
return newState.map((item, index) => ({
...item,
order: index + 1, // Update the order based on the new index
}));
});
}
};
return (
<DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
<SortableContext items={dataSource.map((i) => i.id)} strategy={verticalListSortingStrategy}>
<Table
rowKey="id"
components={{ body: { row: Row } }}
columns={columns}
dataSource={dataSource.sort((a, b) => a.order - b.order)} // Sort by order for rendering
pagination={false}
/>
</SortableContext>
</DndContext>
);
};
export default DrapableTable;

View File

@ -6,15 +6,25 @@ import { QueryStatusEnum } from "../../../enums/QueryStatus";
import ModelForm from "./ModelForm"; import ModelForm from "./ModelForm";
import { useAddUnit } from "../../../api/unit"; import { useAddUnit } from "../../../api/unit";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
const AddModel: React.FC = () => { const AddModel: React.FC = () => {
const { mutate, status } = useAddUnit(); const { mutate, status } = useAddUnit();
const { subject_id } = useParams(); const { curriculum_id } = useParams();
const {OldObjectToEdit} = useObjectToEdit()
const handleSubmit = (values: any) => { const handleSubmit = (values: any) => {
mutate({ let order = 0 ;
if(OldObjectToEdit?.order || OldObjectToEdit?.order === 0){
order = Number(OldObjectToEdit.order) + 1;
}
mutate({
...values, ...values,
subject_id: subject_id, curriculum_id: curriculum_id,
order:order
}); });
}; };
return ( return (
@ -26,7 +36,7 @@ const AddModel: React.FC = () => {
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
getInitialValues={getInitialValues({})} getInitialValues={getInitialValues({})}
getValidationSchema={getValidationSchema} getValidationSchema={getValidationSchema}
width="40vw" width="60vw"
> >
<ModelForm /> <ModelForm />
</LayoutModel> </LayoutModel>

View File

@ -5,20 +5,16 @@ import LayoutModel from "../../../Layout/Dashboard/LayoutModel";
import ModelForm from "./ModelForm"; import ModelForm from "./ModelForm";
import { QueryStatusEnum } from "../../../enums/QueryStatus"; import { QueryStatusEnum } from "../../../enums/QueryStatus";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useUpdateTag } from "../../../api/tags";
import { useUpdateUnit } from "../../../api/unit"; import { useUpdateUnit } from "../../../api/unit";
import { useParams } from "react-router-dom";
const EditModel: React.FC = () => { const EditModel: React.FC = () => {
const { mutate, status } = useUpdateUnit(); const { mutate, status } = useUpdateUnit();
const { objectToEdit } = useObjectToEdit((state) => state); const { objectToEdit } = useObjectToEdit((state) => state);
const { subject_id } = useParams();
const handleSubmit = (values: any) => { const handleSubmit = (values: any) => {
mutate({ mutate({
...values, ...values,
subject_id: subject_id,
}); });
}; };
@ -32,7 +28,7 @@ const EditModel: React.FC = () => {
getInitialValues={getInitialValues(objectToEdit)} getInitialValues={getInitialValues(objectToEdit)}
getValidationSchema={getValidationSchema} getValidationSchema={getValidationSchema}
isAddModal={false} isAddModal={false}
width="40vw" width="60vw"
> >
<ModelForm /> <ModelForm />
</LayoutModel> </LayoutModel>

View File

@ -1,12 +1,21 @@
import { Col, Row } from "reactstrap"; import { Col, Row } from "reactstrap";
import ValidationField from "../../../Components/ValidationField/ValidationField"; import ValidationField from "../../../Components/ValidationField/ValidationField";
import { TermEnum } from "../../../enums/Term";
import { enumToArray } from "../../../utils/enumToArray";
const Form = () => { const Form = () => {
const termsArray = enumToArray(TermEnum);
console.log(termsArray,"termsArray");
return ( return (
<Row className="w-100"> <Row className="w-100">
<Col> <Col>
<ValidationField name="name" placeholder="name" label="name" /> <ValidationField name="name" placeholder="name" label="name" />
</Col> </Col>
<Col>
<ValidationField name="term" type="Select" placeholder="term" label="term" option={termsArray} />
</Col>
</Row> </Row>
); );
}; };

View File

@ -1,9 +1,12 @@
import * as Yup from "yup"; import * as Yup from "yup";
import { UnitInitialValues,Unit } from "../../../types/Unit";
export const getInitialValues = (objectToEdit: any): any => { export const getInitialValues = (objectToEdit: Partial<Unit>): UnitInitialValues => {
return { return {
id: objectToEdit?.id, id: objectToEdit?.id,
name: objectToEdit?.name ?? "", name: objectToEdit?.name ?? null,
term: objectToEdit?.term ?? null,
order: objectToEdit?.order ?? null,
}; };
}; };
@ -11,6 +14,7 @@ export const getValidationSchema = () => {
// validate input // validate input
return Yup.object().shape({ return Yup.object().shape({
name: Yup.string().required("validation.required"), name: Yup.string().required("validation.required"),
term: Yup.string().required("validation.required"),
// subject_id: Yup.string().required('validation.required'), // subject_id: Yup.string().required('validation.required'),
}); });
}; };

View File

@ -7,6 +7,9 @@ import { useGetAllSubject } from "../../api/subject";
import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
import { useDeleteUnit } from "../../api/unit"; import { useDeleteUnit } from "../../api/unit";
import DrapableTable from "./DrapableTable";
import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum";
const Table = lazy(() => import('./Table')); const Table = lazy(() => import('./Table'));
const AddModalForm = lazy(() => import('./Model/AddModel')); const AddModalForm = lazy(() => import('./Model/AddModel'));
@ -17,23 +20,34 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteUnit(); const deleteMutation = useDeleteUnit();
const { subject_id} = useParams<ParamsEnum>(); const { subject_id,grade_id,curriculum_id} = useParams<ParamsEnum>();
const { data: Subject } = useGetAllSubject({ const { data: Subject } = useGetAllSubject({
show: subject_id, show: subject_id,
}); });
const SubjectName = Subject?.data?.name ?? ""; const { data: grade } = useGetAllGrade({
show: grade_id,
});
const { data: Curriculum } = useGetAllCurriculum({
show: curriculum_id,
});
const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? "";
const CurriculumName = Curriculum?.data?.name ?? "";
useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum")+"/"+CurriculumName+"/"+t("PageTitle.unit"));
useSetPageTitle(t(`page_header.subject`) + "/" + t(`${SubjectName}`));
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<header> <header>
<h6> <h6>
{t("models.units")} {SubjectName} {t("models.units")}
</h6> </h6>
</header> </header>
<Table /> <Table />
<AddModalForm /> <AddModalForm />

View File

@ -1,15 +1,26 @@
import { useColumns } from "./useTableColumns"; import { useColumns } from "./useTableColumns";
import React from "react"; import React, { useEffect } from "react";
import DataTable from "../../Layout/Dashboard/Table/DataTable"; import DataTable from "../../Layout/Dashboard/Table/DataTable";
import { useGetAllUnit } from "../../api/unit"; import { useGetAllUnit } from "../../api/unit";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../enums/params"; import { ParamsEnum } from "../../enums/params";
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
const App: React.FC = () => { const App: React.FC = () => {
const { subject_id } = useParams<ParamsEnum>(); const { subject_id } = useParams<ParamsEnum>();
const response = useGetAllUnit({ subject_id: subject_id, pagination: true }); const response = useGetAllUnit({ subject_id: subject_id, pagination: true });
const {setOldObjectToEdit} = useObjectToEdit()
console.log(response?.data?.data, "response?.data"); console.log(response?.data?.data, "response?.data");
const data = response?.data?.data;
const lastElement = response?.data?.data && response?.data?.data[data?.length - 1]
console.log(lastElement);
useEffect(() => {
if(lastElement){
setOldObjectToEdit(lastElement)
}
}, [lastElement])
return <DataTable response={response} useColumns={useColumns} />; return <DataTable response={response} useColumns={useColumns} />;
}; };

View File

@ -1,5 +1,4 @@
import { Space, TableColumnsType, Tooltip } from "antd"; import { Space, TableColumnsType, Tooltip } from "antd";
import { Unit } from "../../types/Item";
import { FaPlus } from "react-icons/fa"; import { FaPlus } from "react-icons/fa";
import useModalHandler from "../../utils/useModalHandler"; import useModalHandler from "../../utils/useModalHandler";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
@ -17,6 +16,7 @@ import {
canShowUnit, canShowUnit,
} from "../../utils/hasAbilityFn"; } from "../../utils/hasAbilityFn";
import ActionButtons from "../../Components/Table/ActionButtons"; import ActionButtons from "../../Components/Table/ActionButtons";
import { Unit } from "../../types/Unit";
export const useColumns = () => { export const useColumns = () => {
const { handel_open_model } = useModalHandler(); const { handel_open_model } = useModalHandler();
@ -24,16 +24,16 @@ export const useColumns = () => {
const { setObjectToEdit } = useObjectToEdit((state) => state); const { setObjectToEdit } = useObjectToEdit((state) => state);
const navigate = useNavigate(); const navigate = useNavigate();
const handelShow = (record: any) => { const handelShow = (record: Unit) => {
navigate(`${ABILITIES_ENUM?.UNIT}/${record?.id}`); navigate(`${ABILITIES_ENUM?.UNIT}/${record?.id}`);
}; };
const handelDelete = (data: any) => { const handelDelete = (data: Unit) => {
setObjectToEdit(data); setObjectToEdit(data);
handel_open_model(ModalEnum?.UNIT_DELETE); handel_open_model(ModalEnum?.UNIT_DELETE);
}; };
const handleEdit = (record: any) => { const handleEdit = (record: Unit) => {
setObjectToEdit(record); setObjectToEdit(record);
handel_open_model(ModalEnum?.UNIT_EDIT); handel_open_model(ModalEnum?.UNIT_EDIT);
}; };
@ -56,11 +56,11 @@ export const useColumns = () => {
}, },
{ {
title: t("columns.lesson_count"), title: t("columns.term"),
dataIndex: "lesson_count", dataIndex: "term",
key: "lesson_count", key: "term",
align: "center", align: "center",
render: (text, record) => record?.lessons_count, render: (text, record) => record?.term,
}, },
{ {

View File

@ -9,13 +9,14 @@ import { useAddLesson } from "../../../api/lesson";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../../enums/params"; import { ParamsEnum } from "../../../enums/params";
import { useModalState } from "../../../zustand/Modal"; import { useModalState } from "../../../zustand/Modal";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
const AddModel: React.FC = () => { const AddModel: React.FC = () => {
const { isOpen, setIsOpen } = useModalState((state) => state); const { isOpen, setIsOpen } = useModalState((state) => state);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { mutate, isSuccess, status } = useAddLesson(); const { mutate, isSuccess, status } = useAddLesson();
const {OldObjectToEdit} = useObjectToEdit()
const { unit_id } = useParams<ParamsEnum>(); const { unit_id } = useParams<ParamsEnum>();
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
@ -25,6 +26,13 @@ const AddModel: React.FC = () => {
}, [setIsOpen, isSuccess, queryClient]); }, [setIsOpen, isSuccess, queryClient]);
const handleSubmit = (values: any) => { const handleSubmit = (values: any) => {
let order = 0 ;
if(OldObjectToEdit?.order || OldObjectToEdit?.order === 0){
order = Number(OldObjectToEdit.order) + 1;
}
mutate({ ...values, unit_id: unit_id }); mutate({ ...values, unit_id: unit_id });
}; };

View File

@ -7,6 +7,9 @@ import { ParamsEnum } from "../../enums/params";
import { useGetAllUnit } from "../../api/unit"; import { useGetAllUnit } from "../../api/unit";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
import { useDeleteLesson } from "../../api/lesson"; import { useDeleteLesson } from "../../api/lesson";
import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum";
import { useGetAllSubject } from "../../api/subject";
const Table = lazy(() => import('./Table')); const Table = lazy(() => import('./Table'));
const AddModalForm = lazy(() => import('./Model/AddModel')); const AddModalForm = lazy(() => import('./Model/AddModel'));
@ -17,22 +20,29 @@ const TableHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteLesson(); const deleteMutation = useDeleteLesson();
const { unit_id } = useParams<ParamsEnum>(); const { unit_id,curriculum_id,grade_id ,subject_id} = useParams<ParamsEnum>();
const { data: unit } = useGetAllUnit({ show: unit_id }); const { data: unit } = useGetAllUnit({ show: unit_id });
const { data: Subject } = useGetAllSubject({
show: subject_id,
});
const { data: grade } = useGetAllGrade({
show: grade_id,
});
const { data: Curriculum } = useGetAllCurriculum({
show: curriculum_id,
});
const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? "";
const CurriculumName = Curriculum?.data?.name ?? "";
const unitName = unit?.data?.name ?? ""; const unitName = unit?.data?.name ?? "";
const SubjectName = unit?.data?.subject?.name ?? "";
console.log(unit?.data);
useSetPageTitle( useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum")+"/"+CurriculumName+"/"+t("PageTitle.unit")+"/"+unitName+"/"+t("PageTitle.lesson"));
t(`page_header.subject`) +
"/" +
`${SubjectName}` +
"/" +
t(`PageTitle.unit`) +
"/" +
`${unitName}`,
);
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -1,14 +1,27 @@
import { useColumns } from "./useTableColumns"; import { useColumns } from "./useTableColumns";
import React from "react"; import React, { useEffect } from "react";
import DataTable from "../../Layout/Dashboard/Table/DataTable"; import DataTable from "../../Layout/Dashboard/Table/DataTable";
import { useGetAllLesson } from "../../api/lesson"; import { useGetAllLesson } from "../../api/lesson";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../enums/params"; import { ParamsEnum } from "../../enums/params";
import { useObjectToEdit } from "../../zustand/ObjectToEditState";
const App: React.FC = () => { const App: React.FC = () => {
const { unit_id } = useParams<ParamsEnum>(); const { unit_id } = useParams<ParamsEnum>();
const response = useGetAllLesson({ unit_id: unit_id, pagination: true }); const response = useGetAllLesson({ unit_id: unit_id, pagination: true });
const {setOldObjectToEdit} = useObjectToEdit()
console.log(response?.data?.data, "response?.data");
const data = response?.data?.data;
const lastElement = response?.data?.data && response?.data?.data[data?.length - 1]
console.log(lastElement);
useEffect(() => {
if(lastElement){
setOldObjectToEdit(lastElement)
}
}, [lastElement])
return <DataTable response={response} useColumns={useColumns} />; return <DataTable response={response} useColumns={useColumns} />;
}; };

View File

@ -31,6 +31,9 @@ import { toast } from "react-toastify";
import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { useGetAllUnit } from "../../api/unit"; import { useGetAllUnit } from "../../api/unit";
import { useGetAllLesson } from "../../api/lesson"; import { useGetAllLesson } from "../../api/lesson";
import { useGetAllSubject } from "../../api/subject";
import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum";
const AddPage: React.FC = () => { const AddPage: React.FC = () => {
const { isSuccess: isSuccessAsync, mutateAsync } = useAddQuestionAsync(); const { isSuccess: isSuccessAsync, mutateAsync } = useAddQuestionAsync();
@ -43,41 +46,42 @@ const AddPage: React.FC = () => {
SavedQuestionData, SavedQuestionData,
} = useObjectToEdit(); } = useObjectToEdit();
const { subject_id, lesson_id, unit_id } = useParams<ParamsEnum>();
const { setIsOpen } = useModalState((state) => state); const { setIsOpen } = useModalState((state) => state);
const { data: unit } = useGetAllUnit({ show: unit_id });
const { data: lesson } = useGetAllLesson({ show: lesson_id });
const [t] = useTranslation(); const [t] = useTranslation();
const { unit_id,curriculum_id,grade_id ,subject_id,lesson_id} = useParams<ParamsEnum>();
const { data: unit } = useGetAllUnit({ show: unit_id });
const { data: Subject } = useGetAllSubject({
show: subject_id,
});
const { data: grade } = useGetAllGrade({
show: grade_id,
});
const { data: Curriculum } = useGetAllCurriculum({
show: curriculum_id,
});
const { data: Lesson } = useGetAllLesson({
show: lesson_id,
});
const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? "";
const CurriculumName = Curriculum?.data?.name ?? "";
const unitName = unit?.data?.name ?? ""; const unitName = unit?.data?.name ?? "";
const SubjectName = unit?.data?.subject?.name ?? ""; const LessonName = Lesson?.data?.name ?? "";
const lessonName = lesson?.data?.name ?? "";
useSetPageTitle(
t(`page_header.subject`) +
"/" +
`${SubjectName}` +
"/" +
t(`PageTitle.unit`) +
"/" +
`${unitName}` +
"/" +
t(`PageTitle.lesson`) +
"/" +
`${lessonName}` +
"/" +
t(`PageTitle.questions`),
);
useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum")+"/"+CurriculumName+"/"+t("PageTitle.unit")+"/"+unitName+"/"+t("PageTitle.lesson")+"/"+LessonName+"/"+t("PageTitle.question")+"/"+t("practical.add"));
const handleSubmit = ( const handleSubmit = (
values: any, values: any,
{ resetForm }: { resetForm: () => void }, { resetForm }: { resetForm: () => void },
) => { ) => {
const DataToSend = structuredClone(values); const DataToSend = structuredClone(values);
setTagsSearch(null); setTagsSearch(null);
console.log(isBseQuestion); const canAnswersBeShuffled = DataToSend?.canAnswersBeShuffled ? 1 : 0 ;
if (isBseQuestion || DataToSend?.isBase === 1) { if (isBseQuestion || DataToSend?.isBase === 1) {
const newBseQuestion = { const newBseQuestion = {
subject_id: subject_id, subject_id: subject_id,
@ -85,6 +89,9 @@ const AddPage: React.FC = () => {
image: DataToSend?.image ?? "", image: DataToSend?.image ?? "",
isBase: 1, isBase: 1,
lessons_ids: [lesson_id], lessons_ids: [lesson_id],
canAnswersBeShuffled,
hint:DataToSend?.hint
}; };
mutateAsync(newBseQuestion).then((data: any) => { mutateAsync(newBseQuestion).then((data: any) => {
@ -113,6 +120,7 @@ const AddPage: React.FC = () => {
subject_id: subject_id, subject_id: subject_id,
tags, tags,
lessons_ids: [lesson_id], lessons_ids: [lesson_id],
canAnswersBeShuffled
}); });
} }
}; };
@ -162,7 +170,7 @@ const AddPage: React.FC = () => {
> >
<main className="w-100 exercise_add_main"> <main className="w-100 exercise_add_main">
<Header/> <Header/>
<ModelForm/> <BaseForm/>
<div className="exercise_add_buttons"> <div className="exercise_add_buttons">
<div onClick={handleCancel}>{t("practical.back")}</div> <div onClick={handleCancel}>{t("practical.back")}</div>

View File

@ -28,9 +28,12 @@ import { toast } from "react-toastify";
import useSetPageTitle from "../../Hooks/useSetPageTitle"; import useSetPageTitle from "../../Hooks/useSetPageTitle";
import { useGetAllUnit } from "../../api/unit"; import { useGetAllUnit } from "../../api/unit";
import { useGetAllLesson } from "../../api/lesson"; import { useGetAllLesson } from "../../api/lesson";
import { useGetAllSubject } from "../../api/subject";
import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum";
const EditPage: React.FC = () => { const EditPage: React.FC = () => {
const { question_id, subject_id, unit_id } = useParams<ParamsEnum>(); const { unit_id,curriculum_id,grade_id ,subject_id,lesson_id,question_id } = useParams<ParamsEnum>();
const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions } = const { isBseQuestion, setIsBseQuestion, setTagsSearch, DeletedQuestions } =
useObjectToEdit(); useObjectToEdit();
@ -50,36 +53,38 @@ const EditPage: React.FC = () => {
const objectToEdit = { ...data?.data, Questions: Questions?.data }; const objectToEdit = { ...data?.data, Questions: Questions?.data };
const { lesson_id } = useParams();
useEffect(() => { useEffect(() => {
if (objectToEdit?.isBase && isBseQuestion !== true) { if (objectToEdit?.isBase && isBseQuestion !== true) {
setIsBseQuestion(true); setIsBseQuestion(true);
} }
}, [objectToEdit?.isBase]); }, [objectToEdit?.isBase]);
const { data: unit } = useGetAllUnit({ show: unit_id });
const { data: lesson } = useGetAllLesson({ show: lesson_id });
const [t] = useTranslation(); const [t] = useTranslation();
const unitName = unit?.data?.name ?? ""; const { data: unit } = useGetAllUnit({ show: unit_id });
const SubjectName = unit?.data?.subject?.name ?? "";
const lessonName = lesson?.data?.name ?? "";
useSetPageTitle(
t(`page_header.subject`) + const { data: Subject } = useGetAllSubject({
"/" + show: subject_id,
`${SubjectName}` + });
"/" + const { data: grade } = useGetAllGrade({
t(`PageTitle.unit`) + show: grade_id,
"/" + });
`${unitName}` + const { data: Curriculum } = useGetAllCurriculum({
"/" + show: curriculum_id,
t(`PageTitle.lesson`) + });
"/" + const { data: Lesson } = useGetAllLesson({
`${lessonName}` + show: lesson_id,
"/" + });
t(`PageTitle.questions`),
); const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? "";
const CurriculumName = Curriculum?.data?.name ?? "";
const unitName = unit?.data?.name ?? "";
const LessonName = Lesson?.data?.name ?? "";
useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum")+"/"+CurriculumName+"/"+t("PageTitle.unit")+"/"+unitName+"/"+t("PageTitle.lesson")+"/"+LessonName+"/"+t("PageTitle.question")+"/"+t("practical.edit"));
const handleSubmit = (values: any) => { const handleSubmit = (values: any) => {
const DataToSend = structuredClone(values); const DataToSend = structuredClone(values);
@ -91,6 +96,8 @@ const EditPage: React.FC = () => {
id: DataToSend?.id, id: DataToSend?.id,
content: DataToSend?.content, content: DataToSend?.content,
image: DataToSend?.image ?? "", image: DataToSend?.image ?? "",
}; };
if ( if (
typeof UpdateBseQuestion?.image === "string" && typeof UpdateBseQuestion?.image === "string" &&
@ -150,6 +157,8 @@ const EditPage: React.FC = () => {
tags, tags,
lessons_ids: [lesson_id], lessons_ids: [lesson_id],
parent_id: values?.id, parent_id: values?.id,
}); });
} }
}); });
@ -169,9 +178,6 @@ const EditPage: React.FC = () => {
updatedObject?.QuestionOptions?.forEach((item: any) => { updatedObject?.QuestionOptions?.forEach((item: any) => {
if (item?.id) { if (item?.id) {
// if(!item?.answer_image){
// item["answer_image"] = ""
// }
console.log(item); console.log(item);
oldQuestionOptions.push(item); oldQuestionOptions.push(item);

View File

@ -9,6 +9,7 @@ import TextField from "./TextField";
import File from "./File"; import File from "./File";
import { FaCirclePlus, FaDeleteLeft } from "react-icons/fa6"; import { FaCirclePlus, FaDeleteLeft } from "react-icons/fa6";
import { FaTrash } from "react-icons/fa"; import { FaTrash } from "react-icons/fa";
import HintField from "./HintField";
const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => { const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
@ -25,9 +26,9 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
formik.setFieldValue("QuestionOptions", updatedQuestionOptions); formik.setFieldValue("QuestionOptions", updatedQuestionOptions);
}; };
console.log(formik.values);
return ( return (
<>
<div className="ChoiceFields"> <div className="ChoiceFields">
<TextField <TextField
className="textarea_exercise" className="textarea_exercise"
@ -50,10 +51,21 @@ const ChoiceFields = ({ index, data }: { index: number; data: Choice }) => {
name={index} name={index}
type="Checkbox" type="Checkbox"
/> />
<p className="delete_question_options"> <p className="delete_question_options">
<FaTrash onClick={handleDeleteChoice} size={17} /> <FaTrash onClick={handleDeleteChoice} size={17} />
</p> </p>
</div> </div>
<div>
<HintField
placeholder={"hint"}
name={index}
label="hint"
id={`hint`}
/>
</div>
</>
); );
}; };

View File

@ -0,0 +1,70 @@
import React from 'react';
import { Choice } from '../../../../types/Item';
import ChoiceFields from './ChoiceFields';
import { useFormikContext } from 'formik';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
const Choices = () => {
const formik = useFormikContext<any>();
const handleDragEnd = (result: any) => {
// Check if the item was dropped outside the list
if (!result.destination) return;
// Create a new array from the current QuestionOptions
const items = Array.from(formik?.values?.QuestionOptions);
// Remove the item from the original position
const [reorderedItem] = items.splice(result.source.index, 1);
// Insert the item at the new position
items.splice(result.destination.index, 0, reorderedItem);
// Update the order keys based on the new indices
const updatedItems = items.map((item, index) => ({
...item ?? {},
order: index + 1, // Update order to be 1-based index
}));
// Update the formik state with the new order
console.log(updatedItems,"updatedItems");
formik.setFieldValue('QuestionOptions', updatedItems);
};
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="choices">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{formik?.values?.QuestionOptions?.map((item: Choice, index: number) => {
// Use a unique identifier for draggableId
const draggableId = item.name ? item.name.toString() : `item-${index}`;
return (
<Draggable key={draggableId} draggableId={draggableId} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ChoiceFields index={index} data={item} />
</div>
)}
</Draggable>
);
})}
{provided.placeholder} {/* Placeholder for spacing */}
</div>
)}
</Droppable>
</DragDropContext>
);
};
export default Choices;

View File

@ -17,7 +17,6 @@ const File = ({
const { formik, t, isError, errorMsg } = useFormField(newName, props); const { formik, t, isError, errorMsg } = useFormField(newName, props);
let imageUrl = formik?.values?.QuestionOptions[name]?.answer_image ?? null; let imageUrl = formik?.values?.QuestionOptions[name]?.answer_image ?? null;
// console.log(imageUrl); // console.log(imageUrl);
console.log(imageUrl);
const fileList: UploadFile[] = useMemo(() => { const fileList: UploadFile[] = useMemo(() => {
if (!imageUrl) return []; if (!imageUrl) return [];

View File

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

View File

@ -10,17 +10,14 @@ import { useTranslation } from "react-i18next";
import DynamicTags from "./Tags/DynamicTags"; import DynamicTags from "./Tags/DynamicTags";
import QuestionFIeld from "./QuestionFIeld/QuestionFIeld"; import QuestionFIeld from "./QuestionFIeld/QuestionFIeld";
import { useObjectToEdit } from "../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../zustand/ObjectToEditState";
import Choices from "./ChoiceField/Choices";
const Form = () => { const Form = () => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
const { isOpen } = useModalState((state) => state);
// const {data} = useGetAllQuestion();
const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit(); const { setSuccess, Success, setSavedQuestionData } = useObjectToEdit();
useEffect(() => { useEffect(() => {
if (Success) { if (Success) {
console.log(1);
formik.setErrors({}); formik.setErrors({});
formik.resetForm({ values: {} }); formik.resetForm({ values: {} });
setSuccess(false); setSuccess(false);
@ -32,7 +29,6 @@ const Form = () => {
}, [formik?.values]); }, [formik?.values]);
// console.log(formik?.errors); // console.log(formik?.errors);
console.log(formik?.values?.Questions, "formik?.values?.Questions");
const handleAddChoice = (parent_index: number) => { const handleAddChoice = (parent_index: number) => {
console.log(parent_index); console.log(parent_index);
@ -70,7 +66,6 @@ const Form = () => {
formik.setFieldValue("max_mark", max_mark); formik.setFieldValue("max_mark", max_mark);
}; };
const [t] = useTranslation(); const [t] = useTranslation();
console.log(formik.errors);
return ( return (
<Row className="w-100"> <Row className="w-100">
@ -96,7 +91,7 @@ const Form = () => {
<div></div> <div></div>
</div> </div>
<div className=" flex "></div> <div className=" flex "></div>
{((formik?.values as any)?.Questions || [])?.map( {((formik?.values as any)?.Questions || [])?.map(
(item: Choice, parent_index: number) => { (item: Choice, parent_index: number) => {
return ( return (
@ -109,19 +104,7 @@ const Form = () => {
/> />
</div> </div>
{( <Choices parent_index={parent_index} />
(formik?.values as any)?.Questions?.[parent_index]
?.QuestionOptions || []
).map((item: Choice, index: number) => {
return (
<ChoiceFields
key={index}
parent_index={parent_index}
index={index}
data={item}
/>
);
})}
{formik?.values?.Questions?.[parent_index]?.QuestionOptions {formik?.values?.Questions?.[parent_index]?.QuestionOptions
?.length < 5 && ( ?.length < 5 && (

View File

@ -0,0 +1,87 @@
import React from 'react'
import ChoiceFields from './ChoiceFields';
import { Choice } from '../../../../../types/Item';
import { useFormikContext } from 'formik';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
const Choices = ({parent_index}:{parent_index:number}) => {
const formik = useFormikContext<any>();
const handleDragEnd = (result: any) => {
// Check if the item was dropped outside the list
if (!result.destination) return;
// Create a new array from the current QuestionOptions
const items = Array.from(formik?.values?.QuestionOptions);
// Remove the item from the original position
const [reorderedItem] = items.splice(result.source.index, 1);
// Insert the item at the new position
items.splice(result.destination.index, 0, reorderedItem);
// Update the order keys based on the new indices
const updatedItems = items.map((item, index) => ({
...item ?? {},
order: index + 1, // Update order to be 1-based index
}));
// Update the formik state with the new order
console.log(updatedItems,"updatedItems");
formik.setFieldValue('QuestionOptions', updatedItems);
};
return (
<>
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="choices">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
{(
(formik?.values as any)?.Questions?.[parent_index]
?.QuestionOptions || []
).map((item: Choice, index: number) => {
const draggableId = item.name ? item.name.toString() : `item-${index}`;
return (
<Draggable key={draggableId} draggableId={draggableId} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ChoiceFields
key={index}
parent_index={parent_index}
index={index}
data={item}
/>
</div>
)}
</Draggable>
);
})}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</>
)
}
export default Choices

View File

@ -0,0 +1,36 @@
import React from "react";
import { Checkbox, Form } from "antd";
import { useFormik, useFormikContext } from "formik";
import { useTranslation } from "react-i18next";
const CheckboxField = ({
name,
label,
isDisabled,
onChange,
Group,
className,
props,
}: any) => {
const formik = useFormikContext<any>();
const [t] = useTranslation();
const newName = `Questions[${name}].canAnswersBeShuffled`;
console.log(formik.values?.Questions?.[name]);
const CheckboxhandleChange = (value: any, index: number) => {
formik.setFieldValue(newName, value?.target?.checked ? 1 : 0);
};
return (
<div className={Group ? "d-inline mt-5 Checkbox" : ``}>
<Checkbox
onChange={onChange || CheckboxhandleChange}
disabled={isDisabled}
checked={formik.values?.Questions?.[name]?.canAnswersBeShuffled === 1}
className={className}
>
{t(`input.${label ? label : name}`)}
</Checkbox>
</div>
);
};
export default CheckboxField;

View File

@ -9,6 +9,7 @@ import File from "./File";
import { FaTrash } from "react-icons/fa"; import { FaTrash } from "react-icons/fa";
import { useObjectToEdit } from "../../../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../../../zustand/ObjectToEditState";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import CheckboxField from "./CheckboxField";
const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => { const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
const formik = useFormikContext<any>(); const formik = useFormikContext<any>();
@ -46,6 +47,13 @@ const QuestionFIeld = ({ index, data }: { index: number; data: Choice }) => {
name={index} name={index}
type="File" type="File"
/> />
<CheckboxField
className="canAnswersBeShuffled"
label={"canAnswersBeShuffled"}
name={index}
/>
<p className="delete_question_options"> <p className="delete_question_options">
<FaTrash onClick={handleDeleteQuestion} size={17} /> <FaTrash onClick={handleDeleteQuestion} size={17} />
</p> </p>

View File

@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
import DynamicTags from "./Tags/DynamicTags"; import DynamicTags from "./Tags/DynamicTags";
import { useObjectToEdit } from "../../../zustand/ObjectToEditState"; import { useObjectToEdit } from "../../../zustand/ObjectToEditState";
import { useEffect } from "react"; import { useEffect } from "react";
import Choices from "./Field/Choices";
const Form = () => { const Form = () => {
const [t] = useTranslation() const [t] = useTranslation()
@ -42,11 +43,12 @@ const Form = () => {
<div className="exercise_form"> <div className="exercise_form">
<ValidationField className="textarea_exercise" name="content" label="details" type="TextArea" /> <ValidationField className="textarea_exercise" name="content" label="details" type="TextArea" />
<ValidationField className="file_exercise" name="image" label="attachment" type="File" /> <ValidationField className="file_exercise" name="image" label="attachment" type="File" />
<ValidationField name="canAnswersBeShuffled" label="canAnswersBeShuffled" type="Checkbox" />
</div> </div>
{ <div>
(((formik?.values as any)?.QuestionOptions as Choice[])||[]) .map((item:Choice,index:number)=>{ <ValidationField className="hint" name="hint" label="hint" type="text" style={{width:200}} />
return <ChoiceFields key={index} index={index} data={item}/> </div>
})} <Choices />
{formik?.values?.QuestionOptions?.length < 5 && ( {formik?.values?.QuestionOptions?.length < 5 && (
<p className="add_new_button" > <p className="add_new_button" >
<FaCirclePlus onClick={handleAddChoice} size={23} /> {t("header.add_new_choice")} <FaCirclePlus onClick={handleAddChoice} size={23} /> {t("header.add_new_choice")}

View File

@ -13,6 +13,7 @@ export const getInitialValues = (objectToEdit: Question): any => {
content: objectToEdit?.content ?? "", content: objectToEdit?.content ?? "",
image: objectToEdit?.image ?? "", image: objectToEdit?.image ?? "",
subject_id: objectToEdit?.subject_id ?? "", subject_id: objectToEdit?.subject_id ?? "",
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ?? 0,
isBase: 0, isBase: 0,
parent_id: objectToEdit?.parent_id ?? "", parent_id: objectToEdit?.parent_id ?? "",
QuestionOptions: objectToEdit?.QuestionOptions ?? [], QuestionOptions: objectToEdit?.QuestionOptions ?? [],
@ -38,10 +39,6 @@ export const getValidationSchema = () => {
}; };
export const getInitialValuesBase = (objectToEdit: Question): any => { export const getInitialValuesBase = (objectToEdit: Question): any => {
const tags = objectToEdit?.tags?.map((item: any, index: number) => {
return { ...item, key: index };
});
console.log(objectToEdit);
const newQuestions = objectToEdit?.Questions?.map((item: any) => { const newQuestions = objectToEdit?.Questions?.map((item: any) => {
const tags = item?.tags?.map((tag: any) => ({ const tags = item?.tags?.map((tag: any) => ({
@ -52,6 +49,7 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
return { return {
...item, ...item,
canAnswersBeShuffled: item?.canAnswersBeShuffled ?? 0,
tags, tags,
}; };
}); });
@ -65,10 +63,13 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
subject_id: objectToEdit?.subject_id ?? "", subject_id: objectToEdit?.subject_id ?? "",
isBase: 1, isBase: 1,
parent_id: objectToEdit?.parent_id ?? "", parent_id: objectToEdit?.parent_id ?? "",
canAnswersBeShuffled: objectToEdit?.canAnswersBeShuffled ?? 0,
Questions: questions, Questions: questions,
}; };
}; };
export const getValidationSchemaBase = () => { export const getValidationSchemaBase = () => {
// validate input // validate input
return Yup.object().shape({ return Yup.object().shape({

View File

@ -9,6 +9,9 @@ import { useGetAllLesson } from "../../api/lesson";
import { useDeleteQuestion } from "../../api/Question"; import { useDeleteQuestion } from "../../api/Question";
import DeleteModels from "../../Layout/Dashboard/DeleteModels"; import DeleteModels from "../../Layout/Dashboard/DeleteModels";
import { ModalEnum } from "../../enums/Model"; import { ModalEnum } from "../../enums/Model";
import { useGetAllSubject } from "../../api/subject";
import { useGetAllGrade } from "../../api/grade";
import { useGetAllCurriculum } from "../../api/curriculum";
const Table = lazy(() => import("./Table")); const Table = lazy(() => import("./Table"));
const TableHeader = () => { const TableHeader = () => {
@ -16,35 +19,37 @@ const TableHeader = () => {
const deleteMutation = useDeleteQuestion(); const deleteMutation = useDeleteQuestion();
const { unit_id,lesson_id } = useParams<ParamsEnum>(); const { unit_id,curriculum_id,grade_id ,subject_id,lesson_id} = useParams<ParamsEnum>();
const { data: unit } = useGetAllUnit({ show: unit_id }); const { data: unit } = useGetAllUnit({ show: unit_id });
const { data: lesson } = useGetAllLesson({ show: lesson_id });
const unitName = unit?.data?.name ?? "";
const SubjectName = unit?.data?.subject?.name ?? "";
const lessonName = lesson?.data?.name ?? "";
useSetPageTitle(
t(`page_header.subject`) + const { data: Subject } = useGetAllSubject({
"/" + show: subject_id,
`${SubjectName}` + });
"/" + const { data: grade } = useGetAllGrade({
t(`PageTitle.unit`) + show: grade_id,
"/" + });
`${unitName}` + const { data: Curriculum } = useGetAllCurriculum({
"/" + show: curriculum_id,
t(`PageTitle.lesson`) + });
"/" + const { data: Lesson } = useGetAllLesson({
`${lessonName}` + show: lesson_id,
"/" + });
t(`PageTitle.questions`),
); const gradeName = grade?.data?.name ?? "";
const SubjectName = Subject?.data?.name ?? "";
const CurriculumName = Curriculum?.data?.name ?? "";
const unitName = unit?.data?.name ?? "";
const LessonName = Lesson?.data?.name ?? "";
useSetPageTitle( t(`page_header.grade`)+ "/"+ gradeName +"/"+t(`PageTitle.subject`)+"/"+SubjectName+"/"+t("PageTitle.curriculum")+"/"+CurriculumName+"/"+t("PageTitle.unit")+"/"+unitName+"/"+t("PageTitle.lesson")+"/"+LessonName+"/"+t("PageTitle.question"));
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">
<Suspense fallback={<Spin />}> <Suspense fallback={<Spin />}>
<header> <header>
<h6> <h6>
{t("models.Question")} {SubjectName} {unitName} {lessonName} {t("models.Question")}
</h6> </h6>
</header> </header>
<Table /> <Table />

View File

@ -6,6 +6,10 @@ import useSetPageTitle from "../../../Hooks/useSetPageTitle";
import { canAddSubject } from "../../../utils/hasAbilityFn"; import { canAddSubject } from "../../../utils/hasAbilityFn";
import { useDeleteSubject } from "../../../api/subject"; import { useDeleteSubject } from "../../../api/subject";
import { lazy } from "react"; import { lazy } from "react";
import { useGetAllCurriculum } from "../../../api/curriculum";
import { useParams } from "react-router-dom";
import { ParamsEnum } from "../../../enums/params";
import { useGetAllGrade } from "../../../api/grade";
const Table = lazy(() => import("./TablePage")); const Table = lazy(() => import("./TablePage"));
const AddModalForm = lazy(() => import("../Model/AddModel")); const AddModalForm = lazy(() => import("../Model/AddModel"));
const EditModalForm = lazy(() => import("../Model/EditModel")); const EditModalForm = lazy(() => import("../Model/EditModel"));
@ -16,7 +20,15 @@ const TableWithHeader = () => {
const [t] = useTranslation(); const [t] = useTranslation();
const deleteMutation = useDeleteSubject(); const deleteMutation = useDeleteSubject();
useSetPageTitle(t(`page_header.subject`)); const { grade_id} = useParams<ParamsEnum>();
const { data: grade } = useGetAllGrade({
show: grade_id,
});
const gradeName = grade?.data?.name ?? "";
useSetPageTitle( t(`page_header.grade`)+ "/"+ `${gradeName}` +"/"+t(`PageTitle.subject`));
return ( return (
<div className="TableWithHeader"> <div className="TableWithHeader">

View File

@ -4,3 +4,8 @@
@import "./Model.scss"; @import "./Model.scss";
@import "./Segmented.scss"; @import "./Segmented.scss";
@import "./Mix.scss"; @import "./Mix.scss";
.draggable-row {
cursor: move; /* Change cursor to indicate draggable rows */
}

View File

@ -120,3 +120,10 @@
position: absolute; position: absolute;
left: 100px; left: 100px;
} }
.hint_canAnswersBeShuffled{
display: flex;
gap: 5px;
}

View File

@ -1,7 +1,7 @@
// export const BaseURL = "http://192.168.1.6:8000/api/"; export const BaseURL = "http://192.168.1.111:8000/api/";
// export const BaseURL = "http://127.0.0.1:8000/api/"; // export const BaseURL = "http://127.0.0.1:8000/api/";
export const BaseURL = "http://192.168.1.111:8000/api/"; // export const BaseURL = "http://192.168.1.120:8000/api/";
// export const ImageBaseURL = "http://192.168.1.9:8000/"; // export const ImageBaseURL = "http://192.168.1.9:8000/";

5
src/enums/Term.ts Normal file
View File

@ -0,0 +1,5 @@
export enum TermEnum {
FIRST_TERM = "first_term",
SECOND_TERM = "second_term",
}

View File

@ -6,7 +6,7 @@ function QueryProvider({ children }: any) {
defaultOptions: { defaultOptions: {
queries: { queries: {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
cacheTime:0 // staleTime:Infinity
}, },
}, },
}); });

View File

@ -106,7 +106,9 @@
"add_Question": "إضافة اسئلة", "add_Question": "إضافة اسئلة",
"malty_exercise": "تمرين متعدد", "malty_exercise": "تمرين متعدد",
"add_new_question": "اضافة سؤال جديد", "add_new_question": "اضافة سؤال جديد",
"exercise": "تمارين" "exercise": "تمارين",
"icon" :"الايقونة"
}, },
"columns": { "columns": {
"id": "الرقم التعريفي", "id": "الرقم التعريفي",
@ -119,6 +121,7 @@
"payment_type": "نوع الدفع", "payment_type": "نوع الدفع",
"value": "القيمة", "value": "القيمة",
"subject_name": "اسم المادة", "subject_name": "اسم المادة",
"icon" :"الايقونة",
"image": "الصورة", "image": "الصورة",
"card": "البطاقة", "card": "البطاقة",
"birthday": "تاريخ الميلاد", "birthday": "تاريخ الميلاد",
@ -317,6 +320,7 @@
"due_date": "تاريخ الاستحقاق", "due_date": "تاريخ الاستحقاق",
"assigning_date": "تاريخ التعيين", "assigning_date": "تاريخ التعيين",
"subject_name": "اسم المادة", "subject_name": "اسم المادة",
"icon" :"الايقونة",
"exam_type": "نوع الامتحان", "exam_type": "نوع الامتحان",
"grade_to_pass": "الدرجة اللازمة للنجاح", "grade_to_pass": "الدرجة اللازمة للنجاح",
"max_grade": "الدرجة القصوى", "max_grade": "الدرجة القصوى",
@ -354,7 +358,9 @@
"isBase": "سؤال رئيسي", "isBase": "سؤال رئيسي",
"main_question": "النص الأساسي ", "main_question": "النص الأساسي ",
"question": "السؤال", "question": "السؤال",
"id": "الرقم التعريفي" "id": "الرقم التعريفي",
"canAnswersBeShuffled":"يمكن خلط الإجابات",
"hint":"لحليح"
}, },
"select": { "select": {
@ -679,7 +685,9 @@
"edit": "تعديل", "edit": "تعديل",
"questions": "اسئلة", "questions": "اسئلة",
"lesson": "الدرس", "lesson": "الدرس",
"curriculum": "مقرر" "curriculum": "مقرر",
"subject":"المادة",
"question":"السؤال"
}, },
"page_header": { "page_header": {
"dashboard": "لوحة القيادة / الصفحة الرئيسية", "dashboard": "لوحة القيادة / الصفحة الرئيسية",

View File

@ -306,6 +306,7 @@ export interface Question {
parent_id: number; parent_id: number;
isBase: number; isBase: number;
content: string; content: string;
canAnswersBeShuffled:number ;
max_mark: number; max_mark: number;
min_mark_to_pass: number; min_mark_to_pass: number;
image: string | null; image: string | null;

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

@ -0,0 +1,21 @@
import { TermEnum } from "../enums/Term";
import { Nullable } from "./App";
// Define the type for the object
export interface Unit {
curriculum_id: number;
id: number; // Represents the student ID
name: string; // Represents the student's name
order: number; // Represents the order (could be for sorting)
term: TermEnum; // Represents the term using the Term enum
}
export interface InitialValues {
curriculum_id: number;
id: number; // Represents the student ID
name: string; // Represents the student's name
order: number; // Represents the order (could be for sorting)
term: TermEnum;
}
export type UnitInitialValues = Partial<Nullable<InitialValues>>;

6
src/utils/enumToArray.ts Normal file
View File

@ -0,0 +1,6 @@
export const enumToArray = (enumObj: any) => {
return Object.keys(enumObj).map(key => ({
value: enumObj[key],
label: enumObj[key],
}));
};

View File

@ -3,6 +3,8 @@ import { create } from "zustand";
interface ModelState { interface ModelState {
objectToEdit: any; objectToEdit: any;
setObjectToEdit: (data: any) => void; setObjectToEdit: (data: any) => void;
OldObjectToEdit: any;
setOldObjectToEdit: (data: any) => void;
paramToSend: any; paramToSend: any;
setParamToSend: (data: any) => void; setParamToSend: (data: any) => void;
TagsSearch: any; TagsSearch: any;
@ -27,6 +29,8 @@ interface ModelState {
export const useObjectToEdit = create<ModelState>((set) => ({ export const useObjectToEdit = create<ModelState>((set) => ({
objectToEdit: null, objectToEdit: null,
setObjectToEdit: (data) => set(() => ({ objectToEdit: data })), setObjectToEdit: (data) => set(() => ({ objectToEdit: data })),
OldObjectToEdit: null,
setOldObjectToEdit: (data) => set(() => ({ OldObjectToEdit: data })),
paramToSend: {}, paramToSend: {},
setParamToSend: (data) => set(() => ({ paramToSend: data })), setParamToSend: (data) => set(() => ({ paramToSend: data })),
TagsSearch: null, TagsSearch: null,