какие-то изменения

This commit is contained in:
Никита Потапов 2024-01-15 15:29:51 +04:00
parent 1e435ad24b
commit 8ceed9a2c7
12 changed files with 484 additions and 170 deletions

386
data.json
View File

@ -1,155 +1,233 @@
{
"users": [
{
"id": 1,
"name": "Никита",
"surname": "Потапов",
"username": "nspotapov",
"password": "123456",
"isTeacher": false,
"groupId": 1
},
{
"id": 2,
"name": "Алексей",
"surname": "Филиппов",
"username": "afilippov",
"password": "123456",
"isTeacher": true,
"groupId": null
},
{
"id": 3,
"name": "Елена",
"surname": "Бакальская",
"username": "ekallin",
"password": "123456",
"isTeacher": false,
"groupId": 1
},
{
"name": "Алексей",
"surname": "Крюков",
"username": "akrukov",
"password": "123",
"isTeacher": false,
"groupId": 1,
"id": 4
}
],
"groups": [
{
"id": 1,
"name": "пибд-21"
}
],
"subjects": [
{
"id": 1,
"name": "Интернет программирование"
},
{
"id": 2,
"name": "РПП"
},
{
"id": 3,
"name": "Физическая культура"
},
{
"id": 4,
"name": "Системный анализ"
},
{
"id": 5,
"name": "Системное администрирование"
},
{
"id": 6,
"name": "Основы теории систем"
},
{
"id": 7,
"name": "Иностранный язык"
},
{
"id": 8,
"name": "Философия"
},
{
"id": 9,
"name": "Теория вероятностей"
},
{
"id": 10,
"name": "Операционные системы"
},
{
"id": 11,
"name": "Основы ЭВМ и систем"
},
{
"id": 12,
"name": "Базы данных"
}
],
"marks": [
{
"id": 2,
"name": "неуд",
"controltypeId": 1
},
{
"id": 3,
"name": "удов",
"controltypeId": 1
},
{
"id": 4,
"name": "хорошо",
"controltypeId": 1
},
{
"id": 5,
"name": "отлично",
"controltypeId": 1
},
{
"id": 6,
"name": "зачтено",
"controltypeId": 2
},
{
"id": 7,
"name": "незачет",
"controltypeId": 2
}
],
"statements": [
{
"id": 1,
"subjectId": 1,
"groupId": 1,
"controlTypeId": 2,
"date": "Dec 23 2023"
}
],
"controltypes": [
{
"id": 1,
"name": "Экзамен"
},
{
"id": 2,
"name": "Зачет"
}
],
"usermarks": [
{
"id": 1,
"userId": 1,
"markId": 1,
"statementId": 1
}
]
}
"users": [
{
"id": 1,
"name": "Никита",
"surname": "Потапов",
"username": "nspotapov",
"password": "123456",
"isTeacher": false,
"groupId": 1
},
{
"id": 2,
"name": "Алексей",
"surname": "Филиппов",
"username": "afilippov",
"password": "123456",
"isTeacher": true,
"groupId": null
},
{
"id": 3,
"name": "Елена",
"surname": "Бакальская",
"username": "ekallin",
"password": "123456",
"isTeacher": false,
"groupId": 1
},
{
"name": "Алексей",
"surname": "Крюков",
"username": "akrukov",
"password": "123",
"isTeacher": false,
"groupId": 1,
"id": 4
},
{
"name": "Данил",
"surname": "Горячев",
"username": "danilgor",
"password": "123456",
"isTeacher": false,
"groupId": 2,
"id": 5
},
{
"name": "Максим",
"surname": "Кузярин",
"username": "maximkuzyarin",
"password": "123456",
"isTeacher": false,
"groupId": 3,
"id": 6
},
{
"name": "dfgfdgb",
"surname": "fdfdbdf",
"username": "afilippovffb",
"password": "123456",
"isTeacher": true,
"groupId": null,
"id": 7
},
{
"name": "Оксана",
"surname": "Потапова",
"username": "opotapova",
"password": "123456",
"isTeacher": false,
"groupId": 3,
"id": 8
}
],
"groups": [
{
"id": 1,
"name": "пибд-21"
},
{
"id": 2,
"name": "пибд-22"
},
{
"id": 3,
"name": "пибд-23"
}
],
"subjects": [
{
"id": 1,
"name": "Интернет программирование"
},
{
"id": 2,
"name": "РПП"
},
{
"id": 3,
"name": "Физическая культура"
},
{
"id": 4,
"name": "Системный анализ"
},
{
"id": 5,
"name": "Системное администрирование"
},
{
"id": 6,
"name": "Основы теории систем"
},
{
"id": 7,
"name": "Иностранный язык"
},
{
"id": 8,
"name": "Философия"
},
{
"id": 9,
"name": "Теория вероятностей"
},
{
"id": 10,
"name": "Операционные системы"
},
{
"id": 11,
"name": "Основы ЭВМ и систем"
},
{
"id": 12,
"name": "Базы данных"
}
],
"marks": [
{
"id": 2,
"name": "неуд",
"controltypeId": 1
},
{
"id": 3,
"name": "удов",
"controltypeId": 1
},
{
"id": 4,
"name": "хорошо",
"controltypeId": 1
},
{
"id": 5,
"name": "отлично",
"controltypeId": 1
},
{
"id": 6,
"name": "зачтено",
"controltypeId": 2
},
{
"id": 7,
"name": "незачет",
"controltypeId": 2
}
],
"statements": [
{
"id": 1,
"subjectId": 1,
"groupId": 1,
"controltypeId": 2,
"date": "Dec 23 2023"
},
{
"id": 2,
"subjectId": 2,
"groupId": 1,
"controltypeId": 1,
"date": "Dec 25 2023"
},
{
"id": 3,
"subjectId": 4,
"groupId": 1,
"controltypeId": 1,
"date": "Dec 26 2023"
},
{
"subjectId": "1",
"groupId": "1",
"controltypeId": "2",
"id": 4
},
{
"subjectId": "3",
"groupId": "2",
"controltypeId": "2",
"date": "2024-01-12T00:00:00.000Z",
"id": 5
},
{
"subjectId": "3",
"groupId": "2",
"controltypeId": "2",
"date": "2024-01-12T00:00:00.000Z",
"id": 6
}
],
"controltypes": [
{
"id": 1,
"name": "Экзамен"
},
{
"id": 2,
"name": "Зачет"
}
],
"usermarks": [
{
"id": 1,
"userId": 1,
"markId": 1,
"statementId": 1
}
]
}

View File

@ -1,5 +1,6 @@
import axios from "axios";
import { Toast } from "react-bootstrap";
import toast from "react-hot-toast";
export class HttpError extends Error {
constructor(message = "") {
@ -25,7 +26,7 @@ function responseErrorHandler(error) {
if (error === null) {
throw new Error("Unrecoverable error!! Error is null!");
}
Toast.error(error.message, { id: "AxiosError" });
toast.error(error.message, { id: "AxiosError" });
return Promise.reject(error.message);
}

View File

@ -19,8 +19,8 @@ const Header = ({ routes }) => {
}
return (
<header className='sticky-top'>
<Navbar expand='md bg-dark'>
<header className='sticky-top bg-dark mb-2'>
<Navbar expand='md'>
<Container fluid>
<Navbar.Brand as={Link} to={indexPageLink?.path ?? '/'}>
Учебный процесс

View File

@ -0,0 +1,23 @@
import { PropTypes } from "prop-types";
import { Trash } from "react-bootstrap-icons";
import { Link } from "react-router-dom";
const StatementTableRow = ({ statement, onDelete }) => {
return (
<tr>
<th scope='row'>{statement.id}</th>
<td>{statement.subject.name}</td>
<td>{statement.group.name}</td>
<td>{statement.controltype.name}</td>
<td>{new Date(statement.date).toLocaleDateString('ru')}</td>
<td><Link><Trash fill="red" onClick={() => { onDelete(statement.id); }} /></Link></td>
</tr>
);
};
StatementTableRow.propTypes = {
statement: PropTypes.object,
onDelete: PropTypes.func
};
export default StatementTableRow;

View File

@ -0,0 +1,23 @@
import { useEffect, useState } from "react";
import ControlTypesApiService from "../services/CotrolTypesApiService";
export const useControlTypes = () => {
const [controlTypesRefresh, setControlTypesRefresh] = useState(false);
const [controlTypes, setControlTypes] = useState([]);
const handleControlTypesChange = () =>
setControlTypesRefresh(!controlTypesRefresh);
const getControlTypes = async () => {
const data = await ControlTypesApiService.getAll();
setControlTypes(data ?? []);
};
useEffect(() => {
getControlTypes();
}, [controlTypesRefresh]);
return {
controlTypes,
handleControlTypesChange,
};
};

30
src/hooks/FilterHook.js Normal file
View File

@ -0,0 +1,30 @@
import { useSearchParams } from "react-router-dom";
import { useState } from "react";
const useQueryParamFilter = (queryParamName) => {
const [filterValue, setFilterValue] = useState(null);
const [searchParams, setSearchParams] = useSearchParams();
const onFilterChange = (event) => {
let targetValue = event.target.value;
try {
targetValue = parseInt(targetValue);
} catch (e) {
targetValue = null;
}
if (targetValue) {
searchParams.set(queryParamName, event.target.value);
} else {
searchParams.delete(queryParamName);
}
setSearchParams(searchParams);
};
return {
filterValue: searchParams.get(queryParamName) || null,
onFilterChange,
};
};
export default useQueryParamFilter;

View File

@ -4,7 +4,7 @@ import GroupsApiService from "../services/GroupsApiService";
export const useGroups = () => {
const [groupsRefresh, setGroupsRefresh] = useState(false);
const [groups, setGroups] = useState([]);
const handleGroupsRefresh = () => setGroupsRefresh(!groupsRefresh);
const handleGroupsChange = () => setGroupsRefresh(!groupsRefresh);
const getGroups = async () => {
const data = await GroupsApiService.getAll();
@ -17,6 +17,6 @@ export const useGroups = () => {
return {
groups,
handleGroupsRefresh,
handleGroupsChange,
};
};

View File

@ -0,0 +1,34 @@
import { useEffect, useState } from "react";
import StatementsApiService from "../services/StatementsApiService";
export const useStatements = (filters) => {
const [statementsRefresh, setStatementsRefresh] = useState(false);
const [statements, setStatements] = useState([]);
const handleStatementsChange = () =>
setStatementsRefresh(!statementsRefresh);
const getStatements = async () => {
let expand = `?_expand=group&_expand=controltype&_expand=subject&_sort=date&_order=desc`;
if (Object.keys(filters).length !== 0) {
for (let index = 0; index < Object.keys(filters).length; index++) {
const key = Object.keys(filters)[index];
const v = filters[key];
if (v) {
expand += `&${key}=${v}`;
}
}
}
const data = await StatementsApiService.getAll(expand);
setStatements(data ?? []);
};
useEffect(() => {
getStatements();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [statementsRefresh]);
return {
statements,
handleStatementsChange,
};
};

View File

@ -1,13 +1,23 @@
import { useEffect, useState } from "react";
import SubjectsApiService from "../services/SubjectsApiService";
export const useSubjects = () => {
export const useSubjects = (filters = null) => {
const [subjectsRefresh, setSubjectsRefresh] = useState(false);
const [subjects, setSubjects] = useState([]);
const handleSubjectsRefresh = () => setSubjectsRefresh(!subjectsRefresh);
const handleSubjectsChange = () => setSubjectsRefresh(!subjectsRefresh);
const getSubjects = async () => {
const data = await SubjectsApiService.getAll();
let expand = "";
if (filters && Object.keys(filters).length !== 0) {
for (let index = 0; index < Object.keys(filters).length; index++) {
const key = Object.keys(filters)[index];
const v = filters[key];
if (v) {
expand += `&${key}=${v}`;
}
}
}
const data = await SubjectsApiService.getAll(expand);
setSubjects(data ?? []);
};
@ -17,6 +27,6 @@ export const useSubjects = () => {
return {
subjects,
handleSubjectsRefresh,
handleSubjectsChange,
};
};

View File

@ -62,7 +62,7 @@ export const useUsers = () => {
useEffect(() => {
getUsers();
}, [usersRefresh]);
});
return {
users,
@ -70,23 +70,33 @@ export const useUsers = () => {
};
};
export const useStudents = () => {
export const useStudents = (filters = null) => {
const [studentsRefresh, setStudentsRefresh] = useState(false);
const [students, setStudents] = useState([]);
const handleStudentsRefresh = () => setStudentsRefresh(!studentsRefresh);
const handleStudentsChange = () => setStudentsRefresh(!studentsRefresh);
const getStudents = async () => {
const expand = `?isTeacher=false&_expand=group`;
let expand = `?isTeacher=false&_expand=group&_sort=surname,name`;
if (filters && Object.keys(filters).length !== 0) {
for (let index = 0; index < Object.keys(filters).length; index++) {
const key = Object.keys(filters)[index];
const v = filters[key];
if (v) {
expand += `&${key}=${v}`;
}
}
}
const data = await UsersApiService.getAll(expand);
setStudents(data ?? []);
};
useEffect(() => {
getStudents();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [studentsRefresh]);
return {
students,
handleStudentsRefresh,
handleStudentsChange,
};
};

View File

@ -1,11 +1,104 @@
import StatementTableRow from "../../components/statementtablerow/statementtablerow";
import { useGroups } from "../../hooks/GroupHook";
import { useStatements } from "../../hooks/StatementsHook";
import { useSubjects } from "../../hooks/SubjectsHook";
import { useOnlyTeachers } from "../../hooks/UserHooks";
import { Container } from "react-bootstrap";
import useQueryParamFilter from "../../hooks/FilterHook";
import { useControlTypes } from "../../hooks/ControlTypesHook";
import StatementsApiService from "../../services/StatementsApiService";
import { useState } from "react";
import toast from "react-hot-toast";
const StatementsPage = () => {
useOnlyTeachers();
const [statementDate, setStatementDate] = useState('');
const { subjects, handleSubjectsChange } = useSubjects();
const { groups, handleGroupsChange } = useGroups();
const { controlTypes, handleControlTypesChange } = useControlTypes();
const { filterValue: subjectFilter, onFilterChange: onSubjectChange } = useQueryParamFilter("subjectId");
const { filterValue: groupFilter, onFilterChange: onGroupChange } = useQueryParamFilter("groupId");
const { filterValue: controlTypeFilter, onFilterChange: onControlTypeChange } = useQueryParamFilter("controltypeId");
const { statements, handleStatementsChange } = useStatements({
"subjectId": subjectFilter,
"groupId": groupFilter,
"controltypeId": controlTypeFilter
});
const onDateChange = () => {
const date = document.getElementById('inputStatementDate').value;
setStatementDate(date);
};
const onButtonAddStatementClick = async () => {
if (subjectFilter && groupFilter && controlTypeFilter && statementDate) {
await StatementsApiService.create(
{
subjectId: subjectFilter,
groupId: groupFilter,
controltypeId: controlTypeFilter,
date: new Date(statementDate)
}
);
toast.success('Ведомость создана');
handleStatementsChange();
}
};
const onButtonDeleteStatementClick = async (statementId) => {
if (statementId) {
await StatementsApiService.delete(statementId);
toast.success("Ведомость успешно удалена");
handleStatementsChange();
}
};
return (
<>
<h5>StatementsPage</h5>
<Container>
<div className="d-flex flex-column flex-md-row">
<select name="subjectId" id="subjectId" className="me-none me-md-2 mb-2 mb-md-0" onChange={(e) => { onSubjectChange(e); handleStatementsChange(); }}>
<option value={null}>Выберите предмет</option>
{
subjects.map((subject, index) => <option key={index} value={subject.id}>{subject.name}</option>)
}
</select>
<select name="groupId" id="groupId" className="me-none me-md-2 mb-2 mb-md-0" onChange={(e) => { onGroupChange(e); handleStatementsChange(); }}>
<option value={null}>Выберите группу</option>
{
groups.map((group, index) => <option key={index} value={group.id}>{group.name}</option>)
}
</select>
<select name="controltypeId" id="controltypeId" className="mb-2 mb-md-0 me-none me-md-2" onChange={(e) => { onControlTypeChange(e); handleStatementsChange(); }}>
<option value={null}>Выберите тип</option>
{
controlTypes.map((ct, index) => <option key={index} value={ct.id}>{ct.name}</option>)
}
</select>
<input id="inputStatementDate" type="date" className="mb-2 mb-md-0 me-none me-md-2" value={statementDate} onChange={(e) => { onDateChange(e); handleStatementsChange(); }} />
<button className="btn btn-success btn-sm" onClick={onButtonAddStatementClick}>
Добавить ведомость
</button>
</div>
<table className="table">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Предмет</th>
<th scope="col">Группа</th>
<th scope="col">Тип</th>
<th scope="col">Дата</th>
</tr>
</thead>
<tbody>
{
statements.map((statement, index) => <StatementTableRow key={index} statement={statement} onDelete={onButtonDeleteStatementClick} />)
}
</tbody>
</table>
</Container>
</>
);
};

View File

@ -1,14 +1,26 @@
import StudentTableRow from "../../components/studenttablerow/studenttablerow";
import { useOnlyTeachers, useStudents } from "../../hooks/UserHooks";
import { Container } from "react-bootstrap";
import { useGroups } from "../../hooks/GroupHook";
import useQueryParamFilter from "../../hooks/FilterHook";
const StudentsPage = () => {
useOnlyTeachers();
const { students, handleStudentsChange } = useStudents();
const { filterValue: groupFilter, onFilterChange: onGroupChange } = useQueryParamFilter("groupId");
const { groups, handleGroupsChange } = useGroups();
const { students, handleStudentsChange } = useStudents({
'groupId': groupFilter
});
return (
<>
<Container>
<select name="groupId" id="groupId" className="me-none me-md-2 mb-2 mb-md-0" onChange={(e) => { onGroupChange(e); handleStudentsChange(); }}>
<option value={null}>Выберите группу</option>
{
groups.map((group, index) => <option key={index} value={group.id}>{group.name}</option>)
}
</select>
<table className="table">
<thead>
<tr>