From b1ea3a670e6e03546876b7bae70fce17a3808141 Mon Sep 17 00:00:00 2001 From: Maksim Skobaro Date: Fri, 7 Feb 2025 09:48:50 +0300 Subject: [PATCH] GroupCreation, ErrorHandling, Database --- .gitignore | 1 + .../tdms/config/SecurityConfiguration.java | 1 + .../tdms/controller/GroupController.java | 13 +++- .../tdms/controller/SysInfoController.java | 4 +- .../tdms/controller/payload/GroupDTO.java | 4 +- .../payload/GroupRegistrationDTO.java | 14 +++++ .../java/ru/tubryansk/tdms/entity/Group.java | 16 ++++- .../ru/tubryansk/tdms/entity/Student.java | 5 +- .../ru/tubryansk/tdms/entity/Teacher.java | 32 ++++++++++ .../java/ru/tubryansk/tdms/entity/User.java | 2 + .../entity/repository/GroupRepository.java | 2 + .../entity/repository/StudentRepository.java | 4 +- .../entity/repository/TeacherRepository.java | 13 ++++ .../exception/GlobalExceptionHandler.java | 7 ++- .../tubryansk/tdms/service/GroupService.java | 40 +++++++++++- .../tdms/service/StudentService.java | 17 +++-- .../migration/V00050__Create__group_table.sql | 22 ------- .../V00050__Create__teacher_table.sql | 19 ++++++ .../migration/V00060__Create__group_table.sql | 22 +++++++ ....sql => V00070__Create__student_table.sql} | 22 +++---- web/src/components/NotificationContainer.tsx | 36 ++++------- .../custom/controls/ReactiveControls.tsx | 2 +- web/src/components/group/CreateGroupModal.tsx | 55 ++++++++++++++++ web/src/components/layout/Header.tsx | 19 +++++- ...stration.tsx => UserRegistrationModal.tsx} | 30 ++++++--- web/src/models/error.ts | 12 ++++ web/src/router/routes.ts | 3 - web/src/router/viewMap.tsx | 2 - web/src/utils/reactive/reactiveValue.ts | 62 +++++++++---------- web/src/utils/reactive/validators.ts | 39 ++++++------ web/src/utils/request.ts | 3 +- 31 files changed, 369 insertions(+), 154 deletions(-) create mode 100644 server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupRegistrationDTO.java create mode 100644 server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java create mode 100644 server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java delete mode 100644 server/src/main/resources/db/migration/V00050__Create__group_table.sql create mode 100644 server/src/main/resources/db/migration/V00050__Create__teacher_table.sql create mode 100644 server/src/main/resources/db/migration/V00060__Create__group_table.sql rename server/src/main/resources/db/migration/{V00060__Create__student_table.sql => V00070__Create__student_table.sql} (82%) create mode 100644 web/src/components/group/CreateGroupModal.tsx rename web/src/components/user/{UserRegistration.tsx => UserRegistrationModal.tsx} (80%) create mode 100644 web/src/models/error.ts diff --git a/.gitignore b/.gitignore index 7772781..a42b2df 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ build/ ### VS Code ### .vscode/ /logs/app.log +/configurations/Start RDBMS.run.xml diff --git a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java b/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java index 926951b..8765258 100644 --- a/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java +++ b/server/src/main/java/ru/tubryansk/tdms/config/SecurityConfiguration.java @@ -97,6 +97,7 @@ public class SecurityConfiguration { httpAuthorization.requestMatchers("/api/v1/student/current").permitAll(); /* GroupController */ httpAuthorization.requestMatchers("/api/v1/group/get-all").permitAll(); + httpAuthorization.requestMatchers("api/v1/group/create-group").hasAuthority("ROLE_ADMINISTRATOR"); /* deny all other api requests */ httpAuthorization.requestMatchers("/api/**").denyAll(); /* since api already blocked, all other requests are static resources */ diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java b/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java index 504df1a..ee1ac95 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java +++ b/server/src/main/java/ru/tubryansk/tdms/controller/GroupController.java @@ -1,14 +1,16 @@ package ru.tubryansk.tdms.controller; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import ru.tubryansk.tdms.controller.payload.GroupDTO; +import ru.tubryansk.tdms.controller.payload.GroupRegistrationDTO; import ru.tubryansk.tdms.service.GroupService; import java.util.Collection; -@RestController("/api/v1/group/") +@RestController +@RequestMapping("/api/v1/group") public class GroupController { @Autowired private GroupService groupService; @@ -17,4 +19,9 @@ public class GroupController { public Collection getAllGroups() { return groupService.getAllGroups(); } + + @PostMapping("/create-group") + public void createGroup(@RequestBody @Valid GroupRegistrationDTO groupRegistrationDTO) { + groupService.createGroup(groupRegistrationDTO.getName()); + } } diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java b/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java index 003d9c0..860a337 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java +++ b/server/src/main/java/ru/tubryansk/tdms/controller/SysInfoController.java @@ -1,6 +1,5 @@ package ru.tubryansk.tdms.controller; -import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,9 +12,10 @@ public class SysInfoController { @Autowired private SysInfoService sysInfoService; - @SneakyThrows @GetMapping("/version") public String getVersion() { return sysInfoService.getVersion(); } + + // @GetMapping("/status") } diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java b/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java index bbe8931..b6f68a1 100644 --- a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java +++ b/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupDTO.java @@ -9,6 +9,6 @@ import lombok.ToString; @ToString public class GroupDTO { private String name; - private String principalName; - private Boolean isMePrincipal; + private String curatorName; + private Boolean iAmCurator; } diff --git a/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupRegistrationDTO.java b/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupRegistrationDTO.java new file mode 100644 index 0000000..564ff39 --- /dev/null +++ b/server/src/main/java/ru/tubryansk/tdms/controller/payload/GroupRegistrationDTO.java @@ -0,0 +1,14 @@ +package ru.tubryansk.tdms.controller.payload; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +@Getter +public class GroupRegistrationDTO { + @NotEmpty(message = "Имя группы не может быть пустым") + @Size(min = 3, max = 50, message = "Имя группы должно быть от 3 до 50 символов") + @Pattern(regexp = "^[а-яА-ЯёЁ0-9_-]*$", message = "Имя группы должно содержать только русские буквы, дефис, нижнее подчеркивание и цифры") + private String name; +} diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Group.java b/server/src/main/java/ru/tubryansk/tdms/entity/Group.java index 99eea52..e417058 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Group.java +++ b/server/src/main/java/ru/tubryansk/tdms/entity/Group.java @@ -5,13 +5,17 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.ZonedDateTime; @Getter @Setter @ToString @Entity -@Table(name = "group") +@Table(name = "`group`") public class Group { @Id @Column(name = "id") @@ -20,6 +24,12 @@ public class Group { @Column(name = "name") private String name; @ManyToOne - @JoinColumn(name = "curator_user_id") - private User groupCurator; + @JoinColumn(name = "curator_teacher_id") + private Teacher groupCurator; + @Column(name = "created_at") + @CreationTimestamp + private ZonedDateTime createdAt; + @Column(name = "updated_at") + @UpdateTimestamp + private ZonedDateTime updatedAt; } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Student.java b/server/src/main/java/ru/tubryansk/tdms/entity/Student.java index eae5aa1..cdde3fe 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/Student.java +++ b/server/src/main/java/ru/tubryansk/tdms/entity/Student.java @@ -47,9 +47,10 @@ public class Student { @ManyToOne @JoinColumn(name = "diploma_topic_id") private DiplomaTopic diplomaTopic; + // Научный руководитель @ManyToOne - @JoinColumn(name = "mentor_user_id") - private User mentorUser; + @JoinColumn(name = "adviser_teacher_id") + private Teacher adviser; @ManyToOne @JoinColumn(name = "group_id") private Group group; diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java b/server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java new file mode 100644 index 0000000..48aafba --- /dev/null +++ b/server/src/main/java/ru/tubryansk/tdms/entity/Teacher.java @@ -0,0 +1,32 @@ +package ru.tubryansk.tdms.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.ZonedDateTime; +import java.util.List; + +@Getter +@Entity +@Table(name = "teacher") +public class Teacher { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @OneToOne + @JoinColumn(name = "user_id") + private User user; + @OneToMany(mappedBy = "groupCurator") + private List curatingGroups; + @OneToMany(mappedBy = "adviser") + private List advisingStudents; + + @Column(name = "created_at") + @CreationTimestamp + private ZonedDateTime createdAt; + @Column(name = "updated_at") + @UpdateTimestamp + private ZonedDateTime updatedAt; +} diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/User.java b/server/src/main/java/ru/tubryansk/tdms/entity/User.java index c76df19..67a772e 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/User.java +++ b/server/src/main/java/ru/tubryansk/tdms/entity/User.java @@ -2,6 +2,7 @@ package ru.tubryansk.tdms.entity; import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -19,6 +20,7 @@ import java.util.List; @Getter @Setter @ToString +@EqualsAndHashCode(of = "id") @Entity @Table(name = "`user`") public class User implements UserDetails { diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java b/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java index f874a35..c3d7141 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java +++ b/server/src/main/java/ru/tubryansk/tdms/entity/repository/GroupRepository.java @@ -10,4 +10,6 @@ public interface GroupRepository extends JpaRepository { default Group findByIdThrow(Long id) { return this.findById(id).orElseThrow(() -> new NotFoundException(Group.class, id)); } + + boolean existsByName(String name); } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java b/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java index d7bc259..868631b 100644 --- a/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java +++ b/server/src/main/java/ru/tubryansk/tdms/entity/repository/StudentRepository.java @@ -9,8 +9,8 @@ import ru.tubryansk.tdms.exception.NotFoundException; import java.util.Optional; @Repository -public interface StudentRepository extends JpaRepository { - default Student findByIdThrow(Integer id) { +public interface StudentRepository extends JpaRepository { + default Student findByIdThrow(Long id) { return this.findById(id).orElseThrow(() -> new NotFoundException(Student.class, id)); } diff --git a/server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java b/server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java new file mode 100644 index 0000000..6d7f58e --- /dev/null +++ b/server/src/main/java/ru/tubryansk/tdms/entity/repository/TeacherRepository.java @@ -0,0 +1,13 @@ +package ru.tubryansk.tdms.entity.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.tubryansk.tdms.entity.Teacher; +import ru.tubryansk.tdms.exception.NotFoundException; + +@Repository +public interface TeacherRepository extends JpaRepository { + default Teacher findByIdThrow(Long id) { + return this.findById(id).orElseThrow(() -> new NotFoundException(Teacher.class, id)); + } +} diff --git a/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java b/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java index 781e9f1..a10a028 100644 --- a/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java +++ b/server/src/main/java/ru/tubryansk/tdms/exception/GlobalExceptionHandler.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; import ru.tubryansk.tdms.controller.payload.ErrorResponse; +import java.util.Random; import java.util.stream.Collectors; @RestControllerAdvice @@ -51,7 +52,9 @@ public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ErrorResponse handleUnexpectedException(Exception e) { - log.error("Unexpected exception.", e); - return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR); + Random random = new Random(); + long errorInx = random.nextLong(); + log.error("Unexpected exception. random: {}", errorInx, e); + return new ErrorResponse("Произошла непредвиденная ошибка, обратитесь к администратору. Номер ошибки: " + errorInx, ErrorResponse.ErrorCode.INTERNAL_ERROR); } } diff --git a/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java b/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java index 14d7e22..adbaf64 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java +++ b/server/src/main/java/ru/tubryansk/tdms/service/GroupService.java @@ -1,15 +1,53 @@ package ru.tubryansk.tdms.service; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.tubryansk.tdms.controller.payload.GroupDTO; +import ru.tubryansk.tdms.entity.Group; +import ru.tubryansk.tdms.entity.User; +import ru.tubryansk.tdms.entity.repository.GroupRepository; +import ru.tubryansk.tdms.exception.BusinessException; import java.util.Collection; +import java.util.List; @Service @Transactional public class GroupService { + @Autowired + private GroupRepository groupRepository; + @Autowired + private CallerService callerService; + public Collection getAllGroups() { - return null; + List groups = groupRepository.findAll(); + User callerUser = callerService.getCallerUser().orElse(null); + + return groups.stream() + .map(g -> { + GroupDTO groupDTO = new GroupDTO(); + groupDTO.setName(g.getName()); + + if (g.getGroupCurator() != null) { + groupDTO.setCuratorName(g.getGroupCurator().getUser().getFullName()); + if (callerUser != null) { + groupDTO.setIAmCurator(g.getGroupCurator().getUser().equals(callerUser)); + } + } + return groupDTO; + }) + .toList(); + } + + public void createGroup(String groupName) { + boolean existsByName = groupRepository.existsByName(groupName); + if (existsByName) { + throw new BusinessException("Группа с именем " + groupName + " уже существует"); + } + + Group group = new Group(); + group.setName(groupName); + groupRepository.save(group); } } diff --git a/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java b/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java index bd41725..e1ac11c 100644 --- a/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java +++ b/server/src/main/java/ru/tubryansk/tdms/service/StudentService.java @@ -10,7 +10,6 @@ import ru.tubryansk.tdms.entity.repository.DiplomaTopicRepository; import ru.tubryansk.tdms.entity.repository.StudentRepository; import ru.tubryansk.tdms.exception.AccessDeniedException; -import java.util.Map; import java.util.Optional; @Service @@ -26,15 +25,15 @@ public class StudentService { private CallerService callerService; /** @param studentToDiplomaTopic Map of @{@link Student} id and @{@link DiplomaTopic} id */ - public void changeDiplomaTopic(Map studentToDiplomaTopic) { - studentToDiplomaTopic.forEach(this::changeDiplomaTopic); - } + // public void changeDiplomaTopic(Map studentToDiplomaTopic) { + // studentToDiplomaTopic.forEach(this::changeDiplomaTopic); + // } - public void changeDiplomaTopic(Integer studentId, Integer diplomaTopicId) { - Student student = studentRepository.findByIdThrow(studentId); - DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId); - student.setDiplomaTopic(diplomaTopic); - } + // public void changeDiplomaTopic(Integer studentId, Integer diplomaTopicId) { + // Student student = studentRepository.findByIdThrow(studentId); + // DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId); + // student.setDiplomaTopic(diplomaTopic); + // } public void changeCallerDiplomaTopic(Integer diplomaTopicId) { DiplomaTopic diplomaTopic = diplomaTopicRepository.findByIdThrow(diplomaTopicId); diff --git a/server/src/main/resources/db/migration/V00050__Create__group_table.sql b/server/src/main/resources/db/migration/V00050__Create__group_table.sql deleted file mode 100644 index e907daa..0000000 --- a/server/src/main/resources/db/migration/V00050__Create__group_table.sql +++ /dev/null @@ -1,22 +0,0 @@ -create table "group" -( - id bigserial primary key, - - name text not null unique, - curator_user_id bigint, - - created_at timestamptz not null, - updated_at timestamptz -); - --- FOREIGN KEY -alter table "group" - add constraint fk_group_curator_user_id - foreign key (curator_user_id) references "user" (id) - on delete set null on update cascade; - --- COMMENTS -comment on table "group" is 'Таблица групп студентов'; - -comment on column "group".name is 'Название группы'; -comment on column "group".curator_user_id is 'Идентификатор куратора группы'; diff --git a/server/src/main/resources/db/migration/V00050__Create__teacher_table.sql b/server/src/main/resources/db/migration/V00050__Create__teacher_table.sql new file mode 100644 index 0000000..8d7df2f --- /dev/null +++ b/server/src/main/resources/db/migration/V00050__Create__teacher_table.sql @@ -0,0 +1,19 @@ +create table teacher +( + id bigserial primary key, + user_id bigint not null, + + created_at timestamptz not null, + updated_at timestamptz +); + +-- FOREIGN KEY +alter table teacher + add constraint fk_teacher_user_id + foreign key (user_id) references "user" (id) + on delete cascade on update cascade; + +-- COMMENTS +comment on table teacher is 'Таблица преподавателей'; + +comment on column teacher.user_id is 'Идентификатор пользователя'; diff --git a/server/src/main/resources/db/migration/V00060__Create__group_table.sql b/server/src/main/resources/db/migration/V00060__Create__group_table.sql new file mode 100644 index 0000000..4246453 --- /dev/null +++ b/server/src/main/resources/db/migration/V00060__Create__group_table.sql @@ -0,0 +1,22 @@ +create table "group" +( + id bigserial primary key, + + name text not null unique, + curator_teacher_id bigint, + + created_at timestamptz not null, + updated_at timestamptz +); + +-- FOREIGN KEY +alter table "group" + add constraint fk_group_curator_teacher_id + foreign key (curator_teacher_id) references teacher (id) + on delete set null on update cascade; + +-- COMMENTS +comment on table "group" is 'Таблица групп студентов'; + +comment on column "group".name is 'Название группы'; +comment on column "group".curator_teacher_id is 'Идентификатор куратора группы'; diff --git a/server/src/main/resources/db/migration/V00060__Create__student_table.sql b/server/src/main/resources/db/migration/V00070__Create__student_table.sql similarity index 82% rename from server/src/main/resources/db/migration/V00060__Create__student_table.sql rename to server/src/main/resources/db/migration/V00070__Create__student_table.sql index cc9bc47..ef2ed84 100644 --- a/server/src/main/resources/db/migration/V00060__Create__student_table.sql +++ b/server/src/main/resources/db/migration/V00070__Create__student_table.sql @@ -1,18 +1,18 @@ create table student ( id bigserial primary key, - user_id bigint not null, - diploma_topic_id bigint not null, - mentor_user_id bigint not null, - group_id bigint not null, + user_id bigint not null, + diploma_topic_id bigint not null, + adviser_teacher_id bigint not null, + group_id bigint not null, form boolean, - protection_day int, - protection_order int, + protection_day int, + protection_order int, magistracy text, digital_format_present boolean, - mark_comment int, - mark_practice int, + mark_comment int, + mark_practice int, predefence_comment text, normal_control text, anti_plagiarism int, @@ -34,8 +34,8 @@ alter table student foreign key (diploma_topic_id) references diploma_topic (id) on delete set null on update cascade; alter table student - add constraint fk_student_mentor_user_id - foreign key (mentor_user_id) references "user" (id) + add constraint fk_student_adviser_teacher_id + foreign key (adviser_teacher_id) references teacher (id) on delete set null on update cascade; alter table student add constraint fk_student_group_id @@ -47,7 +47,7 @@ comment on table student is 'Таблица студентов'; comment on column student.user_id is 'Идентификатор пользователя'; comment on column student.diploma_topic_id is 'Идентификатор темы дипломной работы'; -comment on column student.mentor_user_id is 'Идентификатор научного руководителя'; +comment on column student.adviser_teacher_id is 'Идентификатор научного руководителя'; comment on column student.group_id is 'Идентификатор группы'; comment on column student.form is 'Форма обучения'; diff --git a/web/src/components/NotificationContainer.tsx b/web/src/components/NotificationContainer.tsx index f85f22f..68e8a9b 100644 --- a/web/src/components/NotificationContainer.tsx +++ b/web/src/components/NotificationContainer.tsx @@ -1,7 +1,7 @@ import {ComponentContext} from "../utils/ComponentContext"; import {observer} from "mobx-react"; import {Notification, NotificationType} from "../store/NotificationStore"; -import {Card, CardBody, CardHeader, CardText, CardTitle, Col, Row} from "react-bootstrap"; +import {Card, CardBody, CardHeader, CardText, CardTitle} from "react-bootstrap"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {action, makeObservable} from "mobx"; @@ -14,7 +14,7 @@ export class NotificationContainer extends ComponentContext { } render() { - return
+ return
{this.forEachNotificationRender(this.notificationStore.errors, NotificationType.ERROR)} {this.forEachNotificationRender(this.notificationStore.successes, NotificationType.SUCCESS)} {this.forEachNotificationRender(this.notificationStore.warnings, NotificationType.WARNING)} @@ -51,37 +51,25 @@ class NotificationPopup extends ComponentContext<{ notification: Notification, t render() { const hasTitle = !!this.props.notification.title && this.props.notification.title.length > 0; - const closeIcon = ; + const closeIcon = ; return { hasTitle && - - - - {this.props.notification.title} - - - {closeIcon} - - + + {this.props.notification.title} + {closeIcon} } - - - - {this.props.notification.message} - - { - !hasTitle && - - {closeIcon} - - } - + + {this.props.notification.message} + { + !hasTitle && + closeIcon + } diff --git a/web/src/components/custom/controls/ReactiveControls.tsx b/web/src/components/custom/controls/ReactiveControls.tsx index 14b381d..ee11d73 100644 --- a/web/src/components/custom/controls/ReactiveControls.tsx +++ b/web/src/components/custom/controls/ReactiveControls.tsx @@ -74,7 +74,7 @@ export class PasswordInput extends React.Component> { className={`${this.props.value.invalid ? 'bg-danger' : this.props.value.touched ? 'bg-success' : ''} bg-opacity-10`} onChange={this.onChange} value={this.props.value.value}/> -
diff --git a/web/src/components/group/CreateGroupModal.tsx b/web/src/components/group/CreateGroupModal.tsx new file mode 100644 index 0000000..b48c1c1 --- /dev/null +++ b/web/src/components/group/CreateGroupModal.tsx @@ -0,0 +1,55 @@ +import {ComponentContext} from "../../utils/ComponentContext"; +import {observer} from "mobx-react"; +import {ModalState} from "../../utils/modalState"; +import {action, computed, makeObservable, observable} from "mobx"; +import {ReactiveValue} from "../../utils/reactive/reactiveValue"; +import {required, strLength, strPattern} from "../../utils/reactive/validators"; +import {Button, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle} from "react-bootstrap"; +import {StringInput} from "../custom/controls/ReactiveControls"; +import {post} from "../../utils/request"; + +export interface CreateGroupModalProps { + modalState: ModalState; +} + +@observer +export class CreateGroupModal extends ComponentContext { + @observable name = new ReactiveValue() + .addValidator(required) + .addValidator(strLength(3, 50)) + .addValidator(strPattern(/^[а-яА-ЯёЁ0-9_-]*$/, "Имя группы должно содержать только русские буквы, дефис, нижнее подчеркивание и цифры")); + + constructor(props: any) { + super(props); + makeObservable(this); + } + + @action.bound + creationRequest() { + post('/group/create-group', {name: this.name.value}).then(() => { + this.notificationStore.success(`Группа ${this.name.value} создана`); + }).finally(() => { + this.props.modalState.close(); + }); + } + + @computed + get formInvalid() { + return this.name.invalid || !this.name.touched; + } + + render() { + return + + Создание группы + + + + + + + + + + } +} \ No newline at end of file diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx index 4a0b55a..feb80ed 100644 --- a/web/src/components/layout/Header.tsx +++ b/web/src/components/layout/Header.tsx @@ -6,14 +6,18 @@ import {observer} from "mobx-react"; import {post} from "../../utils/request"; import {LoginModal} from "../user/LoginModal"; import {ModalState} from "../../utils/modalState"; -import {action, makeObservable} from "mobx"; +import {action, makeObservable, observable} from "mobx"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {ComponentContext} from "../../utils/ComponentContext"; +import {CreateGroupModal} from "../group/CreateGroupModal"; +import {UserRegistrationModal} from "../user/UserRegistrationModal"; @observer class Header extends ComponentContext { - loginModalState = new ModalState(); + @observable loginModalState = new ModalState(); + @observable createGroupModalState = new ModalState(); + @observable userRegistrationModalState = new ModalState(); constructor(props: any) { super(props); @@ -37,10 +41,17 @@ class Header extends ComponentContext { user.authenticated && userStore.isAdministrator() && - } + { + user.authenticated && userStore.isAdministrator() && + + + + + }