diff --git a/site/testing/Testing.tsx b/site/testing/Testing.tsx
new file mode 100644
index 0000000..ed3af39
--- /dev/null
+++ b/site/testing/Testing.tsx
@@ -0,0 +1,15 @@
+import NavBar from "../components/Navbar";
+import Footer from "../components/CustomFooter";
+import Quiz from "./features/Quiz";
+
+function Testing() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+export default Testing;
\ No newline at end of file
diff --git a/site/testing/components/SortableItem.tsx b/site/testing/components/SortableItem.tsx
new file mode 100644
index 0000000..07a965e
--- /dev/null
+++ b/site/testing/components/SortableItem.tsx
@@ -0,0 +1,41 @@
+import { useSortable } from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
+import { ListItem, ListItemText, ListItemButton, ListItemIcon} from '@mui/material';
+import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
+
+interface SortableItemProps {
+ item: string;
+ id: string;// чтобы тайпскрипт компилятор не жаловался, добавил в инетрфейс, хотя и так компилируется нормально
+ isDisabled:boolean;
+}
+
+export function SortableItem({ item ,isDisabled}: SortableItemProps) {
+ const id = item; /* идентификатор для useSortable */
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ } = useSortable({ id });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
\ No newline at end of file
diff --git a/site/testing/features/Matching.tsx b/site/testing/features/Matching.tsx
new file mode 100644
index 0000000..928c9c8
--- /dev/null
+++ b/site/testing/features/Matching.tsx
@@ -0,0 +1,52 @@
+import { Grid, List, ListItem, ListItemButton, ListItemText } from '@mui/material';
+import type {tTasks} from "../quizData"
+import SortableList from '../features/SortableList';
+import { useEffect } from 'react';
+import { useDispatch } from 'react-redux';
+import { addList,mixUp } from './quizSlice';
+interface ComponentProps {
+ tasks: tTasks;
+ index: number;
+ }
+
+function Matching({tasks,index}: ComponentProps) {
+ const dispatch = useDispatch();
+
+ // Добавляем список ответов очередного задания в хранилище
+ useEffect(() => {
+ dispatch(addList({ index, items: answers }));
+ }, []);
+
+ useEffect(() => {
+ dispatch(mixUp());
+ }, []);
+
+
+ const answers: string[] = tasks.map((item) => item.answer);
+ return (
+
+
+
+ {tasks.map((item, index) => (
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
+
+export default Matching
\ No newline at end of file
diff --git a/site/testing/features/Quiz.tsx b/site/testing/features/Quiz.tsx
new file mode 100644
index 0000000..4c322b9
--- /dev/null
+++ b/site/testing/features/Quiz.tsx
@@ -0,0 +1,72 @@
+import { Box, Button, Container, Typography } from '@mui/material';
+import { quiz } from "../quizData";
+import { useSelector } from 'react-redux';
+import {useState } from 'react';
+import Matching from './Matching';
+import type { RootState } from '../../store';
+import store from '../../store';
+import { useDispatch } from 'react-redux';
+import { mixUp, startTesting, stopTesting } from './quizSlice';
+
+function QuizStats() {
+
+ const checkTask = (index: number): string => {
+ useSelector((state: RootState) => state.lists.lists[index]);
+ let state = store.getState();
+ if (state.lists.correctAnswers.length <= index) {
+ return "";
+ }
+ let correctCounter = state.lists.lists[index].reduce((prev, answ, i) => prev + Number(state.lists.correctAnswers[index][i] === answ), 0)
+
+ if (correctCounter == state.lists.lists[index].length) {
+ return "все ответы верные"
+ }
+ return `верно ${correctCounter}/${state.lists.lists[index].length}`
+
+ }
+ return (
+
+ Результаты теста
+ Задание 1: {checkTask(0)}
+ Задание 2: {checkTask(1)}
+
+ )
+}
+
+
+function Quiz() {
+ const dispatch = useDispatch();
+ const [displayingResults, setDisplayingResults] = useState(false);
+ const resetQuiz = () => {
+ setDisplayingResults(false);
+ dispatch(mixUp());
+ dispatch(startTesting());
+ }
+ const checkQuiz = () => {
+ setDisplayingResults(true);
+ dispatch(stopTesting());
+ }
+
+
+ return (
+
+ {quiz.map((item, index) => (
+
+
+ {index + 1}. {item.title}
+
+
+
+ ))}
+
+
+
+
+
+ {displayingResults && }
+
+
+ );
+}
+
+export default Quiz
\ No newline at end of file
diff --git a/site/testing/features/SortableList.tsx b/site/testing/features/SortableList.tsx
new file mode 100644
index 0000000..300d822
--- /dev/null
+++ b/site/testing/features/SortableList.tsx
@@ -0,0 +1,45 @@
+import { DndContext, closestCenter } from '@dnd-kit/core';
+import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
+import { useDispatch, useSelector } from 'react-redux';
+import { setDraggedItems } from './quizSlice';
+import type { RootState } from '../../store';
+import List from '@mui/material/List';
+import { SortableItem } from '../components/SortableItem'
+
+interface ComponentProps {
+ index: number;
+ answers: string[];
+}
+
+function SortableList({ index}: ComponentProps) {
+
+ const dispatch = useDispatch();
+ const arr = useSelector((state: RootState) => state.lists.lists[index])
+ const isDisabled = useSelector((state: RootState) => state.lists.isTestingDone)
+ const draggedItems = arr || [];
+
+ const handleDragEnd = (event: any) => {
+ const { active, over } = event;
+ if (active.id !== over.id) {
+ const oldIndex = draggedItems.indexOf(active.id);
+ const newIndex = draggedItems.indexOf(over.id);
+ const newList = arrayMove(draggedItems, oldIndex, newIndex);
+ dispatch(setDraggedItems({ index, items: newList }));
+ }
+ };
+
+ return (
+
+
+
+ {draggedItems.map((item) => (
+
+ ))}
+
+
+
+ );
+}
+
+export default SortableList;
\ No newline at end of file
diff --git a/site/testing/features/quizSlice.tsx b/site/testing/features/quizSlice.tsx
new file mode 100644
index 0000000..aa7d350
--- /dev/null
+++ b/site/testing/features/quizSlice.tsx
@@ -0,0 +1,67 @@
+import { createSlice } from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
+
+function shuffle(array:Array):Array {
+ let currentIndex = array.length;
+
+ // While there remain elements to shuffle...
+ while (currentIndex != 0) {
+
+ // Pick a remaining element...
+ let randomIndex = Math.floor(Math.random() * currentIndex);
+ currentIndex--;
+
+ // And swap it with the current element.
+ [array[currentIndex], array[randomIndex]] = [
+ array[randomIndex], array[currentIndex]];
+ }
+ return array;
+}
+
+interface ListsState {
+ lists: string[][]; // хранит перемещаемые элементы каждого списка ответов
+ correctAnswers:string[][];
+ isTestingDone:boolean
+}
+
+const initialState: ListsState = {
+ lists: [],
+ correctAnswers:[],
+ isTestingDone:false
+};
+
+const listsSlice = createSlice({
+ name: 'lists',
+ initialState,
+ reducers: {
+ addList: (state, action: PayloadAction<{index: number; items: string[]}>)=>{
+ const { index, items } = action.payload;
+ state.lists.splice(index, 1, items);
+ state.correctAnswers.splice(index, 1, items);
+
+ },
+ setDraggedItems: (state, action: PayloadAction<{ index: number; items: string[] }>) => {
+ const { index, items } = action.payload;
+ if (index >= 0 && index < state.lists.length) {
+ state.lists[index] = items; // обновляем конкретный список
+ }
+ },
+ mixUp: (state) => {
+ state.lists=state.lists.map((list)=>
+ {
+ return shuffle(list);
+ })
+ },
+ stopTesting:(state)=>{
+ state.isTestingDone=true;
+ },
+ startTesting:(state)=>{
+ state.isTestingDone=false;
+ }
+ },
+});
+
+// Экспортируем действия и редьюсер
+export const { addList, setDraggedItems,mixUp,startTesting,stopTesting } = listsSlice.actions;
+export type {ListsState}
+export default listsSlice.reducer;
\ No newline at end of file
diff --git a/site/testing/quizData.tsx b/site/testing/quizData.tsx
new file mode 100644
index 0000000..84fd9e3
--- /dev/null
+++ b/site/testing/quizData.tsx
@@ -0,0 +1,64 @@
+export type tTasks ={
+ "question": string; /* вопрос задания*/
+ "answer": string; /* ответ задания*/
+}[]
+
+export type tQuizzes = {
+ "id": number,
+ "type": "M" | "S", /* типы заданий, М - сопоставление*/
+ "title": string, /* формулировка задания */
+ "tasks": tTasks,
+}[];
+
+export const quiz: tQuizzes = [
+ {
+ "id": 1,
+ "type": "M",
+ "title": "Сопоставьте сооружение и город, в котором оно расположено.",
+ "tasks": [
+ {
+ "question": "Башня Аль-Хамра",
+ "answer": "Кувейт"
+ },
+ {
+ "question": "Башня CITIC",
+ "answer": "Гуанчжоу"
+ },
+ {
+ "question": "Телебашня «Коктобе»",
+ "answer": "Алматы"
+ },
+ {
+ "question": "Си-Эн Тауэр",
+ "answer": "Торонто"
+ },
+ ]
+ },
+ {
+ "id": 2,
+ "type": "M",
+ "title": "Сопоставьте сооружение и его высоту.",
+ "tasks": [
+ {
+ "question": "Tokyo Skytree",
+ "answer": "634"
+ },
+ {
+ "question": "Бурдж-Халифа",
+ "answer": "838"
+ },
+ {
+ "question": "Эмпайр-стейт-билдинг",
+ "answer": "448.7"
+ },
+ {
+ "question": "Останкинская башня",
+ "answer": "540.1"
+ },
+ {
+ "question": "Lotte World Tower",
+ "answer": "555"
+ },
+ ]
+ }
+]
\ No newline at end of file