diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b14df3f..fb4c0ed 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,30 +1,28 @@ -image: ubuntu:18.04 - -cache: - key: "$CI_PROJECT_ID" - paths: - - .gradle/ +image: romanov73/is:ng-tracker-container variables: GRADLE_OPTS: "-Dorg.gradle.daemon=false" before_script: - - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - - apt-get install openjdk-8-jdk git -y + - service postgresql stop + - service postgresql start - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null - mkdir -p ~/.ssh - chmod 700 ~/.ssh + - git log --pretty="%cn;%cd;%s" > src/main/resources/commits.log build: stage: build script: ./gradlew assemble - cache: - key: "$CI_PROJECT_ID" - policy: push - paths: - - build - - .gradle + +checkRun: + stage: test + script: ./gradlew bootRun -Dng-tracker.check-run=true + +checkStyle: + stage: test + script: ./gradlew check -x test deploy: stage: deploy @@ -32,12 +30,6 @@ deploy: - sh deploy/gdccloud/deploy.sh only: - dev - cache: - key: "$CI_PROJECT_ID" - policy: pull - paths: - - build - - .gradle environment: name: staging url: http://193.110.3.124:8080 diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md new file mode 100644 index 0000000..bb1e9f3 --- /dev/null +++ b/.gitlab/issue_templates/feature.md @@ -0,0 +1,46 @@ +## Краткое описание задачи + +Что требуется сделать + + +## `Опционально` Список верстаемых страниц + +Будут затронуты страницы: +* page1.html +* page2.html +* page3.html + +## `Опционально` Список затрагиваемых модулей + +При реализации задачи потребуется также реализовать методы контроллера + + +## `Опционально` Список реализуемых функций + +После выполнения задачи станет доступным: +* просмотр `entity_name` +* редактирование `entity_name` +* валидация `entity_name` + +## `Опционально` Сценарии работы + +Сценарий просмотра: +1. Зайти на главную страницу приложения +2. Перейти в раздел `section_name` +3. Перейти к списку `entity_name` +4. Выбрать нужную `entity_name` и нажать на нее + +Сценарий редактирования: +1. Зайти на главную страницу приложения +2. Перейти в раздел `section_name` +3. Перейти к списку `entity_name` +4. Выбрать нужную `entity_name` и нажать на нее +5. Внести нужные правки в поля и сохранить + +## Описание конечного результата, дающего возможность проверки выполнения задачи: компоненты проекта, сценарии работы + +* Сверстаны страницы page1.hml, page2.hml, page3.hml +* Реализован контроллер для обслуживания страниц +* Сохранение в БД еще не реализовано +* Валидация происходит по полям `field1, field2` +* Сценарий просмотра проверяется при ручном внечении записей в БД diff --git a/README.md b/README.md index ca4db83..f4f6ddd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ 2. Создать новую функцию автоматизированной системы управления задчами - интеллектуальную постановку задач исполнителям. 3. Получить платформу для обкатки научных проектов магистрантов по созданию интеллектуальны систем. 4. Получить проект для обучения бакалавров современым технологиям разработки. +5. Создать систему хранения и трансляции опыта между участниками научной группы. [Демо версия доступна здесь](http://193.110.3.124:8080) diff --git a/build.gradle b/build.gradle index 4e0e9cb..38de2c7 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,10 @@ bootRun.dependsOn checkstyleMain sourceCompatibility = 1.8 targetCompatibility = 1.8 +bootRun { + systemProperties = System.properties +} + checkstyle { project.ext.checkstyleVersion = '8.8' @@ -110,9 +114,6 @@ dependencies { compile group: 'org.liquibase', name: 'liquibase-core', version: '3.5.3' compile group: 'com.mattbertolini', name: 'liquibase-slf4j', version: '2.0.0' - compile group: 'org.apache.poi', name: 'poi', version: '3.9' - compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.9' - compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7' compile group: 'org.webjars', name: 'bootstrap', version: '4.1.0' @@ -121,8 +122,10 @@ dependencies { compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1' compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0' - compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0' - compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0' + compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.6.0' + compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.6.0' testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test' + testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.3.1' + } \ No newline at end of file diff --git a/src/main/java/ru/ulstu/NgTrackerApplication.java b/src/main/java/ru/ulstu/NgTrackerApplication.java index 5e6ee4f..fbe3a2f 100644 --- a/src/main/java/ru/ulstu/NgTrackerApplication.java +++ b/src/main/java/ru/ulstu/NgTrackerApplication.java @@ -2,13 +2,30 @@ package ru.ulstu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import ru.ulstu.configuration.ApplicationProperties; import ru.ulstu.core.repository.JpaDetachableRepositoryImpl; @SpringBootApplication @EnableJpaRepositories(repositoryBaseClass = JpaDetachableRepositoryImpl.class) public class NgTrackerApplication { + private final ApplicationProperties applicationProperties; + + public NgTrackerApplication(ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; + } + public static void main(String[] args) { SpringApplication.run(NgTrackerApplication.class, args); } + + @EventListener(ApplicationReadyEvent.class) + public void doSomethingAfterStartup() { + System.out.println("hello world, I have just started up"); + if (applicationProperties.isCheckRun()) { + System.exit(0); + } + } } diff --git a/src/main/java/ru/ulstu/conference/controller/ConferenceController.java b/src/main/java/ru/ulstu/conference/controller/ConferenceController.java new file mode 100644 index 0000000..823f853 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/controller/ConferenceController.java @@ -0,0 +1,116 @@ +package ru.ulstu.conference.controller; + + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.conference.model.ConferenceDto; +import ru.ulstu.conference.model.ConferenceFilterDto; +import ru.ulstu.conference.service.ConferenceService; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; +import springfox.documentation.annotations.ApiIgnore; + +import javax.validation.Valid; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; +import static ru.ulstu.core.controller.Navigation.CONFERENCES_PAGE; +import static ru.ulstu.core.controller.Navigation.CONFERENCE_PAGE; +import static ru.ulstu.core.controller.Navigation.REDIRECT_TO; + + +@Controller() +@RequestMapping(value = "/conferences") +@ApiIgnore +public class ConferenceController { + + private final ConferenceService conferenceService; + + public ConferenceController(ConferenceService conferenceService) { + this.conferenceService = conferenceService; + } + + @GetMapping("/conferences") + public void getConferences(ModelMap modelMap) { + modelMap.put("filteredConferences", new ConferenceFilterDto(conferenceService.findAllDto())); + } + + @GetMapping("/conference") + public void getConference(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + ConferenceDto conferenceDto = conferenceService.findOneDto(id); + conferenceDto.setNotSelectedPapers(getNotSelectPapers(conferenceDto.getPaperIds())); + modelMap.put("conferenceDto", conferenceDto); + } else { + ConferenceDto conferenceDto = new ConferenceDto(); + conferenceDto.setNotSelectedPapers(getNotSelectPapers(new ArrayList())); + modelMap.put("conferenceDto", conferenceDto); + } + } + + @PostMapping(value = "/conference", params = "save") + public String save(@Valid ConferenceDto conferenceDto, Errors errors) throws IOException { + filterEmptyDeadlines(conferenceDto); + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.save(conferenceDto); + return String.format(REDIRECT_TO, CONFERENCES_PAGE); + + } + + @GetMapping("/delete/{conference-id}") + public String delete(@PathVariable("conference-id") Integer conferenceId) throws IOException { + conferenceService.delete(conferenceId); + return String.format(REDIRECT_TO, CONFERENCES_PAGE); + } + + @PostMapping(value = "/conference", params = "addDeadline") + public String addDeadline(@Valid ConferenceDto conferenceDto, Errors errors) { + filterEmptyDeadlines(conferenceDto); + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceDto.getDeadlines().add(new Deadline()); + return CONFERENCE_PAGE; + } + + @PostMapping(value = "/conference", params = "removeDeadline") + public String removeDeadline(@Valid ConferenceDto conferenceDto, Errors errors, + @RequestParam(value = "removeDeadline") Integer deadlineIndex) throws IOException { + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.removeDeadline(conferenceDto, deadlineIndex); + return CONFERENCE_PAGE; + } + + @PostMapping(value = "/conference", params = "removePaper") + public String removePaper(@Valid ConferenceDto conferenceDto, Errors errors, + @RequestParam(value = "removePaper") Integer paperIndex) throws IOException { + if (errors.hasErrors()) { + return CONFERENCE_PAGE; + } + conferenceService.removePaper(conferenceDto, paperIndex); + return CONFERENCE_PAGE; + } + + public List getNotSelectPapers(List paperIds) { + return conferenceService.getConferencePapers(paperIds); + } + + private void filterEmptyDeadlines(ConferenceDto conferenceDto) { + conferenceDto.setDeadlines(conferenceDto.getDeadlines().stream() + .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/ru/ulstu/conference/model/Conference.java b/src/main/java/ru/ulstu/conference/model/Conference.java new file mode 100644 index 0000000..b6affcd --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/Conference.java @@ -0,0 +1,142 @@ +package ru.ulstu.conference.model; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.format.annotation.DateTimeFormat; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; +import ru.ulstu.user.model.User; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Entity +@Table(name = "conference") +public class Conference extends BaseEntity { + + @NotBlank + private String title; + + private String description; + + private String url; + + private int ping; + + @Column(name = "begin_date") + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date beginDate; + + @Column(name = "end_date") + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date endDate; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "conference_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + @OrderBy("date") + private List deadlines = new ArrayList<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "paper_conference", + joinColumns = {@JoinColumn(name = "conference_id")}, + inverseJoinColumns = {@JoinColumn(name = "paper_id")}) + private List papers = new ArrayList<>(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "users_conference", + joinColumns = {@JoinColumn(name = "conference_id")}, + inverseJoinColumns = {@JoinColumn(name = "users_id")}) + private Set users = new HashSet<>(); + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getPing() { + return ping; + } + + public void setPing(int ping) { + this.ping = ping; + } + + public Date getBeginDate() { + return beginDate; + } + + public void setBeginDate(Date beginDate) { + this.beginDate = beginDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public List getPapers() { + return papers; + } + + public void setPapers(List papers) { + this.papers = papers; + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } +} diff --git a/src/main/java/ru/ulstu/conference/model/ConferenceDto.java b/src/main/java/ru/ulstu/conference/model/ConferenceDto.java new file mode 100644 index 0000000..ec9ec92 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/ConferenceDto.java @@ -0,0 +1,218 @@ +package ru.ulstu.conference.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.format.annotation.DateTimeFormat; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; +import ru.ulstu.user.model.UserDto; + +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.validation.constraints.Size; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static ru.ulstu.core.util.StreamApiUtils.convert; + +public class ConferenceDto { + + private Integer id; + @NotEmpty + @Size(min = 2, max = 400) + private String title; + @Size(max = 500) + private String description = ""; + @Size(max = 255) + private String url = ""; + private int ping = 0; + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date beginDate = new Date(); + @Temporal(TemporalType.TIMESTAMP) + @DateTimeFormat(pattern = "yyyy-MM-dd") + private Date endDate = new Date(); + private List deadlines = new ArrayList<>(); + private List removedDeadlineIds = new ArrayList<>(); + private Set userIds = new HashSet<>(); + private List paperIds = new ArrayList<>(); + private List papers = new ArrayList<>(); + + + private List notSelectedPapers = new ArrayList<>(); + private Set users = new HashSet<>(); + private Integer filterUserId; + + public ConferenceDto() { + } + + @JsonCreator + public ConferenceDto(@JsonProperty("id") Integer id, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("url") String url, + @JsonProperty("ping") Integer ping, + @JsonProperty("beginDate") Date beginDate, + @JsonProperty("endDate") Date endDate, + @JsonProperty("deadlines") List deadlines, + @JsonProperty("userIds") Set userIds, + @JsonProperty("paperIds") List paperIds, + @JsonProperty("users") Set users, + @JsonProperty("papers") List papers, + @JsonProperty("notSelectedPapers") List notSelectedPapers) { + this.id = id; + this.title = title; + this.description = description; + this.url = url; + this.ping = ping; + this.beginDate = beginDate; + this.endDate = endDate; + this.deadlines = deadlines; + this.userIds = userIds; + this.paperIds = paperIds; + this.users = users; + this.papers = papers; + this.notSelectedPapers = notSelectedPapers; + } + + public ConferenceDto(Conference conference) { + this.id = conference.getId(); + this.title = conference.getTitle(); + this.description = conference.getDescription(); + this.url = conference.getUrl(); + this.ping = conference.getPing(); + this.beginDate = conference.getBeginDate(); + this.endDate = conference.getEndDate(); + this.deadlines = conference.getDeadlines(); + this.userIds = convert(conference.getUsers(), user -> user.getId()); + this.paperIds = convert(conference.getPapers(), paper -> paper.getId()); + this.users = convert(conference.getUsers(), UserDto::new); + this.papers = conference.getPapers(); + + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getPing() { + return ping; + } + + public void setPing(int ping) { + this.ping = ping; + } + + public Date getBeginDate() { + return beginDate; + } + + public void setBeginDate(Date beginDate) { + this.beginDate = beginDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public Set getUserIds() { + return userIds; + } + + public void setUserIds(Set userIds) { + this.userIds = userIds; + } + + public List getPaperIds() { + return paperIds; + } + + public void setPaperIds(List paperIds) { + this.paperIds = paperIds; + } + + public List getPapers() { + return papers; + } + + public void setPapers(List papers) { + this.papers = papers; + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + + public Integer getFilterUserId() { + return filterUserId; + } + + public void setFilterUserId(Integer filterUserId) { + this.filterUserId = filterUserId; + } + + public List getRemovedDeadlineIds() { + return removedDeadlineIds; + } + + public void setRemovedDeadlineIds(List removedDeadlineIds) { + this.removedDeadlineIds = removedDeadlineIds; + } + + public List getNotSelectedPapers() { + return notSelectedPapers; + } + + public void setNotSelectedPapers(List notSelectedPapers) { + this.notSelectedPapers = notSelectedPapers; + } + +} diff --git a/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java b/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java new file mode 100644 index 0000000..37878b2 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/model/ConferenceFilterDto.java @@ -0,0 +1,47 @@ +package ru.ulstu.conference.model; + +import java.util.List; + +public class ConferenceFilterDto { + + private List conferences; + private Integer filterAuthorId; + private Integer year; + + public ConferenceFilterDto() { + } + + public ConferenceFilterDto(List conferenceDtos, Integer filterAuthorId, Integer year) { + this.conferences = conferenceDtos; + this.filterAuthorId = filterAuthorId; + this.year = year; + } + + public ConferenceFilterDto(List conferenceDtos) { + this(conferenceDtos, null, null); + } + + public List getConferences() { + return conferences; + } + + public void setConferences(List conferences) { + this.conferences = conferences; + } + + public Integer getFilterAuthorId() { + return filterAuthorId; + } + + public void setFilterAuthorId(Integer filterAuthorId) { + this.filterAuthorId = filterAuthorId; + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer year) { + this.year = year; + } +} diff --git a/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java b/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java new file mode 100644 index 0000000..4325633 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/repository/ConferenceRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.conference.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.conference.model.Conference; + +public interface ConferenceRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java b/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java new file mode 100644 index 0000000..ff9f69a --- /dev/null +++ b/src/main/java/ru/ulstu/conference/service/ConferenceNotificationService.java @@ -0,0 +1,7 @@ +package ru.ulstu.conference.service; + +import org.springframework.stereotype.Service; + +@Service +public class ConferenceNotificationService { +} diff --git a/src/main/java/ru/ulstu/conference/service/ConferenceService.java b/src/main/java/ru/ulstu/conference/service/ConferenceService.java new file mode 100644 index 0000000..4d267d1 --- /dev/null +++ b/src/main/java/ru/ulstu/conference/service/ConferenceService.java @@ -0,0 +1,112 @@ +package ru.ulstu.conference.service; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.conference.model.Conference; +import ru.ulstu.conference.model.ConferenceDto; +import ru.ulstu.conference.repository.ConferenceRepository; +import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.paper.model.Paper; +import ru.ulstu.paper.service.PaperService; + +import java.io.IOException; +import java.util.List; + +import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; + +@Service +public class ConferenceService { + private final static int MAX_DISPLAY_SIZE = 40; + + private final ConferenceRepository conferenceRepository; + private final DeadlineService deadlineService; + private final PaperService paperService; + + public ConferenceService(ConferenceRepository conferenceRepository, + DeadlineService deadlineService, + PaperService paperService) { + this.conferenceRepository = conferenceRepository; + this.deadlineService = deadlineService; + this.paperService = paperService; + } + + public List findAll() { + return conferenceRepository.findAll(); + } + + public List findAllDto() { + List conferences = convert(findAll(), ConferenceDto::new); + conferences.forEach(conferenceDto -> conferenceDto.setTitle(StringUtils.abbreviate(conferenceDto.getTitle(), MAX_DISPLAY_SIZE))); + return conferences; + } + + public ConferenceDto findOneDto(Integer id) { + return new ConferenceDto(conferenceRepository.findOne(id)); + } + + public void save(ConferenceDto conferenceDto) throws IOException { + if (isEmpty(conferenceDto.getId())) { + create(conferenceDto); + } else { + update(conferenceDto); + } + } + + @Transactional + public Integer create(ConferenceDto conferenceDto) throws IOException { + Conference newConference = copyFromDto(new Conference(), conferenceDto); + newConference = conferenceRepository.save(newConference); + return newConference.getId(); + } + + @Transactional + public Integer update(ConferenceDto conferenceDto) throws IOException { + Conference conference = conferenceRepository.findOne(conferenceDto.getId()); + conferenceRepository.save(copyFromDto(conference, conferenceDto)); + conferenceDto.getRemovedDeadlineIds().forEach(deadlineService::remove); + return conference.getId(); + } + + @Transactional + public void delete(Integer conferenceId) { + if (conferenceRepository.exists(conferenceId)) { + conferenceRepository.delete(conferenceId); + } + } + + public void removeDeadline(ConferenceDto conferenceDto, Integer deadlineIndex) throws IOException { + if (conferenceDto.getDeadlines().get(deadlineIndex).getId() != null) { + conferenceDto.getRemovedDeadlineIds().add(conferenceDto.getDeadlines().get(deadlineIndex).getId()); + } + conferenceDto.getDeadlines().remove((int) deadlineIndex); + } + + public void removePaper(ConferenceDto conferenceDto, Integer paperIndex) throws IOException { + Paper removedPaper = conferenceDto.getPapers().remove((int) paperIndex); + conferenceDto.getNotSelectedPapers().add(removedPaper); + } + + public List getConferencePapers(List paperIds) { + return paperService.findAllNotSelect(paperIds); + } + + private Conference copyFromDto(Conference conference, ConferenceDto conferenceDto) throws IOException { + conference.setTitle(conferenceDto.getTitle()); + conference.setDescription(conferenceDto.getDescription()); + conference.setUrl(conferenceDto.getUrl()); + conference.setPing(0); + conference.setBeginDate(conferenceDto.getBeginDate()); + conference.setEndDate(conferenceDto.getEndDate()); + conference.setPapers(conferenceDto.getPapers()); + conference.setDeadlines(deadlineService.saveOrCreate(conferenceDto.getDeadlines())); + if (conferenceDto.getPaperIds() != null && !conferenceDto.getPaperIds().isEmpty()) { + conferenceDto.getPaperIds().forEach(paperId -> + conference.getPapers().add(paperService.findEntityById(paperId))); + } + return conference; + } + + +} diff --git a/src/main/java/ru/ulstu/configuration/ApplicationProperties.java b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java index 8615cb2..f75f53f 100644 --- a/src/main/java/ru/ulstu/configuration/ApplicationProperties.java +++ b/src/main/java/ru/ulstu/configuration/ApplicationProperties.java @@ -11,12 +11,16 @@ import org.springframework.validation.annotation.Validated; public class ApplicationProperties { @NotBlank private String baseUrl; + @NotBlank private String undeadUserLogin; + private boolean devMode; private boolean useHttps; + private boolean checkRun; + public boolean isUseHttps() { return useHttps; } @@ -48,4 +52,12 @@ public class ApplicationProperties { public void setDevMode(boolean devMode) { this.devMode = devMode; } + + public boolean isCheckRun() { + return checkRun; + } + + public void setCheckRun(boolean checkRun) { + this.checkRun = checkRun; + } } diff --git a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java index 3e8d66f..496d493 100644 --- a/src/main/java/ru/ulstu/configuration/MvcConfiguration.java +++ b/src/main/java/ru/ulstu/configuration/MvcConfiguration.java @@ -10,10 +10,11 @@ public class MvcConfiguration extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{articlename:\\w+}"); - //registry.addViewController("/admin/{articlename:\\w+}"); + registry.addViewController("/admin/{articlename:\\w+}"); registry.addViewController("/papers/{articlename:\\w+}"); registry.addViewController("/grants/{articlename:\\w+}"); registry.addViewController("/conferences/{articlename:\\w+}"); + registry.addViewController("/students/{articlename:\\w+}"); registry.addRedirectViewController("/", "/index"); registry.addRedirectViewController("/default", "/index"); } diff --git a/src/main/java/ru/ulstu/core/controller/AdviceController.java b/src/main/java/ru/ulstu/core/controller/AdviceController.java index 1c71df1..8238797 100644 --- a/src/main/java/ru/ulstu/core/controller/AdviceController.java +++ b/src/main/java/ru/ulstu/core/controller/AdviceController.java @@ -20,7 +20,6 @@ import ru.ulstu.user.error.UserNotActivatedException; import ru.ulstu.user.error.UserNotFoundException; import ru.ulstu.user.error.UserPasswordsNotValidOrNotMatchException; import ru.ulstu.user.error.UserResetKeyError; -import ru.ulstu.user.model.User; import ru.ulstu.user.service.UserService; import java.util.Set; @@ -28,7 +27,6 @@ import java.util.stream.Collectors; @ControllerAdvice public class AdviceController { - private final static String USER_NAME_TEMPLATE = "%s %s %s"; private final Logger log = LoggerFactory.getLogger(AdviceController.class); private final UserService userService; @@ -38,11 +36,7 @@ public class AdviceController { @ModelAttribute("currentUser") public String getCurrentUser() { - User user = userService.getCurrentUser(); - return String.format(USER_NAME_TEMPLATE, - user.getLastName(), - user.getFirstName().substring(0, 1), - user.getPatronymic().substring(0, 1)); + return userService.getCurrentUser().getUserAbbreviate(); } private Response handleException(ErrorConstants error) { diff --git a/src/main/java/ru/ulstu/grant/controller/Navigation.java b/src/main/java/ru/ulstu/core/controller/Navigation.java similarity index 69% rename from src/main/java/ru/ulstu/grant/controller/Navigation.java rename to src/main/java/ru/ulstu/core/controller/Navigation.java index bf1626f..caea429 100644 --- a/src/main/java/ru/ulstu/grant/controller/Navigation.java +++ b/src/main/java/ru/ulstu/core/controller/Navigation.java @@ -1,4 +1,4 @@ -package ru.ulstu.grant.controller; +package ru.ulstu.core.controller; import org.springframework.validation.Errors; @@ -7,6 +7,9 @@ public class Navigation { public static final String GRANTS_PAGE = "/grants/grants"; public static final String GRANT_PAGE = "/grants/grant"; + public static final String CONFERENCES_PAGE = "/conferences/conferences"; + public static final String CONFERENCE_PAGE = "/conferences/conference"; + public static String hasErrors(Errors errors, String page) { if (errors.hasErrors()) { return page; diff --git a/src/main/java/ru/ulstu/core/error/XlsLoadException.java b/src/main/java/ru/ulstu/core/error/XlsLoadException.java deleted file mode 100644 index 372731f..0000000 --- a/src/main/java/ru/ulstu/core/error/XlsLoadException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.ulstu.core.error; - -public class XlsLoadException extends Exception { - public XlsLoadException(String s) { - super(s); - } -} diff --git a/src/main/java/ru/ulstu/core/error/XlsParseException.java b/src/main/java/ru/ulstu/core/error/XlsParseException.java deleted file mode 100644 index fe8c4b3..0000000 --- a/src/main/java/ru/ulstu/core/error/XlsParseException.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.ulstu.core.error; - -public class XlsParseException extends Exception { - public XlsParseException(String s) { - super(s); - } -} diff --git a/src/main/java/ru/ulstu/core/service/TreeService.java b/src/main/java/ru/ulstu/core/service/TreeService.java deleted file mode 100644 index 1e3d4a0..0000000 --- a/src/main/java/ru/ulstu/core/service/TreeService.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.ulstu.core.service; - -import org.springframework.stereotype.Service; -import ru.ulstu.core.model.TreeDto; -import ru.ulstu.core.model.TreeEntity; - -import java.util.List; -import java.util.function.Predicate; - -@Service -public class TreeService { - public TreeDto getTree(String rootName, List rootItems) { - return addChildNode(new TreeDto(rootName), rootItems, element -> true); - } - - public TreeDto getTree(String rootName, List rootItems, Predicate filterPredicate) { - return addChildNode(new TreeDto(rootName), rootItems, filterPredicate); - } - - private TreeDto addChildNode(TreeDto currentRoot, List children, Predicate filterPredicate) { - if (children != null) { - children.stream() - .filter(filterPredicate) - .forEach(item -> { - TreeDto newNode = new TreeDto(item); - currentRoot.getChildren().add(addChildNode(newNode, item.getChildren(), filterPredicate)); - }); - } - return currentRoot; - } -} diff --git a/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java b/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java deleted file mode 100644 index d70d86e..0000000 --- a/src/main/java/ru/ulstu/core/service/XlsDocumentBuilder.java +++ /dev/null @@ -1,207 +0,0 @@ -package ru.ulstu.core.service; - -import org.apache.poi.POIXMLDocument; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.ss.util.RegionUtil; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import ru.ulstu.core.error.XlsParseException; - -import java.io.*; - -public class XlsDocumentBuilder { - private static final int DEFAULT_SHEET_NUM = 0; - private File documentFile; - private Workbook workBook; - private Sheet currentSheet; - - /** - * Constructor for reading and writing data from/to *.[xls|xlsx] document - * - * @param file contains existing document for reading or new document to save - */ - public XlsDocumentBuilder(File file) throws IOException, XlsParseException { - this.documentFile = file; - if (file.exists()) { - workBook = getWorkBook(file); - currentSheet = workBook.getSheetAt(DEFAULT_SHEET_NUM); - } else { - workBook = new XSSFWorkbook(); - currentSheet = workBook.createSheet(); - } - } - - private Workbook getWorkBook(File file) throws XlsParseException, IOException { - InputStream inputStream = new PushbackInputStream(new FileInputStream(file), 4096); - if (isXlsx(inputStream)) { - return new XSSFWorkbook(inputStream); - } else if (isExcel(inputStream)) { - return new HSSFWorkbook(inputStream); - } - throw new XlsParseException("Wrong document format"); - } - - /** - * Change active sheet to write or read data - * - * @param index index of sheet to activate - */ - public XlsDocumentBuilder setActiveSheet(int index) { - workBook.setActiveSheet(index); - currentSheet = workBook.getSheetAt(index); - return this; - } - - /** - * Create new sheet in document and set it active - * - * @param sheetName - */ - public XlsDocumentBuilder insertNewSheet(String sheetName) { - currentSheet = workBook.createSheet(sheetName); - workBook.setActiveSheet(getSheetCount() - 1); - return this; - } - - public XlsDocumentBuilder insertNewSheet(String sheetName, int order) { - insertNewSheet(sheetName); - workBook.setSheetOrder(sheetName, order); - return this; - } - - /** - * Returns number of sheet in document - * - * @return sheets count - */ - public int getSheetCount() { - return workBook.getNumberOfSheets(); - } - - /** - * Returns number of rows in sheet - * - * @return rows count - */ - public int getRowCount() { - return currentSheet.getLastRowNum(); - } - - /** - * Returns number of columns in sheet - * - * @return columns count - */ - public int getColumnCount() { - Row row = currentSheet.getRow(getRowCount()); - if (row == null) { - return 0; - } - return row.getLastCellNum() - 1; - } - - /** - * Returns converted to string representation of cell value - * - * @param rowIndex row index of current sheet - * @param colIndex column index of current sheet - * @return string value of cell - */ - public String getCellAsString(int rowIndex, int colIndex) { - Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex); - cell.setCellType(Cell.CELL_TYPE_STRING); - return cell.getStringCellValue(); - } - - /** - * Sets string cell value - * - * @param rowIndex row index of current sheet - * @param colIndex column index of current sheet - */ - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, String value) { - if (currentSheet.getRow(rowIndex) == null) { - currentSheet.createRow(rowIndex); - } - if (currentSheet.getRow(rowIndex).getCell(colIndex) == null) { - currentSheet.getRow(rowIndex).createCell(colIndex); - } - Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex); - cell.setCellValue(value); - setColumnAutosize(colIndex, colIndex); - return this; - } - - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, int value) { - return setCellValue(rowIndex, colIndex, String.valueOf(value)); - } - - public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, long value) { - return setCellValue(rowIndex, colIndex, String.valueOf(value)); - } - - /** - * Save current file - */ - public XlsDocumentBuilder save() throws IOException { - OutputStream out = new FileOutputStream(documentFile); - workBook.write(out); - return this; - } - - private boolean isExcel(InputStream i) throws IOException { - return (POIFSFileSystem.hasPOIFSHeader(i) || POIXMLDocument.hasOOXMLHeader(i)); - } - - private boolean isXlsx(InputStream i) throws IOException { - return POIXMLDocument.hasOOXMLHeader(i); - } - - public int getActiveSheetIndex() { - return workBook.getActiveSheetIndex(); - } - - public XlsDocumentBuilder mergeCells(int rowFrom, int rowTo, int colFrom, int colTo) { - currentSheet.addMergedRegion(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo)); - return this; - } - - public void setRegionBorderWithMedium(int rowFrom, int rowTo, int colFrom, int colTo) { - for (int row = rowFrom; row < rowTo; row++) { - for (int col = colFrom; col <= colTo; col++) { - CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, col, col); - RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderRight(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - RegionUtil.setBorderTop(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook); - } - } - } - - public XlsDocumentBuilder setColumnAutosize(int from, int to) { - for (int col = from; col <= to; col++) { - currentSheet.autoSizeColumn(col, true); - } - return this; - } - - public XlsDocumentBuilder setRowAutosize(int from, int to) { - CellStyle style = workBook.createCellStyle(); - style.setWrapText(true); - for (int row = from; row <= to; row++) { - for (int col = 0; col <= currentSheet.getRow(row).getLastCellNum(); col++) { - if (currentSheet.getRow(row).getCell(col) != null) { - currentSheet.getRow(row).getCell(col).setCellStyle(style); - } - } - } - return this; - } - - public XlsDocumentBuilder deleteSheet(int index) { - workBook.removeSheetAt(index); - return this; - } -} diff --git a/src/main/java/ru/ulstu/deadline/service/DeadlineService.java b/src/main/java/ru/ulstu/deadline/service/DeadlineService.java index 2180025..0ef8a4f 100644 --- a/src/main/java/ru/ulstu/deadline/service/DeadlineService.java +++ b/src/main/java/ru/ulstu/deadline/service/DeadlineService.java @@ -41,4 +41,9 @@ public class DeadlineService { newDeadline = deadlineRepository.save(newDeadline); return newDeadline; } + + @Transactional + public void remove(Integer deadlineId) { + deadlineRepository.delete(deadlineId); + } } diff --git a/src/main/java/ru/ulstu/grant/controller/GrantController.java b/src/main/java/ru/ulstu/grant/controller/GrantController.java index c786ffa..a7dc948 100644 --- a/src/main/java/ru/ulstu/grant/controller/GrantController.java +++ b/src/main/java/ru/ulstu/grant/controller/GrantController.java @@ -13,6 +13,7 @@ import ru.ulstu.deadline.model.Deadline; import ru.ulstu.grant.model.Grant; import ru.ulstu.grant.model.GrantDto; import ru.ulstu.grant.service.GrantService; +import springfox.documentation.annotations.ApiIgnore; import javax.validation.Valid; import java.io.IOException; @@ -20,14 +21,15 @@ import java.util.List; import java.util.stream.Collectors; import static org.springframework.util.StringUtils.isEmpty; -import static ru.ulstu.grant.controller.Navigation.GRANTS_PAGE; -import static ru.ulstu.grant.controller.Navigation.GRANT_PAGE; -import static ru.ulstu.grant.controller.Navigation.REDIRECT_TO; -import static ru.ulstu.grant.controller.Navigation.hasErrors; +import static ru.ulstu.core.controller.Navigation.GRANTS_PAGE; +import static ru.ulstu.core.controller.Navigation.GRANT_PAGE; +import static ru.ulstu.core.controller.Navigation.REDIRECT_TO; +import static ru.ulstu.core.controller.Navigation.hasErrors; @Controller() @RequestMapping(value = "/grants") +@ApiIgnore public class GrantController { private final GrantService grantService; diff --git a/src/main/java/ru/ulstu/index/controller/IndexController.java b/src/main/java/ru/ulstu/index/controller/IndexController.java index d964997..f6ab100 100644 --- a/src/main/java/ru/ulstu/index/controller/IndexController.java +++ b/src/main/java/ru/ulstu/index/controller/IndexController.java @@ -6,9 +6,11 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import ru.ulstu.core.controller.AdviceController; import ru.ulstu.user.service.UserService; +import springfox.documentation.annotations.ApiIgnore; @Controller() @RequestMapping(value = "/index") +@ApiIgnore public class IndexController extends AdviceController { public IndexController(UserService userService) { super(userService); diff --git a/src/main/java/ru/ulstu/paper/controller/PaperController.java b/src/main/java/ru/ulstu/paper/controller/PaperController.java index e0dec98..ad30822 100644 --- a/src/main/java/ru/ulstu/paper/controller/PaperController.java +++ b/src/main/java/ru/ulstu/paper/controller/PaperController.java @@ -19,6 +19,7 @@ import ru.ulstu.paper.model.PaperFilterDto; import ru.ulstu.paper.service.LatexService; import ru.ulstu.paper.service.PaperService; import ru.ulstu.user.model.User; +import springfox.documentation.annotations.ApiIgnore; import javax.validation.Valid; import java.io.IOException; @@ -34,6 +35,7 @@ import static org.springframework.util.StringUtils.isEmpty; @Controller() @RequestMapping(value = "/papers") +@ApiIgnore public class PaperController { private final PaperService paperService; private final LatexService latexService; @@ -57,7 +59,7 @@ public class PaperController { @GetMapping("/dashboard") public void getDashboard(ModelMap modelMap) { - modelMap.put("papers", paperService.findAllActive()); + modelMap.put("papers", paperService.findAllActiveDto()); } @GetMapping("/paper") diff --git a/src/main/java/ru/ulstu/paper/controller/PaperRestController.java b/src/main/java/ru/ulstu/paper/controller/PaperRestController.java index 5dd6b9f..2bd8384 100644 --- a/src/main/java/ru/ulstu/paper/controller/PaperRestController.java +++ b/src/main/java/ru/ulstu/paper/controller/PaperRestController.java @@ -61,4 +61,9 @@ public class PaperRestController { public Response> filter(@RequestBody @Valid PaperFilterDto paperFilterDto) throws IOException { return new Response<>(paperService.filter(paperFilterDto)); } + + @GetMapping("formatted-list") + public Response> getFormattedPaperList() { + return new Response<>(paperService.getFormattedPaperList()); + } } diff --git a/src/main/java/ru/ulstu/paper/model/Paper.java b/src/main/java/ru/ulstu/paper/model/Paper.java index 31d106c..c7f7fcf 100644 --- a/src/main/java/ru/ulstu/paper/model/Paper.java +++ b/src/main/java/ru/ulstu/paper/model/Paper.java @@ -7,6 +7,7 @@ import ru.ulstu.core.model.BaseEntity; import ru.ulstu.core.model.UserContainer; import ru.ulstu.deadline.model.Deadline; import ru.ulstu.file.model.FileData; +import ru.ulstu.timeline.model.Event; import ru.ulstu.user.model.User; import javax.persistence.CascadeType; @@ -29,7 +30,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; - @Entity public class Paper extends BaseEntity implements UserContainer { public enum PaperStatus { @@ -75,8 +75,14 @@ public class Paper extends BaseEntity implements UserContainer { private String comment; + private String url; + private Boolean locked = false; + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn(name = "paper_id") + private List events = new ArrayList<>(); + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "paper_id", unique = true) @Fetch(FetchMode.SUBSELECT) @@ -160,6 +166,22 @@ public class Paper extends BaseEntity implements UserContainer { this.authors = authors; } + public List getEvents() { + return events; + } + + public void setEvents(List events) { + this.events = events; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + public String getLatexText() { return latexText; } diff --git a/src/main/java/ru/ulstu/paper/model/PaperDto.java b/src/main/java/ru/ulstu/paper/model/PaperDto.java index f33d207..7ddc0e0 100644 --- a/src/main/java/ru/ulstu/paper/model/PaperDto.java +++ b/src/main/java/ru/ulstu/paper/model/PaperDto.java @@ -30,6 +30,7 @@ public class PaperDto { @NotEmpty private List deadlines = new ArrayList<>(); private String comment; + private String url; private Boolean locked; private List files = new ArrayList<>(); private Set authorIds; @@ -50,6 +51,7 @@ public class PaperDto { @JsonProperty("deadlines") List deadlines, @JsonProperty("comment") String comment, @JsonProperty("latex_text") String latexText, + @JsonProperty("url") String url, @JsonProperty("locked") Boolean locked, @JsonProperty("files") List files, @JsonProperty("authorIds") Set authorIds, @@ -61,6 +63,7 @@ public class PaperDto { this.updateDate = updateDate; this.deadlines = deadlines; this.comment = comment; + this.url = url; this.latexText = latexText; this.locked = locked; this.files = files; @@ -75,6 +78,7 @@ public class PaperDto { this.updateDate = paper.getUpdateDate(); this.deadlines = paper.getDeadlines(); this.comment = paper.getComment(); + this.url = paper.getUrl(); this.latexText = paper.getLatexText(); this.locked = paper.getLocked(); this.files = convert(paper.getFiles(), FileDataDto::new); @@ -170,6 +174,14 @@ public class PaperDto { this.authorIds = authorIds; } + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + public String getLatexText() { return latexText; } diff --git a/src/main/java/ru/ulstu/paper/repository/PaperRepository.java b/src/main/java/ru/ulstu/paper/repository/PaperRepository.java index 3951f53..343e9e2 100644 --- a/src/main/java/ru/ulstu/paper/repository/PaperRepository.java +++ b/src/main/java/ru/ulstu/paper/repository/PaperRepository.java @@ -12,4 +12,6 @@ public interface PaperRepository extends JpaRepository { @Query("SELECT p FROM Paper p WHERE (:author IS NULL OR :author MEMBER OF p.authors) AND (YEAR(p.createDate) = :year OR :year IS NULL)") List filter(@Param("author") User author, @Param("year") Integer year); + + List findByIdNotIn(List paperIds); } diff --git a/src/main/java/ru/ulstu/paper/service/PaperService.java b/src/main/java/ru/ulstu/paper/service/PaperService.java index 926a7d7..a10d941 100644 --- a/src/main/java/ru/ulstu/paper/service/PaperService.java +++ b/src/main/java/ru/ulstu/paper/service/PaperService.java @@ -11,6 +11,7 @@ import ru.ulstu.paper.model.Paper; import ru.ulstu.paper.model.PaperDto; import ru.ulstu.paper.model.PaperFilterDto; import ru.ulstu.paper.repository.PaperRepository; +import ru.ulstu.timeline.service.EventService; import ru.ulstu.user.model.User; import ru.ulstu.user.service.UserService; @@ -20,6 +21,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static org.springframework.util.ObjectUtils.isEmpty; @@ -33,23 +35,27 @@ import static ru.ulstu.paper.model.Paper.PaperStatus.ON_PREPARATION; @Service public class PaperService { private final static int MAX_DISPLAY_SIZE = 40; + private final static String PAPER_FORMATTED_TEMPLATE = "%s %s"; private final PaperNotificationService paperNotificationService; private final PaperRepository paperRepository; private final UserService userService; private final DeadlineService deadlineService; private final FileService fileService; + private final EventService eventService; public PaperService(PaperRepository paperRepository, FileService fileService, PaperNotificationService paperNotificationService, UserService userService, - DeadlineService deadlineService) { + DeadlineService deadlineService, + EventService eventService) { this.paperRepository = paperRepository; this.fileService = fileService; this.paperNotificationService = paperNotificationService; this.userService = userService; this.deadlineService = deadlineService; + this.eventService = eventService; } public List findAll() { @@ -62,13 +68,17 @@ public class PaperService { return papers; } - public List findAllActive() { - return findAllDto() + public List findAllActive() { + return findAll() .stream() .filter(paper -> paper.getStatus() != COMPLETED && paper.getStatus() != FAILED) .collect(toList()); } + public List findAllActiveDto() { + return convert(findAllActive(), PaperDto::new); + } + public PaperDto findOneDto(Integer id) { return new PaperDto(paperRepository.findOne(id)); } @@ -78,11 +88,13 @@ public class PaperService { Paper newPaper = copyFromDto(new Paper(), paperDto); newPaper = paperRepository.save(newPaper); paperNotificationService.sendCreateNotification(newPaper); + eventService.createFromPaper(newPaper); return newPaper.getId(); } private Paper copyFromDto(Paper paper, PaperDto paperDto) throws IOException { paper.setComment(paperDto.getComment()); + paper.setUrl(paperDto.getUrl()); paper.setLatexText(paperDto.getLatexText()); paper.setCreateDate(paper.getCreateDate() == null ? new Date() : paper.getCreateDate()); paper.setLocked(paperDto.getLocked()); @@ -111,8 +123,8 @@ public class PaperService { .collect(toList())) { fileService.delete(file.getId()); } - paperRepository.save(copyFromDto(paper, paperDto)); + eventService.updatePaperDeadlines(paper); paper.getAuthors().forEach(author -> { if (!oldAuthors.contains(author)) { @@ -128,7 +140,7 @@ public class PaperService { } @Transactional - public void delete(Integer paperId) throws IOException { + public void delete(Integer paperId) { Paper paper = paperRepository.findOne(paperId); paperRepository.delete(paper); } @@ -149,6 +161,7 @@ public class PaperService { paper = paperRepository.save(paper); paperNotificationService.sendCreateNotification(paper); + eventService.createFromPaper(paper); return paper; } @@ -203,7 +216,41 @@ public class PaperService { return new PaperDto(paperRepository.findOne(paperId)); } + public Paper findEntityById(Integer paperId) { + return paperRepository.findOne(paperId); + } + + public List findAllNotSelect(List paperIds) { + if (!paperIds.isEmpty()) { + return sortPapers(paperRepository.findByIdNotIn(paperIds)); + } else { + return sortPapers(paperRepository.findAll()); + } + + } + public List getPaperAuthors() { return userService.findAll(); } + + public List getFormattedPaperList() { + return findAllCompleted() + .stream() + .map(paper -> String.format(PAPER_FORMATTED_TEMPLATE, paper.getTitle(), getAuthors(paper))) + .collect(toList()); + } + + private List findAllCompleted() { + return findAll() + .stream() + .filter(paper -> paper.getStatus() == COMPLETED) + .collect(toList()); + } + + private String getAuthors(Paper paper) { + return paper.getAuthors() + .stream() + .map(User::getUserAbbreviate) + .collect(Collectors.joining(", ")); + } } diff --git a/src/main/java/ru/ulstu/project/controller/ProjectController.java b/src/main/java/ru/ulstu/project/controller/ProjectController.java new file mode 100644 index 0000000..3244ccb --- /dev/null +++ b/src/main/java/ru/ulstu/project/controller/ProjectController.java @@ -0,0 +1,49 @@ +package ru.ulstu.project.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import ru.ulstu.project.model.Project; +import ru.ulstu.project.model.ProjectDto; +import ru.ulstu.project.service.ProjectService; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; + +@Controller() +@RequestMapping(value = "/projects") +@ApiIgnore +public class ProjectController { + private final ProjectService projectService; + + public ProjectController(ProjectService projectService) { + this.projectService = projectService; + } + + @GetMapping("/dashboard") + public void getDashboard(ModelMap modelMap) { + modelMap.put("projects", projectService.findAllDto()); + } + + @GetMapping("/projects") + public void getProjects(ModelMap modelMap) { + modelMap.put("projects", projectService.findAllDto()); + } + + @GetMapping("/project") + public void getProject(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + modelMap.put("projectDto", projectService.findOneDto(id)); + } else { + modelMap.put("projectDto", new ProjectDto()); + } + } + + @ModelAttribute("allStatuses") + public List getProjectStatuses() { + return projectService.getProjectStatuses(); + } +} diff --git a/src/main/java/ru/ulstu/project/model/Project.java b/src/main/java/ru/ulstu/project/model/Project.java index 06722a1..2e0e86c 100644 --- a/src/main/java/ru/ulstu/project/model/Project.java +++ b/src/main/java/ru/ulstu/project/model/Project.java @@ -3,24 +3,60 @@ package ru.ulstu.project.model; import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.grant.model.Grant; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; @Entity public class Project extends BaseEntity { + public enum ProjectStatus { + APPLICATION("Заявка"), + ON_COMPETITION("Отправлен на конкурс"), + SUCCESSFUL_PASSAGE("Успешное прохождение"), + IN_WORK("В работе"), + COMPLETED("Завершен"), + FAILED("Провалены сроки"); + + private String statusName; + + ProjectStatus(String statusName) { + this.statusName = statusName; + } + + public String getStatusName() { + return statusName; + } + } @NotBlank private String title; + @Enumerated(value = EnumType.STRING) + private ProjectStatus status = ProjectStatus.APPLICATION; + + @NotNull + private String description; + @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "project_id") private List deadlines = new ArrayList<>(); + @ManyToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "grant_id") + private Grant grant; + + @NotNull + private String repository; + public String getTitle() { return title; } @@ -29,6 +65,38 @@ public class Project extends BaseEntity { this.title = title; } + public ProjectStatus getStatus() { + return status; + } + + public void setStatus(ProjectStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Grant getGrant() { + return grant; + } + + public void setGrant(Grant grant) { + this.grant = grant; + } + + public String getRepository() { + return repository; + } + + public void setRepository(String repository) { + this.repository = repository; + } + public List getDeadlines() { return deadlines; } diff --git a/src/main/java/ru/ulstu/project/model/ProjectDto.java b/src/main/java/ru/ulstu/project/model/ProjectDto.java index db7fc75..6c83d59 100644 --- a/src/main/java/ru/ulstu/project/model/ProjectDto.java +++ b/src/main/java/ru/ulstu/project/model/ProjectDto.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.hibernate.validator.constraints.NotEmpty; import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.grant.model.GrantDto; import java.util.ArrayList; import java.util.List; @@ -13,8 +14,11 @@ public class ProjectDto { @NotEmpty private String title; - + private Project.ProjectStatus status; + private String description; private List deadlines = new ArrayList<>(); + private GrantDto grant; + private String repository; public ProjectDto() { } @@ -26,9 +30,17 @@ public class ProjectDto { @JsonCreator public ProjectDto(@JsonProperty("id") Integer id, @JsonProperty("title") String title, + @JsonProperty("status") Project.ProjectStatus status, + @JsonProperty("description") String description, + @JsonProperty("grant") GrantDto grant, + @JsonProperty("repository") String repository, @JsonProperty("deadlines") List deadlines) { this.id = id; this.title = title; + this.status = status; + this.description = description; + this.grant = grant; + this.repository = repository; this.deadlines = deadlines; } @@ -36,6 +48,10 @@ public class ProjectDto { public ProjectDto(Project project) { this.id = project.getId(); this.title = project.getTitle(); + this.status = project.getStatus(); + this.description = project.getDescription(); + this.grant = project.getGrant() == null ? null : new GrantDto(project.getGrant()); + this.repository = project.getRepository(); this.deadlines = project.getDeadlines(); } @@ -55,6 +71,38 @@ public class ProjectDto { this.title = title; } + public Project.ProjectStatus getStatus() { + return status; + } + + public void setStatus(Project.ProjectStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public GrantDto getGrant() { + return grant; + } + + public void setGrant(GrantDto grant) { + this.grant = grant; + } + + public String getRepository() { + return repository; + } + + public void setRepository(String repository) { + this.repository = repository; + } + public List getDeadlines() { return deadlines; } diff --git a/src/main/java/ru/ulstu/project/service/ProjectService.java b/src/main/java/ru/ulstu/project/service/ProjectService.java index b54a60a..99d8919 100644 --- a/src/main/java/ru/ulstu/project/service/ProjectService.java +++ b/src/main/java/ru/ulstu/project/service/ProjectService.java @@ -2,17 +2,21 @@ package ru.ulstu.project.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.util.StringUtils; import ru.ulstu.deadline.service.DeadlineService; import ru.ulstu.project.model.Project; import ru.ulstu.project.model.ProjectDto; import ru.ulstu.project.repository.ProjectRepository; +import java.util.Arrays; import java.util.List; import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; @Service public class ProjectService { + private final static int MAX_DISPLAY_SIZE = 40; private final ProjectRepository projectRepository; private final DeadlineService deadlineService; @@ -27,6 +31,20 @@ public class ProjectService { return projectRepository.findAll(); } + public List findAllDto() { + List projects = convert(findAll(), ProjectDto::new); + projects.forEach(projectDto -> projectDto.setTitle(StringUtils.abbreviate(projectDto.getTitle(), MAX_DISPLAY_SIZE))); + return projects; + } + + public ProjectDto findOneDto(Integer id) { + return new ProjectDto(projectRepository.findOne(id)); + } + + public List getProjectStatuses() { + return Arrays.asList(Project.ProjectStatus.values()); + } + @Transactional public Project create(ProjectDto projectDto) { Project newProject = copyFromDto(new Project(), projectDto); diff --git a/src/main/java/ru/ulstu/students/controller/Navigation.java b/src/main/java/ru/ulstu/students/controller/Navigation.java new file mode 100644 index 0000000..3804a8b --- /dev/null +++ b/src/main/java/ru/ulstu/students/controller/Navigation.java @@ -0,0 +1,16 @@ +package ru.ulstu.students.controller; + +import org.springframework.validation.Errors; + +public class Navigation { + public static final String REDIRECT_TO = "redirect:%s"; + public static final String TASKS_PAGE = "/students/tasks"; + public static final String TASK_PAGE = "/students/task"; + + public static String hasErrors(Errors errors, String page) { + if (errors.hasErrors()) { + return page; + } + return null; + } +} diff --git a/src/main/java/ru/ulstu/students/controller/TaskController.java b/src/main/java/ru/ulstu/students/controller/TaskController.java new file mode 100644 index 0000000..8fd9575 --- /dev/null +++ b/src/main/java/ru/ulstu/students/controller/TaskController.java @@ -0,0 +1,84 @@ +package ru.ulstu.students.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.*; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.students.model.Task; +import ru.ulstu.students.model.TaskDto; +import ru.ulstu.students.service.TaskService; +import springfox.documentation.annotations.ApiIgnore; + +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; +import static ru.ulstu.students.controller.Navigation.*; + +@Controller() +@RequestMapping(value = "/students") +@ApiIgnore +public class TaskController { + + private final TaskService taskService; + + public TaskController(TaskService taskService) { + this.taskService = taskService; + } + + @GetMapping("/tasks") + public void getTasks(ModelMap modelMap) { + modelMap.put("tasks", taskService.findAllDto()); + } + + @GetMapping("/dashboard") + public void getDashboard(ModelMap modelMap) { + modelMap.put("tasks", taskService.findAllDto()); + } + + @GetMapping("/task") + public void getTask(ModelMap modelMap, @RequestParam(value = "id") Integer id) { + if (id != null && id > 0) { + modelMap.put("taskDto", taskService.findOneDto(id)); + } else { + modelMap.put("taskDto", new TaskDto()); + } + } + + @PostMapping(value = "/task", params = "save") + public String save(@Valid TaskDto taskDto, Errors errors) throws IOException { + filterEmptyDeadlines(taskDto); + if (taskDto.getDeadlines().isEmpty()) { + errors.rejectValue("deadlines", "errorCode", "Не может быть пустым"); + } + if (errors.hasErrors()) { + return TASK_PAGE; + } + taskService.save(taskDto); + return String.format(REDIRECT_TO, TASKS_PAGE); + } + + @PostMapping(value = "/task", params = "addDeadline") + public String addDeadline(@Valid TaskDto taskDto, Errors errors) { + filterEmptyDeadlines(taskDto); + if (errors.hasErrors()) { + return TASK_PAGE; + } + taskDto.getDeadlines().add(new Deadline()); + return TASK_PAGE; + } + + @ModelAttribute("allStatuses") + public List getTaskStatuses() { + return taskService.getTaskStatuses(); + } + + private void filterEmptyDeadlines(TaskDto taskDto) { + taskDto.setDeadlines(taskDto.getDeadlines().stream() + .filter(dto -> dto.getDate() != null || !isEmpty(dto.getDescription())) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/ru/ulstu/students/model/Task.java b/src/main/java/ru/ulstu/students/model/Task.java new file mode 100644 index 0000000..be1b1c6 --- /dev/null +++ b/src/main/java/ru/ulstu/students/model/Task.java @@ -0,0 +1,118 @@ +package ru.ulstu.students.model; + +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.validator.constraints.NotBlank; +import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.tags.model.Tag; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@Entity +public class Task extends BaseEntity { + + public enum TaskStatus { + IN_WORK("В работе"), + COMPLETED("Завершен"), + FAILED("Провалены сроки"), + LOADED_FROM_KIAS("Загружен автоматически"); + + private String statusName; + + TaskStatus(String name) { + this.statusName = name; + } + + public String getStatusName() { + return statusName; + } + } + + @NotBlank + private String title; + + private String description; + + @Enumerated(value = EnumType.STRING) + private ru.ulstu.students.model.Task.TaskStatus status = TaskStatus.IN_WORK; + + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "task_id", unique = true) + @Fetch(FetchMode.SUBSELECT) + @OrderBy("date") + private List deadlines = new ArrayList<>(); + + @Column(name = "create_date") + @Temporal(TemporalType.TIMESTAMP) + private Date createDate = new Date(); + + @Column(name = "update_date") + @Temporal(TemporalType.TIMESTAMP) + private Date updateDate = new Date(); + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "task_tags", + joinColumns = {@JoinColumn(name = "task_id")}, + inverseJoinColumns = {@JoinColumn(name = "tag_id")}) + private List tags = new ArrayList<>(); + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public TaskStatus getStatus() { + return status; + } + + public void setStatus(TaskStatus status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public Date getUpdateDate() { + return updateDate; + } + + public void setUpdateDate(Date updateDate) { + this.updateDate = updateDate; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } +} diff --git a/src/main/java/ru/ulstu/students/model/TaskDto.java b/src/main/java/ru/ulstu/students/model/TaskDto.java new file mode 100644 index 0000000..941a6df --- /dev/null +++ b/src/main/java/ru/ulstu/students/model/TaskDto.java @@ -0,0 +1,144 @@ +package ru.ulstu.students.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.validator.constraints.NotEmpty; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.tags.model.Tag; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class TaskDto { + + private final static int MAX_TAGS_LENGTH = 50; + + private Integer id; + @NotEmpty + private String title; + private String description; + private Task.TaskStatus status; + private List deadlines = new ArrayList<>(); + private Date createDate; + private Date updateDate; + private Set tagIds; + private List tags; + + public TaskDto() { + deadlines.add(new Deadline()); + } + + @JsonCreator + public TaskDto(@JsonProperty("id") Integer id, + @JsonProperty("title") String title, + @JsonProperty("description") String description, + @JsonProperty("createDate") Date createDate, + @JsonProperty("updateDate") Date updateDate, + @JsonProperty("status") Task.TaskStatus status, + @JsonProperty("deadlines") List deadlines, + @JsonProperty("tagIds") Set tagIds, + @JsonProperty("tags") List tags) { + this.id = id; + this.title = title; + this.status = status; + this.deadlines = deadlines; + this.createDate = createDate; + this.updateDate = updateDate; + this.description = description; + this.tags = tags; + } + + public TaskDto(Task task) { + this.id = task.getId(); + this.title = task.getTitle(); + this.status = task.getStatus(); + this.deadlines = task.getDeadlines(); + this.createDate = task.getCreateDate(); + this.updateDate = task.getUpdateDate(); + this.description = task.getDescription(); + this.tags = task.getTags(); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Task.TaskStatus getStatus() { + return status; + } + + public void setStatus(Task.TaskStatus status) { + this.status = status; + } + + public List getDeadlines() { + return deadlines; + } + + public void setDeadlines(List deadlines) { + this.deadlines = deadlines; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public Date getUpdateDate() { + return updateDate; + } + + public void setUpdateDate(Date updateDate) { + this.updateDate = updateDate; + } + + public Set getTagIds() { + return tagIds; + } + + public void setTagIds(Set tagIds) { + this.tagIds = tagIds; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public String getTagsString() { + return StringUtils.abbreviate(tags + .stream() + .map(tag -> tag.getTagName()) + .collect(Collectors.joining(", ")), MAX_TAGS_LENGTH); + } +} diff --git a/src/main/java/ru/ulstu/students/repository/TaskRepository.java b/src/main/java/ru/ulstu/students/repository/TaskRepository.java new file mode 100644 index 0000000..6f48d1f --- /dev/null +++ b/src/main/java/ru/ulstu/students/repository/TaskRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.students.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.students.model.Task; + +public interface TaskRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/students/service/TaskService.java b/src/main/java/ru/ulstu/students/service/TaskService.java new file mode 100644 index 0000000..7b67a44 --- /dev/null +++ b/src/main/java/ru/ulstu/students/service/TaskService.java @@ -0,0 +1,95 @@ +package ru.ulstu.students.service; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.deadline.service.DeadlineService; +import ru.ulstu.students.model.Task; +import ru.ulstu.students.model.TaskDto; +import ru.ulstu.students.repository.TaskRepository; +import ru.ulstu.tags.service.TagService; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import static org.springframework.util.ObjectUtils.isEmpty; +import static ru.ulstu.core.util.StreamApiUtils.convert; +import static ru.ulstu.students.model.Task.TaskStatus.IN_WORK; + +@Service +public class TaskService { + + private final static int MAX_DISPLAY_SIZE = 40; + + private final TaskRepository taskRepository; + private final DeadlineService deadlineService; + private final TagService tagService; + + public TaskService(TaskRepository grantRepository, + DeadlineService deadlineService, TagService tagService) { + this.taskRepository = grantRepository; + this.deadlineService = deadlineService; + this.tagService = tagService; + } + + public List findAll() { + return taskRepository.findAll(); + } + + public List findAllDto() { + List tasks = convert(findAll(), TaskDto::new); + tasks.forEach(taskDto -> taskDto.setTitle(StringUtils.abbreviate(taskDto.getTitle(), MAX_DISPLAY_SIZE))); + return tasks; + } + + public TaskDto findOneDto(Integer id) { + return new TaskDto(taskRepository.findOne(id)); + } + + @Transactional + public Integer create(TaskDto taskDto) throws IOException { + Task newTask = copyFromDto(new Task(), taskDto); + newTask = taskRepository.save(newTask); + return newTask.getId(); + } + + private Task copyFromDto(Task task, TaskDto taskDto) throws IOException { + task.setTitle(taskDto.getTitle()); + task.setDescription(taskDto.getDescription()); + task.setStatus(taskDto.getStatus() == null ? IN_WORK : taskDto.getStatus()); + task.setDeadlines(deadlineService.saveOrCreate(taskDto.getDeadlines())); + task.setCreateDate(task.getCreateDate() == null ? new Date() : task.getCreateDate()); + task.setUpdateDate(new Date()); + task.getTags().clear(); + task.setTags(tagService.saveOrCreate(taskDto.getTags())); + return task; + } + + @Transactional + public Integer update(TaskDto taskDto) throws IOException { + Task task = taskRepository.findOne(taskDto.getId()); + taskRepository.save(copyFromDto(task, taskDto)); + return task.getId(); + } + + @Transactional + public void delete(Integer taskId) throws IOException { + Task task = taskRepository.findOne(taskId); + taskRepository.delete(task); + } + + public void save(TaskDto taskDto) throws IOException { + if (isEmpty(taskDto.getId())) { + create(taskDto); + } else { + update(taskDto); + } + } + + public List getTaskStatuses() { + return Arrays.asList(Task.TaskStatus.values()); + } + +} diff --git a/src/main/java/ru/ulstu/tags/model/Tag.java b/src/main/java/ru/ulstu/tags/model/Tag.java new file mode 100644 index 0000000..aed000f --- /dev/null +++ b/src/main/java/ru/ulstu/tags/model/Tag.java @@ -0,0 +1,44 @@ +package ru.ulstu.tags.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hibernate.validator.constraints.NotEmpty; +import ru.ulstu.core.model.BaseEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.validation.constraints.Size; + +@Entity +@Table(name = "tag") +public class Tag extends BaseEntity { + + @NotEmpty + @Size(max = 50) + @Column(name = "tag_name") + private String tagName; + + public Tag() { + + } + + @JsonCreator + public Tag(@JsonProperty("id") Integer id, + @JsonProperty("tag_name") String tagName) { + this.setId(id); + this.tagName = tagName; + } + + public Tag(String name) { + this.tagName = name; + } + + public String getTagName() { + return tagName; + } + + public void setTagName(String tagName) { + this.tagName = tagName; + } +} diff --git a/src/main/java/ru/ulstu/tags/repository/TagRepository.java b/src/main/java/ru/ulstu/tags/repository/TagRepository.java new file mode 100644 index 0000000..213abae --- /dev/null +++ b/src/main/java/ru/ulstu/tags/repository/TagRepository.java @@ -0,0 +1,12 @@ +package ru.ulstu.tags.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import ru.ulstu.tags.model.Tag; + +public interface TagRepository extends JpaRepository { + + @Query("SELECT t FROM Tag t WHERE (t.tagName = :tagName)") + Tag findByName(@Param("tagName") String tagName); +} diff --git a/src/main/java/ru/ulstu/tags/service/TagService.java b/src/main/java/ru/ulstu/tags/service/TagService.java new file mode 100644 index 0000000..4e88304 --- /dev/null +++ b/src/main/java/ru/ulstu/tags/service/TagService.java @@ -0,0 +1,53 @@ +package ru.ulstu.tags.service; + +import org.springframework.stereotype.Service; +import ru.ulstu.tags.model.Tag; +import ru.ulstu.tags.repository.TagRepository; + +import javax.transaction.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class TagService { + + private final TagRepository tagRepository; + + + public TagService(TagRepository tagRepository) { + + this.tagRepository = tagRepository; + } + + public List saveOrCreate(List tags) { + return tags + .stream() + .map(tag -> { + if (tag.getId() != null) { + return getExistById(tag); + } else { + Tag existTag = isExistByName(tag.getTagName()); + return existTag != null ? existTag : create(tag); + } + }).collect(Collectors.toList()); + } + + @Transactional + public Tag getExistById(Tag tag) { + return tagRepository.findOne(tag.getId()); + } + + @Transactional + public Tag isExistByName(String tagName) { + return tagRepository.findByName(tagName); + } + + @Transactional + public Tag create(Tag tag) { + Tag newTag = new Tag(); + newTag.setTagName(tag.getTagName()); + newTag = tagRepository.save(newTag); + return newTag; + } + +} diff --git a/src/main/java/ru/ulstu/timeline/model/Event.java b/src/main/java/ru/ulstu/timeline/model/Event.java index 290f136..ef0a4b8 100644 --- a/src/main/java/ru/ulstu/timeline/model/Event.java +++ b/src/main/java/ru/ulstu/timeline/model/Event.java @@ -2,6 +2,7 @@ package ru.ulstu.timeline.model; import org.hibernate.validator.constraints.NotBlank; import ru.ulstu.core.model.BaseEntity; +import ru.ulstu.paper.model.Paper; import ru.ulstu.user.model.User; import javax.persistence.CascadeType; @@ -71,6 +72,10 @@ public class Event extends BaseEntity { @JoinColumn(name = "child_id") private List parents; + @ManyToOne + @JoinColumn(name = "paper_id") + private Paper paper; + public String getTitle() { return title; } @@ -150,4 +155,12 @@ public class Event extends BaseEntity { public void setParents(List parents) { this.parents = parents; } + + public Paper getPaper() { + return paper; + } + + public void setPaper(Paper paper) { + this.paper = paper; + } } diff --git a/src/main/java/ru/ulstu/timeline/model/EventDto.java b/src/main/java/ru/ulstu/timeline/model/EventDto.java index 9e70a9d..6a4a90b 100644 --- a/src/main/java/ru/ulstu/timeline/model/EventDto.java +++ b/src/main/java/ru/ulstu/timeline/model/EventDto.java @@ -3,6 +3,7 @@ package ru.ulstu.timeline.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.hibernate.validator.constraints.NotBlank; +import ru.ulstu.paper.model.PaperDto; import ru.ulstu.user.model.UserDto; import javax.validation.constraints.NotNull; @@ -23,6 +24,7 @@ public class EventDto { private final Date updateDate; private final String description; private final List recipients; + private PaperDto paperDto; @JsonCreator public EventDto(@JsonProperty("id") Integer id, @@ -33,6 +35,7 @@ public class EventDto { @JsonProperty("createDate") Date createDate, @JsonProperty("updateDate") Date updateDate, @JsonProperty("description") String description, + @JsonProperty("paperDto") PaperDto paperDto, @JsonProperty("recipients") List recipients) { this.id = id; this.title = title; @@ -43,6 +46,7 @@ public class EventDto { this.updateDate = updateDate; this.description = description; this.recipients = recipients; + this.paperDto = paperDto; } public EventDto(Event event) { @@ -54,6 +58,7 @@ public class EventDto { this.createDate = event.getCreateDate(); this.updateDate = event.getUpdateDate(); this.description = event.getDescription(); + this.paperDto = new PaperDto(event.getPaper()); this.recipients = convert(event.getRecipients(), UserDto::new); } @@ -92,4 +97,12 @@ public class EventDto { public Date getExecuteDate() { return executeDate; } + + public PaperDto getPaperDto() { + return paperDto; + } + + public void setPaperDto(PaperDto paperDto) { + this.paperDto = paperDto; + } } diff --git a/src/main/java/ru/ulstu/timeline/model/Timeline.java b/src/main/java/ru/ulstu/timeline/model/Timeline.java index 8e588cb..06e04bc 100644 --- a/src/main/java/ru/ulstu/timeline/model/Timeline.java +++ b/src/main/java/ru/ulstu/timeline/model/Timeline.java @@ -6,6 +6,7 @@ import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; @Entity @@ -13,7 +14,7 @@ public class Timeline extends BaseEntity { @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "timeline_id") - private List events; + private List events = new ArrayList<>(); public List getEvents() { return events; diff --git a/src/main/java/ru/ulstu/timeline/repository/EventRepository.java b/src/main/java/ru/ulstu/timeline/repository/EventRepository.java index 4848e12..eb5c08b 100644 --- a/src/main/java/ru/ulstu/timeline/repository/EventRepository.java +++ b/src/main/java/ru/ulstu/timeline/repository/EventRepository.java @@ -2,6 +2,7 @@ package ru.ulstu.timeline.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import ru.ulstu.paper.model.Paper; import ru.ulstu.timeline.model.Event; import java.util.List; @@ -12,4 +13,6 @@ public interface EventRepository extends JpaRepository { @Query("SELECT e FROM Event e WHERE e.executeDate > CURRENT_DATE ORDER BY e.executeDate") List findAllFuture(); + + List findAllByPaper(Paper paper); } diff --git a/src/main/java/ru/ulstu/timeline/service/EventScheduler.java b/src/main/java/ru/ulstu/timeline/service/EventScheduler.java index 6df7fcb..7899aca 100644 --- a/src/main/java/ru/ulstu/timeline/service/EventScheduler.java +++ b/src/main/java/ru/ulstu/timeline/service/EventScheduler.java @@ -34,6 +34,10 @@ public class EventScheduler { Map variables = ImmutableMap.of("description", event.getDescription()); event.getRecipients() .forEach(recipient -> mailService.sendEmailFromTemplate(variables, recipient, "eventNotification", event.getTitle())); + if (event.getPeriod() == null) { + event.setStatus(Event.EventStatus.COMPLETED); + eventService.save(event); + } }); } diff --git a/src/main/java/ru/ulstu/timeline/service/EventService.java b/src/main/java/ru/ulstu/timeline/service/EventService.java index ca95b9a..2d2b83d 100644 --- a/src/main/java/ru/ulstu/timeline/service/EventService.java +++ b/src/main/java/ru/ulstu/timeline/service/EventService.java @@ -1,15 +1,22 @@ package ru.ulstu.timeline.service; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import ru.ulstu.deadline.model.Deadline; +import ru.ulstu.paper.model.Paper; import ru.ulstu.timeline.model.Event; import ru.ulstu.timeline.model.EventDto; +import ru.ulstu.timeline.model.Timeline; import ru.ulstu.timeline.repository.EventRepository; import ru.ulstu.user.model.UserDto; import ru.ulstu.user.service.UserService; +import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static ru.ulstu.core.util.StreamApiUtils.convert; @@ -17,11 +24,14 @@ import static ru.ulstu.core.util.StreamApiUtils.convert; public class EventService { private final EventRepository eventRepository; + private final TimelineService timelineService; private final UserService userService; public EventService(EventRepository eventRepository, + @Lazy TimelineService timelineService, UserService userService) { this.eventRepository = eventRepository; + this.timelineService = timelineService; this.userService = userService; } @@ -56,6 +66,11 @@ public class EventService { return eventRepository.save(copyFromDto(event, eventDto)).getId(); } + @Transactional + public Event save(Event event) { + return eventRepository.save(event); + } + @Transactional public void delete(Integer eventId) { Event event = eventRepository.findOne(eventId); @@ -84,6 +99,36 @@ public class EventService { eventRepository.save(parentEvent); } + public void createFromPaper(Paper newPaper) { + List timelines = timelineService.findAll(); + Timeline timeline = timelines.isEmpty() ? new Timeline() : timelines.get(0); + + for (Deadline deadline : newPaper.getDeadlines() + .stream() + .filter(d -> d.getDate().after(new Date()) || DateUtils.isSameDay(d.getDate(), new Date())) + .collect(Collectors.toList())) { + Event newEvent = new Event(); + newEvent.setTitle("Дедлайн статьи"); + newEvent.setStatus(Event.EventStatus.NEW); + newEvent.setExecuteDate(deadline.getDate()); + newEvent.setCreateDate(new Date()); + newEvent.setUpdateDate(new Date()); + newEvent.setDescription("Дедлайн '" + deadline.getDescription() + "' cтатьи '" + newPaper.getTitle() + "'"); + newEvent.setRecipients(new ArrayList(newPaper.getAuthors())); + newEvent.setPaper(newPaper); + eventRepository.save(newEvent); + + timeline.getEvents().add(newEvent); + timelineService.save(timeline); + } + } + + public void updatePaperDeadlines(Paper paper) { + eventRepository.delete(eventRepository.findAllByPaper(paper)); + + createFromPaper(paper); + } + public List findByCurrentDate() { return eventRepository.findByCurrentDate(); } diff --git a/src/main/java/ru/ulstu/timeline/service/TimelineService.java b/src/main/java/ru/ulstu/timeline/service/TimelineService.java index 94388ee..09f90c0 100644 --- a/src/main/java/ru/ulstu/timeline/service/TimelineService.java +++ b/src/main/java/ru/ulstu/timeline/service/TimelineService.java @@ -23,15 +23,24 @@ public class TimelineService { this.eventService = eventService; } - public List findAll() { + public List findAllDto() { return convert(timelineRepository.findAll(), TimelineDto::new); } + public List findAll() { + return timelineRepository.findAll(); + } + @Transactional public int create(TimelineDto timelineDto) { return timelineRepository.save(copyFromDto(new Timeline(), timelineDto)).getId(); } + @Transactional + public Timeline save(Timeline timeline) { + return timelineRepository.save(timeline); + } + private Timeline copyFromDto(Timeline timeline, TimelineDto timelineDto) { timeline.setEvents(eventService.findByIds(convert(timelineDto.getEvents(), EventDto::getId))); return timeline; diff --git a/src/main/java/ru/ulstu/user/model/User.java b/src/main/java/ru/ulstu/user/model/User.java index de9f028..06f407b 100644 --- a/src/main/java/ru/ulstu/user/model/User.java +++ b/src/main/java/ru/ulstu/user/model/User.java @@ -24,6 +24,8 @@ import java.util.Set; @Entity @Table(name = "users") public class User extends BaseEntity { + private final static String USER_ABBREVIATE_TEMPLATE = "%s %s%s"; + @NotNull @Pattern(regexp = Constants.LOGIN_REGEX) @Size(min = 1, max = 50) @@ -186,4 +188,11 @@ public class User extends BaseEntity { public void setPatronymic(String patronymic) { this.patronymic = patronymic; } + + public String getUserAbbreviate() { + return String.format(USER_ABBREVIATE_TEMPLATE, + lastName == null ? "" : lastName, + firstName == null ? "" : firstName.substring(0, 1) + ".", + patronymic == null ? "" : patronymic.substring(0, 1) + "."); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fb1b116..038ddcf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -35,4 +35,5 @@ liquibase.change-log=classpath:db/changelog-master.xml ng-tracker.base-url=http://127.0.0.1:8080 ng-tracker.undead-user-login=admin ng-tracker.dev-mode=true -ng-tracker.use-https=false \ No newline at end of file +ng-tracker.use-https=false +ng-tracker.check-run=false \ No newline at end of file diff --git a/src/main/resources/commits.log b/src/main/resources/commits.log index cb97503..5d1bca7 100644 --- a/src/main/resources/commits.log +++ b/src/main/resources/commits.log @@ -1,489 +1,298 @@ -Anton Romanov;Thu Mar 15 11:10:34 2018 +0400;change to date time -romanov73;Thu Mar 15 11:03:19 2018 +0400;read commits in constructor -romanov73;Thu Mar 15 09:12:07 2018 +0400;add commits page -Romanov Anton;Wed Mar 14 20:26:54 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:16:53 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:08:09 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 20:00:58 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:50:22 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:34:17 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 19:20:42 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:59:18 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:47:17 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:35:37 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:33:54 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:29:27 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:21:18 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:19:27 2018 +0000;Update README.md -Romanov Anton;Wed Mar 14 18:16:06 2018 +0000;Update README.md -romanov73;Wed Mar 14 21:28:10 2018 +0400;fix using constant -Aleksey Filippov;Wed Mar 14 20:07:25 2018 +0400;some fixes after merge -Aleksey Filippov;Wed Mar 14 19:52:45 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into odin-ui -Aleksey Filippov;Wed Mar 14 19:48:45 2018 +0400;refactoring of odin paginator -romanov73;Wed Mar 14 18:43:16 2018 +0400;Merge branch 'develop' into 36-rest -romanov73;Wed Mar 14 18:17:02 2018 +0400;Merge branch 'develop' into 36-rest -Aleksey Filippov;Wed Mar 14 18:11:00 2018 +0400;fix null value check in formatter -Aleksey Filippov;Wed Mar 14 18:06:16 2018 +0400;fix odin paginator style, fix odin negative file -romanov73;Wed Mar 14 18:04:47 2018 +0400;rename entities -Aleksey Filippov;Wed Mar 14 17:58:59 2018 +0400;improve odin table look -romanov73;Wed Mar 14 17:47:45 2018 +0400;fix db changelogs -romanov73;Wed Mar 14 17:47:20 2018 +0400;fix db changelogs -Gleb;Wed Mar 14 08:50:23 2018 +0000;Merge branch '48-' into 'develop' -funny73;Wed Mar 14 12:28:55 2018 +0400;Поправил отображение подразделения при редактировании смены -Aleksey Filippov;Wed Mar 14 00:02:24 2018 +0400;move navbar to the left, move odin css to separate file -Aleksey Filippov;Tue Mar 13 23:26:33 2018 +0400;some style fixes -Aleksey Filippov;Tue Mar 13 23:22:37 2018 +0400;move version panel to navbar -funny73;Tue Mar 13 20:41:41 2018 +0400;Поправил валидацию сменности -romanov73;Tue Mar 13 17:26:54 2018 +0400;fix offsetable page request -romanov73;Tue Mar 13 17:26:02 2018 +0400;fix table constructor -romanov73;Tue Mar 13 17:24:56 2018 +0400;fix checking empty value -Romanov Anton;Tue Mar 13 13:09:41 2018 +0000;Merge branch 'odin-ui' into '36-rest' -Aleksey Filippov;Tue Mar 13 16:59:27 2018 +0400;notes updated -Aleksey Filippov;Tue Mar 13 16:56:00 2018 +0400;odin refactoring -Aleksey Filippov;Tue Mar 13 16:53:38 2018 +0400;odin refactoring -romanov73;Tue Mar 13 15:27:17 2018 +0400;fix tool load calc -romanov73;Tue Mar 13 14:46:46 2018 +0400;fix area load calc -Aleksey Filippov;Tue Mar 13 13:38:23 2018 +0400;some fixes -romanov73;Tue Mar 13 13:33:25 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop -romanov73;Tue Mar 13 13:33:07 2018 +0400;fix tools count calc -Aleksey Filippov;Tue Mar 13 13:12:09 2018 +0400;some odin refactoring -Aleksey Filippov;Tue Mar 13 13:11:41 2018 +0400;some offsetablepagerequest fix -Aleksey Filippov;Tue Mar 13 13:06:48 2018 +0400;add offsetablepagerequest -Aleksey Filippov;Tue Mar 13 07:27:12 2018 +0000;Merge branch '51-rest-points' into '36-rest' -romanov73;Tue Mar 13 11:22:02 2018 +0400;fix by comment: remove default version id -romanov73;Tue Mar 13 00:04:50 2018 +0400;fix path -romanov73;Mon Mar 12 23:55:05 2018 +0400;add tool square dictionary -romanov73;Mon Mar 12 23:36:49 2018 +0400;add dictionary pages -romanov73;Mon Mar 12 23:18:01 2018 +0400;sort second level menu items by name -romanov73;Mon Mar 12 23:11:22 2018 +0400;fix menu -romanov73;Mon Mar 12 22:52:15 2018 +0400;add stream api utils and converter for "map ... collect" -funny73;Mon Mar 12 20:01:47 2018 +0400;Добавил свойство базового изделия для изделия -romanov73;Mon Mar 12 19:46:52 2018 +0400;refactor units -romanov73;Mon Mar 12 19:36:57 2018 +0400;refactor tool types and work types -romanov73;Mon Mar 12 19:20:23 2018 +0400;refactor tools -romanov73;Mon Mar 12 19:07:09 2018 +0400;refactor stages -romanov73;Mon Mar 12 18:53:26 2018 +0400;refactor positions -romanov73;Mon Mar 12 18:27:36 2018 +0400;refactor employees -romanov73;Mon Mar 12 18:26:49 2018 +0400;refactor employees -romanov73;Mon Mar 12 18:09:38 2018 +0400;refactor categories -Aleksey Filippov;Mon Mar 12 17:32:06 2018 +0400;todo added -Aleksey Filippov;Mon Mar 12 17:29:51 2018 +0400;paginator added to odin -Aleksey Filippov;Mon Mar 12 17:29:06 2018 +0400;notes updated -funny73;Mon Mar 12 16:29:13 2018 +0400;Добавил сущность изделие без привязки к производственной программе -funny73;Mon Mar 12 15:16:35 2018 +0400;Переименовал сущность Product на ProductOnProgram Ещё немного переименования -funny73;Mon Mar 12 14:39:52 2018 +0400;Переименовал сущность Product на ProductOnProgram -Aleksey Filippov;Mon Mar 12 13:44:42 2018 +0400;add formatters and initial form support to odin -Aleksey Filippov;Mon Mar 12 13:44:16 2018 +0400;userlistdto refactoring -Aleksey Filippov;Mon Mar 12 13:43:59 2018 +0400;dateutils improvements -Aleksey Filippov;Mon Mar 12 13:43:43 2018 +0400;add support of localdatetime type to odin -Aleksey Filippov;Mon Mar 12 13:42:54 2018 +0400;odin backend example added -Aleksey Filippov;Mon Mar 12 13:42:12 2018 +0400;notes updated -funny73;Mon Mar 12 12:16:02 2018 +0400;Поправил всплывающие сообщения при работе с "Категориями" -Aleksey Filippov;Mon Mar 12 11:25:51 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 12 11:25:30 2018 +0400;odinid annotation added -Aleksey Filippov;Sun Mar 11 15:23:49 2018 +0400;notes updated -Aleksey Filippov;Sun Mar 11 15:18:45 2018 +0400;some fixes -Aleksey Filippov;Sun Mar 11 15:09:57 2018 +0400;simple table draw support added, some refactoring -Aleksey Filippov;Sun Mar 11 15:08:22 2018 +0400;add user id field to userlistdto -Aleksey Filippov;Sun Mar 11 14:32:50 2018 +0400;add jsonproperty annotation support -Aleksey Filippov;Sun Mar 11 13:38:45 2018 +0400;some template fixes -Aleksey Filippov;Sun Mar 11 13:33:09 2018 +0400;some balance page fixes -Aleksey Filippov;Sun Mar 11 13:25:58 2018 +0400;add jsonignore annotation support -Aleksey Filippov;Thu Mar 8 14:16:20 2018 +0400;some odin improvements -Aleksey Filippov;Wed Mar 7 16:03:04 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Wed Mar 7 16:01:21 2018 +0400;odin submodule for basic types added, some refactoring -romanov73;Tue Mar 6 22:52:02 2018 +0400;remove old packages -romanov73;Tue Mar 6 19:12:27 2018 +0400;add tool type crud -romanov73;Tue Mar 6 13:09:51 2018 +0400;add version crud -romanov73;Tue Mar 6 10:48:36 2018 +0400;add unit crud -romanov73;Mon Mar 5 16:58:37 2018 +0400;fix tree -romanov73;Mon Mar 5 16:38:37 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Mon Mar 5 16:38:21 2018 +0400;employee crud -Aleksey Filippov;Mon Mar 5 15:43:49 2018 +0400;some fixes -Aleksey Filippov;Mon Mar 5 15:39:53 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Mon Mar 5 15:39:33 2018 +0400;user reset password function added, some refactoring -romanov73;Mon Mar 5 15:29:16 2018 +0400;fix delete category -Aleksey Filippov;Mon Mar 5 15:08:27 2018 +0400;user change password function added, some refactoring -romanov73;Mon Mar 5 15:08:17 2018 +0400;add category crud -romanov73;Mon Mar 5 14:07:10 2018 +0400;add tree component -Aleksey Filippov;Mon Mar 5 11:30:22 2018 +0400;user delete function added, some refactoring -Aleksey Filippov;Mon Mar 5 11:18:23 2018 +0400;user update function added -Aleksey Filippov;Mon Mar 5 11:18:06 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 10:06:04 2018 +0400;add user activation function -Aleksey Filippov;Mon Mar 5 09:48:22 2018 +0400;add scheduler for users -Aleksey Filippov;Mon Mar 5 09:48:07 2018 +0400;add activateddate field to userentity -Aleksey Filippov;Mon Mar 5 09:47:25 2018 +0400;notes file updated -Aleksey Filippov;Mon Mar 5 09:47:11 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 09:21:49 2018 +0400;thymeleaf cache settings added -Aleksey Filippov;Mon Mar 5 09:21:11 2018 +0400;notes file added -Aleksey Filippov;Mon Mar 5 09:20:57 2018 +0400;some refactoring -Aleksey Filippov;Mon Mar 5 08:37:01 2018 +0400;some refactoring -romanov73;Mon Mar 5 00:08:02 2018 +0400;add callbacks on version getters -romanov73;Mon Mar 5 00:07:34 2018 +0400;fix npe -romanov73;Sun Mar 4 01:13:57 2018 +0400;fix column style -romanov73;Sun Mar 4 01:06:38 2018 +0400;fix table style -romanov73;Sun Mar 4 00:27:14 2018 +0400;add balance dto + draw employee balance -romanov73;Sat Mar 3 22:43:06 2018 +0400;add balance services -romanov73;Sat Mar 3 16:31:19 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Sat Mar 3 16:31:04 2018 +0400;add employee balance table -Aleksey Filippov;Sat Mar 3 15:54:23 2018 +0400;some user support improvements -Aleksey Filippov;Sat Mar 3 15:53:20 2018 +0400;edit migration files, need manual fix of databasechangelog table -Aleksey Filippov;Sat Mar 3 15:51:58 2018 +0400;add application properties handler -Aleksey Filippov;Sat Mar 3 15:51:19 2018 +0400;disable tests -Aleksey Filippov;Sat Mar 3 13:49:32 2018 +0400;some refactoring -romanov73;Fri Mar 2 18:10:23 2018 +0400;add balance page -Aleksey Filippov;Fri Mar 2 18:04:39 2018 +0400;some refactoring -Aleksey Filippov;Fri Mar 2 17:51:41 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 17:50:59 2018 +0400;advicecontroller improvements -Aleksey Filippov;Fri Mar 2 17:50:39 2018 +0400;some mvc improvements -romanov73;Fri Mar 2 15:15:46 2018 +0400;add dtos -romanov73;Fri Mar 2 15:02:38 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 14:54:33 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Fri Mar 2 14:53:39 2018 +0400;add migrations -Aleksey Filippov;Fri Mar 2 14:53:30 2018 +0400;some core improvements -Aleksey Filippov;Fri Mar 2 14:53:13 2018 +0400;some users improvements -romanov73;Fri Mar 2 12:42:54 2018 +0400;add category service -romanov73;Fri Mar 2 12:37:51 2018 +0400;fix unit service -romanov73;Fri Mar 2 11:24:11 2018 +0400;add unit controller -romanov73;Thu Mar 1 23:19:42 2018 +0400;fix versions panel -romanov73;Thu Mar 1 22:50:57 2018 +0400;Merge branch 'develop' into 36-rest -romanov73;Thu Mar 1 22:50:37 2018 +0400;Merge branch 'develop' into 36-rest -Romanov Anton;Thu Mar 1 18:31:03 2018 +0000;Merge branch '29-' into 'develop' -romanov73;Thu Mar 1 22:25:49 2018 +0400;change versions -romanov73;Thu Mar 1 20:14:22 2018 +0400;show version select -romanov73;Thu Mar 1 19:56:24 2018 +0400;add old models, add versions controller -romanov73;Thu Mar 1 19:55:59 2018 +0400;add old models, add versions controller -romanov73;Thu Mar 1 19:04:47 2018 +0400;save menu to session -romanov73;Thu Mar 1 18:50:00 2018 +0400;add favicon -funny73;Thu Mar 1 18:01:43 2018 +0400;1) Поправил имена колонок и таблицы для смен под постгрес 2) Исправил ошибку в имени переменной thidShift ->thirdShift -romanov73;Thu Mar 1 16:03:03 2018 +0400;add menu and restore changelogs -funny73;Thu Mar 1 15:33:36 2018 +0400;Добавил распорядок смен для каждого подразделения. Смена назначается на месяц. Есть возможность сохранить любой вариант комбинирования 1,2 и 3 смены. Например (1,3); (2); ()... Добавлен интерфейс для просмотра и редактирования распорядка смен по каждому подразделению. Добавлена валидация - для каждого месяца может быть только один распорядок смен. -Aleksey Filippov;Thu Mar 1 15:23:06 2018 +0400;migrate to webjars -Aleksey Filippov;Thu Mar 1 13:31:16 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Thu Mar 1 13:30:55 2018 +0400;create migrations for user models -Aleksey Filippov;Thu Mar 1 13:30:09 2018 +0400;old code refactoring -romanov73;Thu Mar 1 12:51:14 2018 +0400;add basic menu from rest -romanov73;Thu Mar 1 11:51:17 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -romanov73;Thu Mar 1 11:50:59 2018 +0400;try to fix ci: 3 remove tests -Aleksey Filippov;Thu Mar 1 11:50:25 2018 +0400;Merge remote-tracking branch 'origin/36-rest' into 36-rest -Aleksey Filippov;Thu Mar 1 11:49:59 2018 +0400;moved to new database -romanov73;Thu Mar 1 11:45:40 2018 +0400;try to fix ci: 2 change image -romanov73;Thu Mar 1 11:44:42 2018 +0400;try to fix ci: 1 -romanov73;Thu Mar 1 11:15:52 2018 +0400;fix csrf tokens -Aleksey Filippov;Thu Mar 1 00:25:43 2018 +0400;initial user management support added -romanov73;Wed Feb 28 22:26:00 2018 +0400;save current version in local storage -romanov73;Wed Feb 28 18:40:57 2018 +0400;add ajax datatable -romanov73;Wed Feb 28 18:01:23 2018 +0400;add static index page with menu -Romanov Anton;Tue Feb 27 16:53:04 2018 +0000;Merge branch '33-' into 'develop' -romanov73;Tue Feb 27 20:50:44 2018 +0400;calc area balance -romanov73;Tue Feb 27 17:36:31 2018 +0400;remove unused panel -Romanov Anton;Tue Feb 27 13:06:02 2018 +0000;Merge branch '32-' into 'develop' -romanov73;Tue Feb 27 17:02:35 2018 +0400;calc tool balance -romanov73;Tue Feb 27 15:45:39 2018 +0400;calc tool balance -romanov73;Tue Feb 27 12:16:41 2018 +0400;calc tool power -romanov73;Tue Feb 27 11:40:59 2018 +0400;use tool types in dto -romanov73;Tue Feb 27 11:29:05 2018 +0400;fix dtos -romanov73;Mon Feb 26 23:50:53 2018 +0400;inherit dtos -romanov73;Mon Feb 26 15:38:00 2018 +0400;fix services for balance calculation -Aleksey Filippov;Mon Feb 26 15:00:48 2018 +0400;move from maven to gradle, move from javaee to spring boot -romanov73;Thu Feb 22 18:03:35 2018 +0400;Add menu resource -Romanov Anton;Tue Feb 20 18:12:55 2018 +0000;Merge branch 'balance-different-dto' into 'develop' -romanov73;Tue Feb 20 22:09:33 2018 +0400;restore tests -romanov73;Tue Feb 20 21:46:45 2018 +0400;filter employees by stage -romanov73;Tue Feb 20 21:21:27 2018 +0400;filter employees by workload -romanov73;Tue Feb 20 20:58:23 2018 +0400;Merge branch 'develop' into balance-different-dto -funny73;Tue Feb 20 19:52:24 2018 +0400;Add shift unit model Add link to unit view -romanov73;Tue Feb 20 16:14:09 2018 +0400;Important fix using coefficient -romanov73;Mon Feb 19 19:00:07 2018 +0400;partially fix ajustment -romanov73;Mon Feb 19 18:24:10 2018 +0400;Modify dto for using with different periods and work types -Romanov Anton;Mon Feb 19 08:11:39 2018 +0000;Merge branch '30-' into 'develop' -romanov73;Mon Feb 19 12:09:05 2018 +0400;Add work type to position -Romanov Anton;Wed Feb 14 20:57:35 2018 +0000;Merge branch '34-' into 'develop' -romanov73;Thu Feb 15 00:51:24 2018 +0400;fixes by comments -romanov73;Thu Feb 15 00:47:21 2018 +0400;merge fields -romanov73;Wed Feb 14 23:42:35 2018 +0400;Merge branch 'develop' into 34-gleb -romanov73;Wed Feb 14 21:05:51 2018 +0400;add clock -Romanov Anton;Wed Feb 14 16:49:29 2018 +0000;Merge branch '45-balance-employee-recomendations' into 'develop' -romanov73;Wed Feb 14 20:46:59 2018 +0400;cleanup code -romanov73;Wed Feb 14 18:25:42 2018 +0400;reduce code -romanov73;Wed Feb 14 18:24:59 2018 +0400;adjust additional employees -romanov73;Wed Feb 14 17:22:10 2018 +0400;move employees from other units -romanov73;Tue Feb 13 23:59:09 2018 +0400;fix save product name -romanov73;Tue Feb 13 23:28:30 2018 +0400;add converter, fix UI and backing for add complex object -romanov73;Tue Feb 13 22:10:59 2018 +0400;Merge branch 'develop' into 34-gleb -funny73;Tue Feb 13 18:07:25 2018 +0400;Add stage to workload -funny73;Tue Feb 13 16:05:45 2018 +0400;Add stage to category -romanov73;Tue Feb 13 13:20:16 2018 +0400;add map of all units balances -romanov73;Tue Feb 13 11:24:50 2018 +0400;move method -romanov73;Tue Feb 13 00:35:30 2018 +0400;add dialog -romanov73;Mon Feb 12 22:59:49 2018 +0400;filter only workshops and manufactures -romanov73;Mon Feb 12 22:04:55 2018 +0400;rename employees -romanov73;Mon Feb 12 17:34:37 2018 +0400;fix font size -romanov73;Mon Feb 12 16:59:16 2018 +0400;fix versions panel width -Romanov Anton;Mon Feb 12 12:47:04 2018 +0000;Merge branch '41-' into 'develop' -romanov73;Mon Feb 12 16:43:06 2018 +0400;change to working hours -romanov73;Mon Feb 12 14:05:36 2018 +0400;remove constructor -romanov73;Mon Feb 12 14:05:13 2018 +0400;Merge remote-tracking branch 'origin/develop' into develop -romanov73;Mon Feb 12 14:04:57 2018 +0400;fix selects -Romanov Anton;Mon Feb 12 09:20:05 2018 +0000;Merge branch '39-work-type-code' into 'develop' -Aleksey Filippov;Mon Feb 12 13:16:36 2018 +0400;add migration for work type code -Aleksey Filippov;Mon Feb 12 13:16:28 2018 +0400;add work type code support to xhtml -Aleksey Filippov;Mon Feb 12 13:16:12 2018 +0400;add work type code -Romanov Anton;Mon Feb 12 08:25:47 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 08:25:35 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 08:07:30 2018 +0000;Update README.md -Romanov Anton;Mon Feb 12 07:10:38 2018 +0000;Merge branch '40-' into 'develop' -Aleksey Filippov;Mon Feb 12 11:08:13 2018 +0400;add migration for position type -Aleksey Filippov;Mon Feb 12 11:07:45 2018 +0400;add position type support to xhtml -Aleksey Filippov;Mon Feb 12 11:07:23 2018 +0400;move position converter from boundary to view -Aleksey Filippov;Mon Feb 12 11:06:55 2018 +0400;add position type converter -Aleksey Filippov;Mon Feb 12 11:06:35 2018 +0400;add position type to backing -Aleksey Filippov;Mon Feb 12 11:06:00 2018 +0400;add position type support to service -Aleksey Filippov;Mon Feb 12 11:05:44 2018 +0400;add position type to model -Romanov Anton;Mon Feb 12 07:02:32 2018 +0000;Merge branch '33-' into 'develop' -romanov73;Mon Feb 12 14:58:51 2018 +0400;refactor -romanov73;Mon Feb 12 14:58:33 2018 +0400;add human hours coefficient -romanov73;Mon Feb 12 14:56:43 2018 +0400;fix api name -romanov73;Mon Feb 12 00:58:15 2018 +0400;add balance button -romanov73;Sun Feb 11 01:36:15 2018 +0400;add tools balance prototype -romanov73;Sat Feb 10 23:09:33 2018 +0400;move enum -romanov73;Sat Feb 10 23:02:36 2018 +0400;refactor -romanov73;Sat Feb 10 22:54:28 2018 +0400;add quarter -romanov73;Sat Feb 10 22:42:14 2018 +0400;refactor -romanov73;Sat Feb 10 20:52:46 2018 +0400;Merge branch 'develop' into 33-balance-area -romanov73;Sat Feb 10 20:52:32 2018 +0400;Merge branch 'develop' into 33-balance-area -Romanov Anton;Sat Feb 10 16:14:26 2018 +0000;Merge branch '31-map-2-dto' into 'develop' -romanov73;Sat Feb 10 19:35:17 2018 +0400;fixes after Almaz consultation -romanov73;Sat Feb 10 14:25:00 2018 +0400;fix npe -romanov73;Sat Feb 10 13:50:21 2018 +0400;add filter by work type -romanov73;Sat Feb 10 13:08:56 2018 +0400;rename field -romanov73;Sat Feb 10 13:08:38 2018 +0400;filter by all children -Aleksey Filippov;Sat Feb 10 11:26:17 2018 +0400;some fixes -Aleksey Filippov;Sat Feb 10 11:26:06 2018 +0400;add dto for total balance -Aleksey Filippov;Sat Feb 10 11:11:58 2018 +0400;add dto for employee load by unit -Aleksey Filippov;Sat Feb 10 10:30:11 2018 +0400;getEmployeesByUnit method refactoring -romanov73;Sat Feb 10 00:22:37 2018 +0400;add other type of balance area -romanov73;Fri Feb 9 22:03:39 2018 +0400;add tools balance -romanov73;Fri Feb 9 19:50:21 2018 +0400;divide balance page -Aleksey Filippov;Fri Feb 9 16:37:18 2018 +0400;adapt backing and view to areas and employee experience dtos -Aleksey Filippov;Fri Feb 9 16:36:18 2018 +0400;add dto for employee experience -Aleksey Filippov;Fri Feb 9 16:35:51 2018 +0400;add dto for areas -Aleksey Filippov;Fri Feb 9 14:57:01 2018 +0400;some ui fixes -Aleksey Filippov;Fri Feb 9 14:55:36 2018 +0400;move db methods from getters to init -romanov73;Thu Feb 8 01:20:12 2018 +0400;add areas panel -romanov73;Thu Feb 8 01:19:13 2018 +0400;add areas panel -romanov73;Thu Feb 8 01:02:39 2018 +0400;add global preloader -romanov73;Thu Feb 8 00:57:46 2018 +0400;select default unit -romanov73;Thu Feb 8 00:57:23 2018 +0400;add preloader -romanov73;Wed Feb 7 22:46:44 2018 +0400;change id -Romanov Anton;Wed Feb 7 13:53:35 2018 +0000;Merge branch '23-' into 'develop' -Romanov Anton;Wed Feb 7 13:49:27 2018 +0000;Merge branch 'deploy-fixes' into 'develop' -funny73;Wed Feb 7 17:34:41 2018 +0400;UDP modify AirplaneKitCounter to double -Aleksey Filippov;Wed Feb 7 17:33:23 2018 +0400;some wildfly deploy fixes -funny73;Wed Feb 7 17:19:22 2018 +0400;Modify AirplaneKitCounter to double -> Workload.java -funny73;Wed Feb 7 17:00:10 2018 +0400;Add AirplaneKitCounter to balance view -> BalanceEmployeeService.java Modify Worckload total Value (Value * AirplaneKitCounter) -> BalanceService.java -funny73;Wed Feb 7 14:13:35 2018 +0400;Add AirplaneKitCounter to view -funny73;Wed Feb 7 14:13:15 2018 +0400;Add AirplaneKitCounter to model Workload -funny73;Wed Feb 7 14:12:15 2018 +0400;Changelog for airplainetKitCounter -Romanov Anton;Wed Feb 7 06:54:55 2018 +0000;Merge branch '22-' into 'develop' -romanov73;Wed Feb 7 14:47:34 2018 +0400;add unit types -Romanov Anton;Tue Feb 6 22:43:40 2018 +0000;Merge branch '25-' into 'develop' -romanov73;Wed Feb 7 02:41:34 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 02:06:46 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 02:05:10 2018 +0400;filter balance by unit -romanov73;Wed Feb 7 00:41:01 2018 +0400;add method get all unit children -romanov73;Tue Feb 6 23:26:18 2018 +0400;add filter by unit in balance results -romanov73;Tue Feb 6 21:26:45 2018 +0400;refactor work with tree of menu items -romanov73;Tue Feb 6 21:08:01 2018 +0400;refactor work with tree -romanov73;Tue Feb 6 21:07:22 2018 +0400;fix update after remove enum key -romanov73;Tue Feb 6 20:03:31 2018 +0400;rename method -Gleb;Tue Feb 6 15:07:04 2018 +0000;Merge branch '17-' into 'develop' -romanov73;Tue Feb 6 21:15:12 2018 +0400;fix inf. -funny73;Tue Feb 6 19:02:36 2018 +0400;Add changelog for units with unit_type == 'Корпус', unit_type='Цех' -funny73;Tue Feb 6 18:19:50 2018 +0400;Remove "Korpus" from "Tip podrazdeleniya" -Romanov Anton;Tue Feb 6 11:31:43 2018 +0000;Merge branch '24-' into 'develop' -romanov73;Tue Feb 6 19:01:21 2018 +0400;add user -Romanov Anton;Thu Feb 1 19:45:21 2018 +0000;Merge branch 'wildfly-deploy' into 'develop' -Aleksey Filippov;Thu Feb 1 23:24:34 2018 +0400;add wildfly-maven-plugin, remove maven-glassfish-plugin -romanov73;Thu Feb 1 19:51:24 2018 +0400;add todos -Romanov Anton;Wed Jan 31 15:03:39 2018 +0000;Merge branch 'master' into 'develop' -romanov73;Wed Jan 31 18:53:27 2018 +0400;fix round -romanov73;Wed Jan 31 18:43:33 2018 +0400;fix round -romanov73;Wed Jan 31 18:38:33 2018 +0400;add balance by employees -romanov73;Wed Jan 31 18:30:18 2018 +0400;add balance by employees -Romanov Anton;Wed Jan 31 13:02:52 2018 +0000;Merge branch 'develop' into 'master' -Romanov Anton;Wed Jan 31 12:58:00 2018 +0000;Merge branch '4-' into 'develop' -romanov73;Wed Jan 31 16:47:51 2018 +0400;add balance by employees -romanov73;Wed Jan 31 00:47:39 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:47:25 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:47:08 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:46:53 2018 +0400;add calculation employee loads -romanov73;Wed Jan 31 00:45:59 2018 +0400;add interface for edit additional fields -romanov73;Wed Jan 31 00:45:04 2018 +0400;add comparable for using in tree map -romanov73;Wed Jan 31 00:44:33 2018 +0400;add fields for production program -romanov73;Wed Jan 31 00:43:56 2018 +0400;move interface to base entity -romanov73;Wed Jan 31 00:43:21 2018 +0400;add database fields for production program -romanov73;Wed Jan 31 00:42:55 2018 +0400;add dynamic columns -romanov73;Tue Jan 30 15:11:12 2018 +0400;add work type for workload -romanov73;Tue Jan 30 14:55:56 2018 +0400;add comments -romanov73;Tue Jan 30 03:00:28 2018 +0400;show by production program -romanov73;Tue Jan 30 00:07:31 2018 +0400;add employee available capacity -romanov73;Mon Jan 29 23:42:06 2018 +0400;fix units hierarchy bypass -romanov73;Mon Jan 29 23:28:28 2018 +0400;divide logic -romanov73;Mon Jan 29 22:59:45 2018 +0400;add employee experience table -romanov73;Mon Jan 29 22:15:29 2018 +0400;fix calc areas -romanov73;Mon Jan 29 21:57:23 2018 +0400;fix calc employee experience -romanov73;Mon Jan 29 21:12:52 2018 +0400;calc employee experience by units -romanov73;Mon Jan 29 14:01:36 2018 +0400;add employee experience -Romanov Anton;Mon Jan 29 06:04:01 2018 +0000;Update README.md -Romanov Anton;Sun Jan 28 16:16:56 2018 +0000;Merge branch 'develop' into 'master' -Romanov Anton;Sun Jan 28 16:06:14 2018 +0000;Merge branch '3-' into 'develop' -romanov73;Sun Jan 28 19:53:21 2018 +0400;remove product work types edit -romanov73;Sun Jan 28 19:41:31 2018 +0400;fix months select -romanov73;Sun Jan 28 19:06:10 2018 +0400;fix year select -romanov73;Sun Jan 28 04:28:21 2018 +0400;fix program edit -romanov73;Sun Jan 28 04:27:55 2018 +0400;add product backend -romanov73;Sun Jan 28 04:26:53 2018 +0400;add table and fields -Romanov Anton;Sat Jan 27 08:11:15 2018 +0000;Merge branch '9-' into 'develop' -romanov73;Sat Jan 27 15:28:22 2018 +0400;sort menu items, change logo -Romanov Anton;Sat Jan 27 05:55:52 2018 +0000;Update README.md -romanov73;Sat Jan 20 00:12:44 2018 +0400;fix versions select -Romanov Anton;Fri Jan 19 04:27:48 2018 +0000;Merge branch 'tool-square-catalog' into 'master' -Aleksey Filippov;Fri Jan 19 01:11:50 2018 +0400;add tool square catalog migration -Aleksey Filippov;Thu Jan 18 02:20:48 2018 +0400;add tool square catalog -romanov73;Thu Jan 11 21:22:14 2018 +0400;fix rest path -romanov73;Tue Jan 9 03:44:10 2018 +0400;fixes -romanov73;Tue Jan 9 00:09:27 2018 +0400;fix unit name -romanov73;Tue Jan 9 00:07:47 2018 +0400;fix year -romanov73;Mon Jan 8 19:16:28 2018 +0400;add short view -romanov73;Mon Jan 8 11:55:30 2018 +0400;fix -romanov73;Mon Jan 8 01:24:37 2018 +0400;fill work type by tool name -romanov73;Mon Jan 8 00:23:00 2018 +0400;ui fixes -romanov73;Sun Jan 7 23:54:40 2018 +0400;fix units hierarchy, add unit type, calc areas balance -romanov73;Sat Jan 6 22:51:11 2018 +0400;remove versions -romanov73;Sat Jan 6 22:06:03 2018 +0400;fix edit program -romanov73;Sat Jan 6 21:40:11 2018 +0400;add title image -romanov73;Sat Jan 6 18:04:27 2018 +0400;add production program input -romanov73;Wed Jan 3 21:05:48 2018 +0400;change wizard to tabs -romanov73;Wed Jan 3 01:03:05 2018 +0400;add wizard -romanov73;Sat Dec 30 02:02:42 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:46:01 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:32:33 2017 +0400;fix tools loading -romanov73;Sat Dec 30 01:28:24 2017 +0400;fix tools loading -romanov73;Sat Dec 30 00:54:26 2017 +0400;fix employee loading -romanov73;Fri Dec 29 19:04:14 2017 +0400;fix unit select -romanov73;Fri Dec 29 17:19:10 2017 +0400;fix unit select -romanov73;Fri Dec 29 01:40:38 2017 +0400;add cache -romanov73;Fri Dec 29 01:27:18 2017 +0400;add cache -romanov73;Fri Dec 29 01:14:09 2017 +0400;add cache -romanov73;Fri Dec 29 01:12:17 2017 +0400;change color -romanov73;Fri Dec 29 00:50:57 2017 +0400;add ci -romanov73;Fri Dec 29 00:48:10 2017 +0400;add ci -romanov73;Fri Dec 29 00:34:36 2017 +0400;add ci -romanov73;Fri Dec 29 00:31:22 2017 +0400;add ci -romanov73;Fri Dec 29 00:18:51 2017 +0400;add ci -romanov73;Fri Dec 29 00:16:44 2017 +0400;add ci -romanov73;Fri Dec 29 00:13:33 2017 +0400;add ci -romanov73;Fri Dec 29 00:09:51 2017 +0400;fix menus -Romanov Anton;Thu Dec 28 19:47:02 2017 +0000;Merge branch 'balance-example' into 'master' -romanov73;Thu Dec 28 23:44:52 2017 +0400;add other dictionaries -romanov73;Sat Dec 23 10:40:41 2017 +0400;add tool and work types -romanov73;Sat Dec 23 09:12:38 2017 +0400;fix menu item -romanov73;Sat Dec 23 09:12:23 2017 +0400;fix employee load -romanov73;Sat Dec 23 08:51:18 2017 +0400;add employee category -romanov73;Sat Dec 23 08:22:10 2017 +0400;add categories dictionary -romanov73;Fri Dec 22 12:38:08 2017 +0400;add menu item -romanov73;Fri Dec 22 12:31:26 2017 +0400;add employee category -romanov73;Fri Dec 22 09:55:31 2017 +0400;fix calendar -romanov73;Fri Dec 22 09:33:56 2017 +0400;fix date -romanov73;Thu Dec 21 16:38:20 2017 +0400;fix for context path change -romanov73;Thu Dec 21 13:58:02 2017 +0400;fluid panel -romanov73;Wed Dec 20 20:50:17 2017 +0400;fix context path -romanov73;Wed Dec 20 17:12:19 2017 +0400;fix displaying position -romanov73;Wed Dec 20 17:10:14 2017 +0400;load employees from file -romanov73;Wed Dec 20 15:09:54 2017 +0400;fix tool loading -romanov73;Mon Dec 18 18:18:04 2017 +0400;add resource versions -romanov73;Mon Dec 18 17:36:50 2017 +0400;add resource -romanov73;Mon Dec 18 17:07:47 2017 +0400;add swagger -romanov73;Sat Dec 16 09:42:46 2017 +0400;add new version -romanov73;Fri Dec 15 19:50:46 2017 +0400;fix title -romanov73;Fri Dec 15 19:24:13 2017 +0400;fix title -romanov73;Fri Dec 15 11:57:18 2017 +0400;add menu items -romanov73;Fri Dec 15 11:52:01 2017 +0400;add positions dictionary -romanov73;Fri Dec 15 11:29:02 2017 +0400;fix select for employee -romanov73;Fri Dec 15 10:35:12 2017 +0400;add employee backing -romanov73;Fri Dec 15 09:36:33 2017 +0400;fix 500 error page -romanov73;Thu Dec 14 22:26:45 2017 +0400;fix mapping -romanov73;Thu Dec 14 22:49:44 2017 +0400;add employee -romanov73;Thu Dec 14 22:37:30 2017 +0400;add employee -romanov73;Thu Dec 14 14:12:03 2017 +0400;fix crud service -romanov73;Thu Dec 14 12:51:59 2017 +0400;fix table style -romanov73;Thu Dec 14 12:22:48 2017 +0400;remove border for grid -romanov73;Thu Dec 14 12:20:14 2017 +0400;add version_id for units -romanov73;Thu Dec 14 11:49:53 2017 +0400;fix font size -romanov73;Thu Dec 14 11:08:46 2017 +0400;add unit fields -romanov73;Thu Dec 14 10:23:01 2017 +0400;KISS units hierarchy -romanov73;Wed Dec 13 19:04:05 2017 +0400;refresh page -romanov73;Wed Dec 13 17:43:54 2017 +0400;add version -romanov73;Wed Dec 13 01:46:05 2017 +0400;body width -romanov73;Wed Dec 13 01:45:17 2017 +0400;add menu item -romanov73;Tue Dec 12 22:20:07 2017 +0400;fix tree -romanov73;Tue Dec 12 20:02:20 2017 +0400;fix login -romanov73;Tue Dec 12 19:29:03 2017 +0400;add unit tree -romanov73;Tue Dec 12 18:23:38 2017 +0400;add unit dictionary -romanov73;Tue Dec 12 16:18:03 2017 +0400;add filter -romanov73;Tue Dec 12 14:13:31 2017 +0400;fix menu session -romanov73;Tue Dec 12 00:10:47 2017 +0400;remove bootstrap -romanov73;Mon Dec 11 21:53:18 2017 +0400;add unit select -romanov73;Mon Dec 11 19:49:15 2017 +0400;add menu item and role -romanov73;Mon Dec 11 19:37:38 2017 +0400;fix permissions -romanov73;Mon Dec 11 17:08:39 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 17:08:04 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 17:07:27 2017 +0400;refactor and add tools dictionary backing -romanov73;Mon Dec 11 14:12:24 2017 +0400;add tools dictionary page -romanov73;Sat Dec 9 09:54:45 2017 +0400;add global exception hadler -romanov73;Sat Nov 25 13:52:33 2017 +0400;add monitoring -romanov73;Sat Nov 25 13:21:18 2017 +0400;fix xls and xlsx -romanov73;Sat Nov 25 12:39:45 2017 +0400;upload tools -romanov73;Fri Nov 24 22:16:27 2017 +0400;save tools -romanov73;Fri Nov 24 13:06:21 2017 +0400;fix message -romanov73;Fri Nov 24 12:11:43 2017 +0400;add loading from xlsx -romanov73;Fri Nov 17 19:43:35 2017 +0400;add message -romanov73;Fri Nov 17 15:11:22 2017 +0400;fix button -romanov73;Fri Nov 17 10:15:42 2017 +0400;add registration service -romanov73;Thu Nov 16 21:35:04 2017 +0400;add push script -romanov73;Thu Nov 16 20:54:49 2017 +0400;fix named query execution -romanov73;Sun Nov 12 22:19:03 2017 +0400;sort menu items -romanov73;Sat Nov 11 15:09:35 2017 +0400;fix title -romanov73;Sat Nov 11 14:54:25 2017 +0400;fix table -romanov73;Sat Nov 11 14:35:38 2017 +0400;fix style attribute -romanov73;Sat Nov 11 14:08:56 2017 +0400;show user sessions -romanov73;Sat Nov 11 13:51:34 2017 +0400;rename service -romanov73;Fri Nov 10 22:03:25 2017 +0400;fix update tree -romanov73;Fri Nov 10 14:29:25 2017 +0400;edit menu items -romanov73;Fri Nov 10 12:17:24 2017 +0400;set bootstrap theme for primefaces -romanov73;Thu Nov 9 23:40:40 2017 +0400;fix saving entitites -romanov73;Thu Nov 9 23:27:42 2017 +0400;fix saving entitites -romanov73;Thu Nov 9 20:51:19 2017 +0400;fix titles -romanov73;Thu Nov 9 18:00:45 2017 +0400;fix login page title -romanov73;Thu Nov 9 13:36:21 2017 +0400;commit log -romanov73;Thu Nov 9 07:31:37 2017 +0400;reverse sort commits -romanov73;Thu Nov 9 00:27:48 2017 +0400;sort commits -romanov73;Wed Nov 8 23:16:07 2017 +0400;extend commits log -romanov73;Wed Nov 8 21:42:16 2017 +0400;fix styles -romanov73;Wed Nov 8 19:17:08 2017 +0400;add logout -romanov73;Wed Nov 8 19:13:17 2017 +0400;add logout -romanov73;Wed Nov 8 20:18:35 2017 +0400;Merge branch 'master' of gitlab.com:romanov73/balance -romanov73;Wed Nov 8 20:17:55 2017 +0400;add database diagramm -Romanov Anton;Wed Nov 8 04:13:39 2017 +0000;Update README.md -Romanov Anton;Wed Nov 8 04:11:33 2017 +0000;Update README.md -romanov73;Wed Nov 8 07:48:13 2017 +0400;Merge remote-tracking branch 'origin/master' -Romanov Anton;Tue Nov 7 18:05:04 2017 +0000;Update README.md -romanov73;Tue Nov 7 22:01:50 2017 +0400;add example of permissions validation -romanov73;Tue Nov 7 20:56:31 2017 +0400;rename project -romanov73;Wed Oct 11 21:37:49 2017 +0400;reverse sort commits -romanov73;Wed Oct 11 21:32:19 2017 +0400;fix read resource as file -romanov73;Wed Oct 11 20:23:10 2017 +0400;Merge remote-tracking branch 'origin/master' -romanov73;Wed Oct 11 20:22:52 2017 +0400;add commits log -Romanov Anton;Tue Oct 10 20:47:29 2017 +0000;Update README.md -romanov73;Fri Oct 6 01:55:57 2017 +0400;add menu to platform \ No newline at end of file +Anton Romanov;Thu Mar 28 14:59:35 2019 +0400;partially restored commits page +Anton Romanov;Wed Mar 27 13:27:49 2019 +0400;#33 small external link +Anton Romanov;Wed Mar 27 09:07:02 2019 +0000;Update README.md +Anton Romanov;Wed Mar 27 07:58:58 2019 +0000;Merge branch '33-paper-url' into 'dev' +Семенова Мария;Sat Mar 23 12:23:30 2019 +0400;#33 link in new tab +Семенова Мария;Sat Mar 23 12:04:51 2019 +0400;Merge branch 'dev' into 33-paper-url +Anton Romanov;Fri Mar 22 10:36:09 2019 +0000;Merge branch '13-paper-files' into 'dev' +Anton Romanov;Fri Mar 22 13:58:03 2019 +0400;#13 reduce of code +Anton Romanov;Fri Mar 22 13:52:06 2019 +0400;#13 fix condition +Семенова Мария;Fri Mar 22 13:21:32 2019 +0400;#13 streams refactoring +Семенова Мария;Fri Mar 22 09:32:21 2019 +0400;#13 rename 'deleted', move creating fileDto to service +Семенова Мария;Thu Mar 21 18:26:28 2019 +0400;#13 fileDataDto instead of Object[] +Семенова Мария;Tue Mar 19 17:59:19 2019 +0400;#13 some refactoring +Семенова Мария;Tue Mar 19 17:41:02 2019 +0400;Merge branch 'dev' into 13-paper-files +Семенова Мария;Mon Mar 18 23:31:33 2019 +0400;#33 url in paper +Anton Romanov;Mon Mar 18 22:35:01 2019 +0400;merge deploy environments +Anton Romanov;Mon Mar 18 22:25:28 2019 +0400;add environment +Семенова Мария;Mon Mar 18 21:02:26 2019 +0400;#13 download files +Семенова Мария;Mon Mar 18 19:17:28 2019 +0400;#13 add files to db +Семенова Мария;Mon Mar 18 18:39:15 2019 +0400;#13 addNewFile js function +Семенова Мария;Mon Mar 18 16:13:22 2019 +0400;Merge branch 'dev' into 13-paper-files +Семенова Мария;Mon Mar 18 16:02:55 2019 +0400;#13 file list on paper page +Anton Romanov;Mon Mar 18 11:08:15 2019 +0000;Merge branch '50-refactorForGrants' into 'dev' +T-Midnight;Mon Mar 18 14:27:54 2019 +0400;Delete DeadlineDTO and update usages +Семенова Мария;Mon Mar 18 13:00:34 2019 +0400;#13 add fileDataDto +Семенова Мария;Mon Mar 18 11:07:30 2019 +0400;#13 change model, schema +T-Midnight;Fri Mar 15 12:24:02 2019 +0400;Add new status for grant +T-Midnight;Fri Mar 15 12:19:25 2019 +0400;Rename title +T-Midnight;Fri Mar 15 12:19:01 2019 +0400;Create Navigation class to avoid "magic strings" and code duplication +Anton Romanov;Wed Mar 13 07:16:32 2019 +0000;Merge branch '72-link-to-timetable' into 'dev' +Anton Romanov;Tue Mar 12 14:44:11 2019 +0400;add patronymic +Anton Romanov;Tue Mar 12 13:40:48 2019 +0400;display user in filter +Anton Romanov;Tue Mar 12 12:51:59 2019 +0300;add service methods +Anton Romanov;Tue Mar 12 12:35:09 2019 +0300;add timetable link +Anton Romanov;Mon Mar 11 10:49:07 2019 +0000;Merge branch '54-view-conference' into 'dev' +Nightblade73;Mon Mar 11 14:43:32 2019 +0400;#54 deleted edit btn +Nightblade73;Mon Mar 11 12:58:40 2019 +0400;#54 add back link, css fixes +Nightblade73;Mon Mar 11 11:36:00 2019 +0400;#54 add custom paper-list +Nightblade73;Sun Mar 10 22:15:09 2019 +0400;#54 add custom deadline-list, add edit-button, add edit and delete icons +Nightblade73;Thu Mar 7 22:50:37 2019 +0400;#54 add member list +Nightblade73;Wed Mar 6 20:51:46 2019 +0400;#54 part of make-up +Nightblade73;Wed Mar 6 16:48:51 2019 +0400;#54 add transitions +Anton Romanov;Wed Mar 6 16:40:33 2019 +0400;fix login +Nightblade73;Wed Mar 6 16:27:25 2019 +0400;#50 creating html page, changing href +Anton Romanov;Wed Mar 6 12:25:34 2019 +0000;Merge branch '29-page-header' into 'dev' +Anton Romanov;Wed Mar 6 16:19:43 2019 +0400;fix css +Anton Romanov;Wed Mar 6 14:36:46 2019 +0300;fix props +Anton Romanov;Wed Mar 6 14:35:09 2019 +0300;fix props +user;Wed Mar 6 14:25:02 2019 +0300;header fix +Anton Romanov;Mon Mar 4 10:28:46 2019 +0400;fix code +Anton Romanov;Mon Mar 4 10:24:30 2019 +0400;fix code +Anton Romanov;Fri Mar 1 11:27:57 2019 +0400;fix branch +Anton Romanov;Fri Mar 1 11:22:25 2019 +0400;add links to papers +Anton Romanov;Fri Mar 1 11:21:25 2019 +0400;add links to papers +Anton Romanov;Sun Feb 3 14:54:02 2019 +0000;Update README.md +Anton Romanov;Thu Jan 17 01:01:26 2019 +0400;filter dashboard papers +Anton Romanov;Thu Jan 17 00:57:34 2019 +0400;fix failed conditions +Anton Romanov;Tue Jan 8 19:55:27 2019 +0000;Update README.md +Anton Romanov;Tue Jan 8 19:36:36 2019 +0000;Update README.md +Anton Romanov;Sat Jan 5 08:01:07 2019 +0400;fix notifications +Anton Romanov;Fri Jan 4 17:41:48 2019 +0400;fix notifications +Anton Romanov;Sun Dec 30 19:23:11 2018 +0400;some layout fix +Anton Romanov;Sun Dec 30 19:22:51 2018 +0400;sort deadlines +Anton Romanov;Sat Dec 29 09:58:37 2018 +0400;fix enum field name +Anton Romanov;Fri Dec 28 10:04:42 2018 +0000;Merge branch '35-' into 'master' +Anton Romanov;Fri Dec 28 14:00:09 2018 +0400;some refactor +Anton Romanov;Fri Dec 28 13:57:20 2018 +0400;some refactor +T-Midnight;Tue Dec 25 12:50:14 2018 +0400;Create model folder for grant +T-Midnight;Tue Dec 25 02:44:50 2018 +0400;Hide button "Add Project" when project already exists (not perfect) +T-Midnight;Tue Dec 25 00:14:38 2018 +0400;Made button "delete grant" visible +T-Midnight;Mon Dec 24 23:39:19 2018 +0400;Rename table grant to grants +T-Midnight;Mon Dec 24 15:20:57 2018 +0400;Update classes +T-Midnight;Mon Dec 24 15:18:52 2018 +0400;Add thymeleaf template +T-Midnight;Mon Dec 24 13:05:37 2018 +0400;Create service&repository for Project +T-Midnight;Mon Dec 24 11:07:02 2018 +0400;Create js for grants +T-Midnight;Mon Dec 24 11:03:03 2018 +0400;Create html fragments +T-Midnight;Sun Dec 23 02:22:41 2018 +0400;Create Controller&Service +T-Midnight;Sun Dec 23 02:22:31 2018 +0400;Update ProjectDto +T-Midnight;Sun Dec 23 02:22:24 2018 +0400;Add constructor +T-Midnight;Sun Dec 23 02:22:06 2018 +0400;Add function getNextDeadline() +T-Midnight;Sat Dec 22 18:03:18 2018 +0400;Create GrantRepository +T-Midnight;Sat Dec 22 03:31:43 2018 +0400;Create setters for GrantDto +Anton Romanov;Fri Dec 21 00:07:26 2018 +0400;fix display paper title +Anton Romanov;Thu Dec 20 23:30:25 2018 +0400;fix paper status template +Anton Romanov;Wed Dec 19 01:29:54 2018 +0400;try to fix template resolvers, step 4 +Anton Romanov;Wed Dec 19 01:00:08 2018 +0400;try to fix template resolvers, step 3 +Anton Romanov;Wed Dec 19 00:44:12 2018 +0400;try to fix template resolvers, step 2 +Anton Romanov;Wed Dec 19 00:22:36 2018 +0400;try to fix template resolvers +Anton Romanov;Wed Dec 19 00:11:39 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Dec 19 00:09:07 2018 +0400;try to fix gradlew +Anton Romanov;Tue Dec 18 23:02:48 2018 +0400;sort filtered papers +Anton Romanov;Tue Dec 18 18:45:43 2018 +0000;Merge branch '47-statuses' into 'master' +Anton Romanov;Tue Dec 18 22:42:34 2018 +0400;add statuses +Anton Romanov;Tue Dec 18 18:18:25 2018 +0000;Merge branch '46-mvc' into 'master' +Anton Romanov;Tue Dec 18 22:15:21 2018 +0400;fix filer +Anton Romanov;Tue Dec 18 19:40:23 2018 +0400;fix scripts +Anton Romanov;Tue Dec 18 19:23:15 2018 +0400;show authors +Anton Romanov;Tue Dec 18 18:47:15 2018 +0400;confirm delete paper +Anton Romanov;Tue Dec 18 18:05:30 2018 +0400;add papers navigation +Anton Romanov;Mon Dec 17 17:56:48 2018 +0400;fix submit form +Anton Romanov;Mon Dec 17 17:29:06 2018 +0400;fix email notification +Anton Romanov;Mon Dec 17 17:28:52 2018 +0400;fix route +Anton Romanov;Mon Dec 17 13:46:08 2018 +0400;edit deadlines +Anton Romanov;Fri Dec 14 16:00:46 2018 +0400;Merge branch 'master' into 46-mvc +Anton Romanov;Fri Dec 14 16:00:33 2018 +0400;Merge branch 'master' into 46-mvc +Anton Romanov;Tue Dec 11 11:14:21 2018 +0000;Merge branch '36-' into 'master' +Anton Romanov;Tue Dec 11 15:08:35 2018 +0400;move classes, add deadline to entities, fix db changelogs +T-Midnight;Sat Dec 8 23:53:38 2018 +0400;Create model for grant, deadline and project +Anton Romanov;Wed Dec 5 18:08:47 2018 +0400;save authors +Anton Romanov;Wed Dec 5 17:36:53 2018 +0400;fix npe +Anton Romanov;Wed Dec 5 17:06:56 2018 +0400;format dates +Anton Romanov;Wed Dec 5 16:51:26 2018 +0400;fix create paper +Anton Romanov;Wed Dec 5 06:00:33 2018 +0000;add rest controller +Anton Romanov;Tue Dec 4 14:49:23 2018 +0400;fix js +Anton Romanov;Tue Dec 4 14:48:39 2018 +0400;show status +Anton Romanov;Tue Dec 4 14:13:01 2018 +0400;add validation +Anton Romanov;Tue Dec 4 11:35:51 2018 +0400;pass values for paper +Anton Romanov;Mon Nov 26 23:07:14 2018 +0400;load paper +Anton Romanov;Fri Nov 23 16:40:15 2018 +0000;Merge branch '14-filter-frontend' into 'master' +Anton Romanov;Fri Nov 23 20:37:24 2018 +0400;fix failed commit +Anton Romanov;Fri Nov 23 16:24:26 2018 +0000;Merge branch '10-savePaper' into 'master' +Anton Romanov;Fri Nov 23 16:45:40 2018 +0400;show paper list in mvc +Alyona;Fri Nov 23 14:17:16 2018 +0400;filter +Alyona;Fri Nov 23 14:12:37 2018 +0400;Merge branch 'master' into 14-filter-frontend +Alyona;Fri Nov 23 14:10:49 2018 +0400;Merge remote-tracking branch 'origin/master' +Alyona;Fri Nov 23 14:10:36 2018 +0400;Merge branch 'master' of C:\Users\катя\IdeaProjects\ng-tracker with conflicts. +Elena;Fri Nov 23 14:05:23 2018 +0400;задача 10(new) +Elena;Fri Nov 23 13:53:20 2018 +0400;задача 10 +Elena;Fri Nov 23 12:39:36 2018 +0400;Merge branch 'master' into 10-savePaper +Anton Romanov;Thu Nov 22 21:48:36 2018 +0400;fix colors +Anton Romanov;Thu Nov 22 21:31:41 2018 +0400;fix colors +Anton Romanov;Thu Nov 22 21:06:58 2018 +0400;add paper status +Anton Romanov;Thu Nov 22 11:20:08 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Thu Nov 22 11:19:50 2018 +0400;add mertica +Anton Romanov;Wed Nov 21 15:47:26 2018 +0400;remove empty page +Anton Romanov;Wed Nov 21 15:47:14 2018 +0400;add toolbar +Anton Romanov;Wed Nov 21 15:42:20 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Nov 21 11:42:47 2018 +0000;Merge branch '31-' into 'master' +Anton Romanov;Wed Nov 21 15:40:20 2018 +0400;close failed papers +T-Midnight;Wed Nov 21 15:28:41 2018 +0400;Create grant page +Anton Romanov;Wed Nov 21 08:51:11 2018 +0000;Merge branch '30-main-grants-page' into 'master' +Anton Romanov;Wed Nov 21 12:46:42 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Wed Nov 21 12:46:27 2018 +0400;notify if paper deadline in future +T-Midnight;Mon Nov 19 12:16:39 2018 +0400;Update dashboard for grants +Anton Romanov;Mon Nov 19 08:21:22 2018 +0400;fix for remove event +Anton Romanov;Mon Nov 19 07:46:02 2018 +0400;fix event create conditions +Anton Romanov;Sun Nov 18 14:29:41 2018 +0400;fix delete button +Anton Romanov;Sat Nov 17 12:31:22 2018 +0400;fix delete button +Anton Romanov;Sat Nov 17 11:56:52 2018 +0400;show events +Anton Romanov;Wed Nov 14 17:14:56 2018 +0400;fix build script for netbeans +Anton Romanov;Wed Nov 14 17:04:40 2018 +0400;show toolbar buttons +Anton Romanov;Wed Nov 14 17:04:29 2018 +0400;show toolbar buttons +Anton Romanov;Wed Nov 14 17:04:13 2018 +0400;show paper status +Anton Romanov;Wed Nov 14 17:03:22 2018 +0400;fix code +Anton Romanov;Wed Nov 14 15:18:22 2018 +0400;fix code +Anton Romanov;Wed Nov 14 15:15:23 2018 +0400;read dashboard +Alyona;Wed Nov 14 08:55:46 2018 +0400;filter +Anton Romanov;Tue Nov 13 11:46:25 2018 +0000;Merge branch '14-DB-filter' into 'master' +Anton Romanov;Tue Nov 13 15:41:18 2018 +0400;filter papers +Anton Romanov;Sun Nov 11 14:28:51 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Sun Nov 11 12:43:27 2018 +0400;move class +Anton Romanov;Sat Nov 10 21:44:36 2018 +0000;Merge branch '20-' into 'master' +Anton Romanov;Sun Nov 11 01:31:27 2018 +0400;fix scheduler +Anton Romanov;Sun Nov 11 01:29:06 2018 +0400;refactor and db changes +Anton Romanov;Sat Nov 10 23:15:35 2018 +0400;delete paper +T-Midnight;Sat Nov 10 21:55:09 2018 +0400;Create dashboard for grants +Anton Romanov;Sat Nov 10 21:43:52 2018 +0400;add route +Alyona;Sat Nov 10 20:28:17 2018 +0400;fix +Alyona;Fri Nov 9 15:43:42 2018 +0400;fix +Elena;Fri Nov 9 15:43:25 2018 +0400;Задача №10 +Anton Romanov;Fri Nov 9 15:37:58 2018 +0400;remove styles from email template +Alyona;Fri Nov 9 15:29:48 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Fri Nov 9 15:21:03 2018 +0400;move styles to body +Anton Romanov;Fri Nov 9 11:15:00 2018 +0000;Merge branch '21-Event-status' into 'master' +Anton Romanov;Fri Nov 9 15:08:10 2018 +0400;Merge branch 'master' into 21-Event-status +Anton Romanov;Fri Nov 9 15:05:39 2018 +0400;fix styles +Anton Romanov;Fri Nov 9 14:53:24 2018 +0400;add styles in email templates +Alyona;Fri Nov 9 14:51:19 2018 +0400;added period event +Alyona;Fri Nov 9 14:48:21 2018 +0400;added period event +Elena;Fri Nov 9 13:43:08 2018 +0400;Merge branch 'master' into 10-savePaper +Elena;Fri Nov 9 13:42:28 2018 +0400;Задача №10 +Alyona;Fri Nov 9 13:30:31 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Fri Nov 9 10:04:50 2018 +0400;add notification templates +Anton Romanov;Fri Nov 9 09:16:24 2018 +0400;move to notification service +Anton Romanov;Fri Nov 9 08:39:00 2018 +0400;fix scheduler code to use service +Anton Romanov;Thu Nov 8 21:58:10 2018 +0400;add toolbar +Anton Romanov;Thu Nov 8 20:34:09 2018 +0400;fix paths +Anton Romanov;Thu Nov 8 20:08:26 2018 +0400;add config parameter +Anton Romanov;Thu Nov 8 19:39:22 2018 +0400;Merge remote-tracking branch 'origin/master' +Anton Romanov;Thu Nov 8 19:36:50 2018 +0400;add paper create strategy +Anton Romanov;Tue Nov 6 10:39:25 2018 +0000;Update README.md +Anton Romanov;Tue Nov 6 14:10:00 2018 +0400;fix ci +Anton Romanov;Thu Nov 1 12:53:19 2018 +0400;fix imports +Alyona;Wed Oct 31 20:06:27 2018 +0400;changelog +Alyona;Wed Oct 31 19:35:23 2018 +0400;Merge branch 'master' into 21-Event-status +Alyona;Wed Oct 31 19:33:21 2018 +0400;Merge branch 'master' of C:\Users\катя\IdeaProjects\ng-tracker with conflicts. +Alyona;Tue Oct 30 23:59:42 2018 +0400;added event status +Anton Romanov;Tue Oct 30 19:32:00 2018 +0000;Merge branch '4-show-time-line' into 'master' +Anton Romanov;Tue Oct 30 23:29:18 2018 +0400;Merge branch 'master' into 4-show-time-line +Anton Romanov;Tue Oct 30 23:26:55 2018 +0400;fix db +Anton Romanov;Tue Oct 30 23:07:01 2018 +0400;Revert "remove liquibase" +Anton Romanov;Tue Oct 30 23:04:49 2018 +0400;Merge remote-tracking branch 'origin/4-show-time-line' into 4-show-time-line +Anton Romanov;Tue Oct 30 23:04:21 2018 +0400;Revert "remove liquibase" +Anton Romanov;Sat Oct 27 21:00:43 2018 +0000;Merge branch '11-Message-about-deadlines' into 'master' +Anton Romanov;Sat Oct 27 21:00:01 2018 +0000;Merge branch '12-' into 'master' +Alyona;Sun Oct 28 00:33:00 2018 +0400;fix +Alyona;Sun Oct 28 00:26:09 2018 +0400;fix +Alyona;Sun Oct 28 00:17:16 2018 +0400;fix +Alyona;Sun Oct 28 00:15:42 2018 +0400;fix +Alyona;Sat Oct 27 23:50:12 2018 +0400;fix +Alyona;Sat Oct 27 23:46:49 2018 +0400;fix +Anton Romanov;Sat Oct 27 23:25:27 2018 +0400;- make scheduler as a service - reformat code - fix cyrillic letter +Anton Romanov;Sat Oct 27 23:16:14 2018 +0400;add database column +Alyona;Sat Oct 27 23:11:07 2018 +0400;class for send messages about update paper +Alyona;Sat Oct 27 22:32:51 2018 +0400;add deadlineSсheduler +Alyona;Sat Oct 27 22:24:10 2018 +0400;add deadlineDate to models +Anton Romanov;Thu Oct 25 10:27:57 2018 +0400;some fix +Anton Romanov;Thu Oct 25 04:59:51 2018 +0000;Merge branch '8-' into 'master' +Alyona;Thu Oct 25 08:53:48 2018 +0400;finish deadline +Alyona;Thu Oct 25 08:53:29 2018 +0400;finish deadline +Alyona;Thu Oct 25 08:44:25 2018 +0400;Merge branch 'master' into 8-Deadline +Alyona;Thu Oct 25 08:41:37 2018 +0400;partially add deadline +Anton Romanov;Thu Oct 18 07:25:58 2018 +0000;Merge branch '7-dates-Matveeva' into 'master' +Anton Romanov;Thu Oct 18 11:21:26 2018 +0400;use html5 datetime pickers +Elena;Thu Oct 18 10:38:04 2018 +0400;Задача №7 +Anton Romanov;Thu Oct 18 04:49:10 2018 +0000;Merge branch '6-authors-list-Matveeva' into 'master' +Anton Romanov;Thu Oct 11 19:49:16 2018 +0400;fix buttons size +Anton Romanov;Thu Oct 11 15:39:57 2018 +0400;fix styles +Anton Romanov;Thu Oct 11 10:03:44 2018 +0400;load paper statuses +Anton Romanov;Thu Oct 11 09:05:41 2018 +0400;fix progress background +Elena;Wed Oct 10 23:09:45 2018 +0400;Задача №6 +Anton Romanov;Wed Oct 10 13:18:24 2018 +0000;Merge branch '5-' into 'master' +Anton Romanov;Wed Oct 10 17:05:54 2018 +0400;fix paper view +Elena;Wed Oct 10 15:30:47 2018 +0400;Задача №5 new +Elena;Wed Oct 10 10:09:07 2018 +0400;Merge branch 'master' into 5-Matveeva-Page +Elena;Thu Oct 4 10:39:27 2018 +0400;Задача №5 +Anton Romanov;Tue Oct 2 07:46:40 2018 +0000;Merge branch '27-file-upload' into 'master' +Anton Romanov;Tue Oct 2 11:11:13 2018 +0400;Merge branch 'master' into 27-file-upload +Anton Romanov;Tue Oct 2 10:58:41 2018 +0400;add messages +Anton Romanov;Mon Oct 1 13:54:11 2018 +0400;add example for file uploading +Anton Romanov;Sat Sep 29 09:22:48 2018 +0400;minimize scripts +Anton Romanov;Thu Sep 27 21:32:16 2018 +0400;fix error pages +Anton Romanov;Thu Sep 27 16:01:02 2018 +0000;Merge branch 'refactor-ui' into 'master' +Anton Romanov;Thu Sep 27 12:11:25 2018 +0400;move pages +Anton Romanov;Thu Sep 27 10:35:22 2018 +0400;some refactor +Anton Romanov;Thu Sep 27 06:08:53 2018 +0000;Merge branch '3-ui' into 'master' +Anton Romanov;Thu Sep 27 10:03:00 2018 +0400;Merge branch 'master' into 3-ui +Anton Romanov;Thu Sep 27 10:02:49 2018 +0400;Merge branch 'master' into 3-ui +Alyona;Wed Sep 26 16:44:36 2018 +0400;Merge branch 'master' into 5-Page-of-paper +Alyona;Wed Sep 26 16:41:33 2018 +0400;Merge branch 'master' into 23-dashboard +Anton Romanov;Wed Sep 26 12:22:38 2018 +0000;Merge branch '23-dashboard' into 'master' +Alyona;Wed Sep 26 16:11:43 2018 +0400;Merge branch 'master' into 23-dashboard +Alyona;Wed Sep 26 16:11:19 2018 +0400;Merge branch 'master' into 23-dashboard +Alyona;Wed Sep 26 16:04:31 2018 +0400;add paper page +Anton Romanov;Wed Sep 26 11:55:20 2018 +0000;Merge branch '24-paper-page' into 'master' +Alyona;Tue Sep 25 17:13:01 2018 +0400;Alyona-Test +Elena;Tue Sep 25 16:45:01 2018 +0400;Тестовое задание +Anton Romanov;Sat Sep 22 06:56:33 2018 +0000;Update README.md +Anton Romanov;Sat Sep 8 10:19:21 2018 +0400;change ports +Anton Romanov;Sat Sep 8 10:07:21 2018 +0400;Merge remote-tracking branch 'origin/4-show-time-line' into 4-show-time-line +Anton Romanov;Sat Sep 8 09:54:14 2018 +0400;Merge branch 'master' into 4-show-time-line +Anton Romanov;Sat Sep 8 09:53:34 2018 +0400;path to timeline +Anton Romanov;Sat Sep 8 09:50:13 2018 +0400;fix papers font +Anton Romanov;Thu Sep 6 19:14:04 2018 +0000;Update README.md +Anton Romanov;Thu Sep 6 23:01:39 2018 +0400;add variable +Anton Romanov;Thu Sep 6 22:50:34 2018 +0400;deploy on vps +Anton Romanov;Wed Sep 5 21:58:04 2018 +0400;add timeline page +Anton Romanov;Wed Sep 5 14:50:49 2018 +0400;sort papers +Anton Romanov;Wed Sep 5 13:45:20 2018 +0400;fix paper colors +Anton Romanov;Wed Sep 5 13:31:51 2018 +0400;add method for create paper +Anton Romanov;Wed Sep 5 11:27:22 2018 +0400;get paper list +Anton Romanov;Sun Jun 10 13:43:40 2018 +0400;add timeline page +Anton Romanov;Sun Jun 10 01:53:32 2018 +0400;fix time +Anton Romanov;Sun Jun 10 01:49:38 2018 +0400;send notification +Anton Romanov;Sun Jun 10 01:12:28 2018 +0400;add event controller +Anton Romanov;Sat Jun 9 22:58:32 2018 +0400;remove liquibase +Anton Romanov;Sat Jun 9 22:41:00 2018 +0400;fix ports +Aleksey Filippov;Sun May 20 08:05:21 2018 +0200;delete orphaned files, migrate to webjars, some fixes and refactoring +Anton Romanov;Sat May 5 14:49:34 2018 +0400;add paper authors +Anton Romanov;Sat May 5 11:17:13 2018 +0400;add paper status +Anton Romanov;Sat May 5 11:14:37 2018 +0400;fix papers delete +Anton Romanov;Sat May 5 10:57:02 2018 +0400;fix papers crud +Anton Romanov;Sat May 5 00:39:18 2018 +0400;fix paper controller +Anton Romanov;Sat May 5 00:24:48 2018 +0400;add file uploading +Anton Romanov;Fri May 4 18:09:29 2018 +0400;add big logo +Anton Romanov;Fri May 4 18:04:09 2018 +0400;add paper controller +Anton Romanov;Fri May 4 17:54:46 2018 +0400;add ci +Anton Romanov;Fri May 4 17:23:22 2018 +0400;gitignore +Anton Romanov;Fri May 4 17:08:20 2018 +0400;add java code +Anton Romanov;Mon Apr 30 00:47:50 2018 +0400;add works +Anton Romanov;Sun Apr 29 20:48:05 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 20:46:54 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 20:44:47 2018 +0400;add paper page +Anton Romanov;Sun Apr 29 18:24:42 2018 +0400;add papers page +Anton Romanov;Sun Apr 29 17:09:58 2018 +0400;fix menu and sources +Anton Romanov;Sun Apr 29 00:49:37 2018 +0400;fix title page +Anton Romanov;Sat Apr 28 23:30:38 2018 +0400;fix title page +Anton Romanov;Sat Apr 28 22:50:26 2018 +0400;copy from landing diff --git a/src/main/resources/db/changelog-20190318_000001-schema.xml b/src/main/resources/db/changelog-20190318_000001-schema.xml new file mode 100644 index 0000000..98ec657 --- /dev/null +++ b/src/main/resources/db/changelog-20190318_000001-schema.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/main/resources/db/changelog-20190327_000000-schema.xml b/src/main/resources/db/changelog-20190327_000000-schema.xml new file mode 100644 index 0000000..4913072 --- /dev/null +++ b/src/main/resources/db/changelog-20190327_000000-schema.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190331_000000-schema.xml b/src/main/resources/db/changelog-20190331_000000-schema.xml new file mode 100644 index 0000000..76a7daa --- /dev/null +++ b/src/main/resources/db/changelog-20190331_000000-schema.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190331_000010-schema.xml b/src/main/resources/db/changelog-20190331_000010-schema.xml new file mode 100644 index 0000000..04bda75 --- /dev/null +++ b/src/main/resources/db/changelog-20190331_000010-schema.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-20190410_000000-schema.xml b/src/main/resources/db/changelog-20190410_000000-schema.xml new file mode 100644 index 0000000..b8db7f9 --- /dev/null +++ b/src/main/resources/db/changelog-20190410_000000-schema.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/db/changelog-20190418_000000-schema.xml b/src/main/resources/db/changelog-20190418_000000-schema.xml new file mode 100644 index 0000000..31cf21c --- /dev/null +++ b/src/main/resources/db/changelog-20190418_000000-schema.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog-master.xml b/src/main/resources/db/changelog-master.xml index 23440cf..be48ef2 100644 --- a/src/main/resources/db/changelog-master.xml +++ b/src/main/resources/db/changelog-master.xml @@ -19,6 +19,12 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/drivers/chromedriver b/src/main/resources/drivers/chromedriver new file mode 100644 index 0000000..02ff671 Binary files /dev/null and b/src/main/resources/drivers/chromedriver differ diff --git a/src/main/resources/drivers/chromedriver.exe b/src/main/resources/drivers/chromedriver.exe new file mode 100644 index 0000000..28a4067 Binary files /dev/null and b/src/main/resources/drivers/chromedriver.exe differ diff --git a/src/main/resources/drivers/geckodriver b/src/main/resources/drivers/geckodriver new file mode 100644 index 0000000..ba1da8c Binary files /dev/null and b/src/main/resources/drivers/geckodriver differ diff --git a/src/main/resources/drivers/geckodriver.exe b/src/main/resources/drivers/geckodriver.exe new file mode 100644 index 0000000..6208aa8 Binary files /dev/null and b/src/main/resources/drivers/geckodriver.exe differ diff --git a/src/main/resources/public/css/agency.css b/src/main/resources/public/css/agency.css index 24680aa..fb3ec24 100644 --- a/src/main/resources/public/css/agency.css +++ b/src/main/resources/public/css/agency.css @@ -1,839 +1,839 @@ -body { - overflow-x: hidden; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -p { - line-height: 1.75; -} - -a { - color: #000000; -} - -a:hover { - color: #fec503; -} - -.text-draft { - color: rgba(0, 0, 0, 0.48) !important; -} - -.text-primary { - color: #228bba !important; -} - -.text-warning { - color: #940000 !important; -} - -.text-review { - color: #94028d !important; -} - -.text-success { - color: #007741 !important; -} - -.text-accepted { - color: #fec503 !important; -} - -.text-not-accepted { - color: #A38831 !important; -} - -.text-failed { - color: #A38831 !important; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-weight: 700; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -section { - padding: 100px 0; -} - -section h2.section-heading { - font-size: 2.5vw; - margin-top: 0; - margin-bottom: 15px; -} - -section h3.section-subheading { - font-size: 16px; - font-weight: 400; - font-style: italic; - margin-bottom: 75px; - text-transform: none; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -@media (min-width: 768px) { - section { - padding: 100px 0; - } -} - -.btn { - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; -} - -.btn-xl { - font-size: 18px; - padding: 20px 40px; -} - -.btn-success { - background-color: #28a745; - border-color: #28a745; -} - -.btn-primary { - background-color: #fed136; - border-color: #fed136; -} - -.btn-default { - background-color: #ffffff; - border-color: #ced4da;; -} - -.btn-info { - background-color: #5bc0de; - border-color: #5bc0de; -} - -.progress-bar { - display: flex; - -ms-flex-direction: column; - flex-direction: column; - -ms-flex-pack: center; - justify-content: center; - color: #fff; - text-align: center; - white-space: nowrap; - background-color: #fed136; - transition: width .6s ease; -} - -.btn-primary:active, .btn-primary:focus, .btn-primary:hover { - background-color: #fec810 !important; - border-color: #fec810 !important; - color: white; -} - -.btn-primary:active, .btn-primary:focus { - box-shadow: 0 0 0 0.2rem rgba(254, 209, 55, 0.5) !important; -} - -::-moz-selection { - background: #fed136; - text-shadow: none; -} - -::selection { - background: #fed136; - text-shadow: none; -} - -img::selection { - background: transparent; -} - -img::-moz-selection { - background: transparent; -} - -#mainNav { - background-color: #212529; -} - -#mainNav .navbar-toggler { - font-size: 12px; - right: 0; - padding: 13px; - text-transform: uppercase; - color: white; - border: 0; - background-color: #fed136; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -#mainNav .navbar-brand { - color: #fed136; - font-family: 'Kaushan Script', 'Helvetica Neue', Helvetica, Arial, cursive; -} - -#mainNav .navbar-brand.active, #mainNav .navbar-brand:active, #mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover { - color: #fec503; -} - -#mainNav .navbar-nav .nav-item .nav-link { - font-size: 90%; - font-weight: 400; - padding: 0.75em 0; - letter-spacing: 1px; - color: white; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -#mainNav .navbar-nav .nav-item .nav-link.active, #mainNav .navbar-nav .nav-item .nav-link:hover { - color: #fed136; -} - -@media (min-width: 992px) { - #mainNav { - padding-top: 25px; - padding-bottom: 25px; - -webkit-transition: padding-top 0.3s, padding-bottom 0.3s; - -moz-transition: padding-top 0.3s, padding-bottom 0.3s; - transition: padding-top 0.3s, padding-bottom 0.3s; - border: none; - background-color: transparent; - } - - #mainNav .navbar-brand { - font-size: 1.75em; - -webkit-transition: all 0.3s; - -moz-transition: all 0.3s; - transition: all 0.3s; - } - - #mainNav .navbar-nav .nav-item .nav-link { - padding: 1.1em 1em !important; - } - - #mainNav.navbar-shrink { - padding-top: 0; - padding-bottom: 0; - background-color: #212529; - } - - #mainNav.navbar-shrink .navbar-brand { - font-size: 1.25em; - padding: 12px 0; - } -} - -header.masthead { - text-align: center; - color: white; - background-image: url("../img/header-bg.jpg"); - background-repeat: no-repeat; - background-attachment: scroll; - background-position: center center; - -webkit-background-size: cover; - -moz-background-size: cover; - -o-background-size: cover; - background-size: cover; -} - -header.masthead .intro-text { - padding-top: 150px; - padding-bottom: 100px; -} - -header.masthead .intro-text .intro-lead-in { - font-size: 22px; - font-style: italic; - line-height: 22px; - margin-bottom: 25px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -header.masthead .intro-text .intro-heading { - font-size: 50px; - font-weight: 700; - line-height: 50px; - margin-bottom: 25px; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -@media (min-width: 768px) { - header.masthead .intro-text { - padding-top: 300px; - padding-bottom: 200px; - } - - header.masthead .intro-text .intro-lead-in { - font-size: 40px; - font-style: italic; - line-height: 40px; - margin-bottom: 25px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; - } - - header.masthead .intro-text .intro-heading { - font-size: 75px; - font-weight: 700; - line-height: 75px; - margin-bottom: 50px; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; - } -} - -.service-heading { - margin: 15px 0; - text-transform: none; -} - -#portfolio .portfolio-item { - right: 0; - margin: 0 0 15px; -} - -#portfolio .portfolio-item .portfolio-link { - position: relative; - display: block; - max-width: 400px; - margin: 0 auto; - cursor: pointer; -} - -#portfolio .portfolio-item .portfolio-link .portfolio-hover { - position: absolute; - width: 100%; - height: 100%; - -webkit-transition: all ease 0.5s; - -moz-transition: all ease 0.5s; - transition: all ease 0.5s; - opacity: 0; - background: rgba(254, 209, 54, 0.9); -} - -#portfolio .portfolio-item .portfolio-link .portfolio-hover:hover { - opacity: 1; -} - -#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content { - font-size: 20px; - position: absolute; - top: 50%; - width: 100%; - height: 20px; - margin-top: -12px; - text-align: center; - color: white; -} - -#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content i { - margin-top: -12px; -} - -#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h3, -#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h4 { - margin: 0; -} - -#portfolio .portfolio-item .portfolio-caption { - max-width: 400px; - margin: 0 auto; - padding: 25px; - text-align: center; - background-color: #fff; -} - -#portfolio .portfolio-item .portfolio-caption h4 { - margin: 0; - text-transform: none; -} - -#portfolio .portfolio-item .portfolio-caption p { - font-size: 16px; - font-style: italic; - margin: 0; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -#portfolio * { - z-index: 2; -} - -@media (min-width: 767px) { - #portfolio .portfolio-item { - margin: 0 0 30px; - } -} - -.portfolio-modal { - padding-right: 0px !important; -} - -.portfolio-modal .modal-dialog { - margin: 1rem; - max-width: 100vw; -} - -.portfolio-modal .modal-content { - padding: 100px 0; - text-align: center; -} - -.portfolio-modal .modal-content h2 { - font-size: 3em; - margin-bottom: 15px; -} - -.portfolio-modal .modal-content p { - margin-bottom: 30px; -} - -.portfolio-modal .modal-content p.item-intro { - font-size: 16px; - font-style: italic; - margin: 20px 0 30px; - font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -.portfolio-modal .modal-content ul.list-inline { - margin-top: 0; - margin-bottom: 30px; -} - -.portfolio-modal .modal-content img { - margin-bottom: 30px; -} - -.portfolio-modal .modal-content button { - cursor: pointer; -} - -.portfolio-modal .close-modal { - position: absolute; - top: 25px; - right: 25px; - width: 75px; - height: 75px; - cursor: pointer; - background-color: transparent; -} - -.portfolio-modal .close-modal:hover { - opacity: 0.3; -} - -.portfolio-modal .close-modal .lr { - /* Safari and Chrome */ - z-index: 1051; - width: 1px; - height: 75px; - margin-left: 35px; - /* IE 9 */ - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); - background-color: #212529; -} - -.portfolio-modal .close-modal .lr .rl { - /* Safari and Chrome */ - z-index: 1052; - width: 1px; - height: 75px; - /* IE 9 */ - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - background-color: #212529; -} - -.timeline { - position: relative; - padding: 0; - list-style: none; -} - -.timeline:before { - position: absolute; - top: 0; - bottom: 0; - left: 40px; - width: 2px; - margin-left: -1.5px; - content: ''; - background-color: #e9ecef; -} - -.timeline > li { - position: relative; - min-height: 50px; - margin-bottom: 50px; -} - -.timeline > li:after, .timeline > li:before { - display: table; - content: ' '; -} - -.timeline > li:after { - clear: both; -} - -.timeline > li .timeline-panel { - position: relative; - float: right; - width: 100%; - padding: 0 20px 0 100px; - text-align: left; -} - -.timeline > li .timeline-panel:before { - right: auto; - left: -15px; - border-right-width: 15px; - border-left-width: 0; -} - -.timeline > li .timeline-panel:after { - right: auto; - left: -14px; - border-right-width: 14px; - border-left-width: 0; -} - -.timeline > li .timeline-image { - position: absolute; - z-index: 100; - left: 0; - width: 80px; - height: 80px; - margin-left: 0; - text-align: center; - color: white; - border: 7px solid #e9ecef; - border-radius: 100%; - background-color: #fed136; -} - -.timeline > li .timeline-image h4 { - font-size: 10px; - line-height: 14px; - margin-top: 12px; -} - -.timeline > li.timeline-inverted > .timeline-panel { - float: right; - padding: 0 20px 0 100px; - text-align: left; -} - -.timeline > li.timeline-inverted > .timeline-panel:before { - right: auto; - left: -15px; - border-right-width: 15px; - border-left-width: 0; -} - -.timeline > li.timeline-inverted > .timeline-panel:after { - right: auto; - left: -14px; - border-right-width: 14px; - border-left-width: 0; -} - -.timeline > li:last-child { - margin-bottom: 0; -} - -.timeline .timeline-heading h4 { - margin-top: 0; - color: inherit; -} - -.timeline .timeline-heading h4.subheading { - text-transform: none; -} - -.timeline .timeline-body > ul, -.timeline .timeline-body > p { - margin-bottom: 0; -} - -@media (min-width: 768px) { - .timeline:before { - left: 50%; - } - - .timeline > li { - min-height: 100px; - margin-bottom: 100px; - } - - .timeline > li .timeline-panel { - float: left; - width: 41%; - padding: 0 20px 20px 30px; - text-align: right; - } - - .timeline > li .timeline-image { - left: 50%; - width: 100px; - height: 100px; - margin-left: -50px; - } - - .timeline > li .timeline-image h4 { - font-size: 13px; - line-height: 18px; - margin-top: 16px; - } - - .timeline > li.timeline-inverted > .timeline-panel { - float: right; - padding: 0 30px 20px 20px; - text-align: left; - } -} - -@media (min-width: 992px) { - .timeline > li { - min-height: 150px; - } - - .timeline > li .timeline-panel { - padding: 0 20px 20px; - } - - .timeline > li .timeline-image { - width: 150px; - height: 150px; - margin-left: -75px; - } - - .timeline > li .timeline-image h4 { - font-size: 18px; - line-height: 26px; - margin-top: 30px; - } - - .timeline > li.timeline-inverted > .timeline-panel { - padding: 0 20px 20px; - } -} - -@media (min-width: 1200px) { - .timeline > li { - min-height: 170px; - } - - .timeline > li .timeline-panel { - padding: 0 20px 20px 100px; - } - - .timeline > li .timeline-image { - width: 170px; - height: 170px; - margin-left: -85px; - } - - .timeline > li .timeline-image h4 { - margin-top: 40px; - } - - .timeline > li.timeline-inverted > .timeline-panel { - padding: 0 100px 20px 20px; - } -} - -.team-member { - margin-bottom: 50px; - text-align: center; -} - -.team-member img { - width: 225px; - height: 225px; - border: 7px solid #fff; -} - -.team-member h4 { - margin-top: 25px; - margin-bottom: 0; - text-transform: none; -} - -.team-member p { - margin-top: 0; -} - -section#contact { - background-color: #212529; - background-image: url("../img/map-image.png"); - background-repeat: no-repeat; - background-position: center; -} - -section#contact .section-heading { - color: #fff; -} - -section#contact .form-group { - margin-bottom: 25px; -} - -section#contact .form-group input, -section#contact .form-group textarea { - padding: 20px; -} - -section#contact .form-group input.form-control { - height: auto; -} - -section#contact .form-group textarea.form-control { - height: 248px; -} - -section#contact .form-control:focus { - border-color: #fed136; - box-shadow: none; -} - -section#contact ::-webkit-input-placeholder { - font-weight: 700; - color: #ced4da; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -section#contact :-moz-placeholder { - font-weight: 700; - color: #ced4da; - /* Firefox 18- */ - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -section#contact ::-moz-placeholder { - font-weight: 700; - color: #ced4da; - /* Firefox 19+ */ - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -section#contact :-ms-input-placeholder { - font-weight: 700; - color: #ced4da; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -footer { - padding: 25px 0; - text-align: center; -} - -footer span.copyright { - font-size: 90%; - line-height: 40px; - text-transform: none; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -footer ul.quicklinks { - font-size: 90%; - line-height: 40px; - margin-bottom: 0; - text-transform: none; - font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -ul.social-buttons { - margin-bottom: 0; -} - -ul.social-buttons li a { - font-size: 20px; - line-height: 40px; - display: block; - width: 40px; - height: 40px; - -webkit-transition: all 0.3s; - -moz-transition: all 0.3s; - transition: all 0.3s; - color: white; - border-radius: 100%; - outline: none; - background-color: #212529; -} - -ul.social-buttons li a:active, ul.social-buttons li a:focus, ul.social-buttons li a:hover { - background-color: #fed136; -} - -.dashboard-card { - background: #ffffff none repeat scroll 0 0; - margin: 15px; - padding: 20px; - border: 0 solid #e7e7e7; - border-radius: 5px; - box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); -} - -.toolbar-button { - width: 100%; - margin: 5px; -} - -/* --------------------------------------------------- - FEEDBACK STYLE ------------------------------------------------------ */ -.feedback-panel { - list-style-type: none; - padding: 5px; -} - -.feedback-panel .alert { - padding: 5px; - margin-bottom: 5px; - cursor: pointer; -} - -/* --------------------------------------------------- - MEDIAQUERIES ------------------------------------------------------ */ -@media (min-width: 768px) { - section h2.section-heading { - font-size: 3.5vw; - margin-top: 0; - margin-bottom: 15px; - } -} - -@media (min-width: 1500px) { - section h2.section-heading { - font-size: 1.5vw; - margin-top: 0; - margin-bottom: 15px; - } -} - -@media (min-width: 768px) { - .feedback-panel { - position: fixed; - overflow-y: hidden; - max-height: calc(100vh - 60px); - width: 250px; - top: 50px; - right: 15px; - opacity: .8; - z-index: 10000; - } - - .feedback-panel .alert { - padding: 10px; - margin-bottom: 10px; - overflow: hidden; - } - - .feedback-panel .alert:hover { - border-color: #888; - } +body { + overflow-x: hidden; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +p { + line-height: 1.75; +} + +a { + color: #000000; +} + +a:hover { + color: #fec503; +} + +.text-draft { + color: rgba(0, 0, 0, 0.48) !important; +} + +.text-primary { + color: #228bba !important; +} + +.text-warning { + color: #940000 !important; +} + +.text-review { + color: #94028d !important; +} + +.text-success { + color: #007741 !important; +} + +.text-accepted { + color: #fec503 !important; +} + +.text-not-accepted { + color: #A38831 !important; +} + +.text-failed { + color: #A38831 !important; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 700; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +section { + padding: 100px 0; +} + +section h2.section-heading { + font-size: 2.5vw; + margin-top: 0; + margin-bottom: 15px; +} + +section h3.section-subheading { + font-size: 16px; + font-weight: 400; + font-style: italic; + margin-bottom: 75px; + text-transform: none; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +@media (min-width: 768px) { + section { + padding: 100px 0; + } +} + +.btn { + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; +} + +.btn-xl { + font-size: 18px; + padding: 20px 40px; +} + +.btn-success { + background-color: #28a745; + border-color: #28a745; +} + +.btn-primary { + background-color: #fed136; + border-color: #fed136; +} + +.btn-default { + background-color: #ffffff; + border-color: #ced4da;; +} + +.btn-info { + background-color: #5bc0de; + border-color: #5bc0de; +} + +.progress-bar { + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #fed136; + transition: width .6s ease; +} + +.btn-primary:active, .btn-primary:focus, .btn-primary:hover { + background-color: #fec810 !important; + border-color: #fec810 !important; + color: white; +} + +.btn-primary:active, .btn-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(254, 209, 55, 0.5) !important; +} + +::-moz-selection { + background: #fed136; + text-shadow: none; +} + +::selection { + background: #fed136; + text-shadow: none; +} + +img::selection { + background: transparent; +} + +img::-moz-selection { + background: transparent; +} + +#mainNav { + background-color: #212529; +} + +#mainNav .navbar-toggler { + font-size: 12px; + right: 0; + padding: 13px; + text-transform: uppercase; + color: white; + border: 0; + background-color: #fed136; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +#mainNav .navbar-brand { + color: #fed136; + font-family: 'Kaushan Script', 'Helvetica Neue', Helvetica, Arial, cursive; +} + +#mainNav .navbar-brand.active, #mainNav .navbar-brand:active, #mainNav .navbar-brand:focus, #mainNav .navbar-brand:hover { + color: #fec503; +} + +#mainNav .navbar-nav .nav-item .nav-link { + font-size: 90%; + font-weight: 400; + padding: 0.75em 0; + letter-spacing: 1px; + color: white; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +#mainNav .navbar-nav .nav-item .nav-link.active, #mainNav .navbar-nav .nav-item .nav-link:hover { + color: #fed136; +} + +@media (min-width: 992px) { + #mainNav { + padding-top: 25px; + padding-bottom: 25px; + -webkit-transition: padding-top 0.3s, padding-bottom 0.3s; + -moz-transition: padding-top 0.3s, padding-bottom 0.3s; + transition: padding-top 0.3s, padding-bottom 0.3s; + border: none; + background-color: transparent; + } + + #mainNav .navbar-brand { + font-size: 1.75em; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; + } + + #mainNav .navbar-nav .nav-item .nav-link { + padding: 1.1em 1em !important; + } + + #mainNav.navbar-shrink { + padding-top: 0; + padding-bottom: 0; + background-color: #212529; + } + + #mainNav.navbar-shrink .navbar-brand { + font-size: 1.25em; + padding: 12px 0; + } +} + +header.masthead { + text-align: center; + color: white; + background-image: url("../img/header-bg.jpg"); + background-repeat: no-repeat; + background-attachment: scroll; + background-position: center center; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +header.masthead .intro-text { + padding-top: 150px; + padding-bottom: 100px; +} + +header.masthead .intro-text .intro-lead-in { + font-size: 22px; + font-style: italic; + line-height: 22px; + margin-bottom: 25px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +header.masthead .intro-text .intro-heading { + font-size: 50px; + font-weight: 700; + line-height: 50px; + margin-bottom: 25px; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +@media (min-width: 768px) { + header.masthead .intro-text { + padding-top: 300px; + padding-bottom: 200px; + } + + header.masthead .intro-text .intro-lead-in { + font-size: 40px; + font-style: italic; + line-height: 40px; + margin-bottom: 25px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; + } + + header.masthead .intro-text .intro-heading { + font-size: 75px; + font-weight: 700; + line-height: 75px; + margin-bottom: 50px; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; + } +} + +.service-heading { + margin: 15px 0; + text-transform: none; +} + +#portfolio .portfolio-item { + right: 0; + margin: 0 0 15px; +} + +#portfolio .portfolio-item .portfolio-link { + position: relative; + display: block; + max-width: 400px; + margin: 0 auto; + cursor: pointer; +} + +#portfolio .portfolio-item .portfolio-link .portfolio-hover { + position: absolute; + width: 100%; + height: 100%; + -webkit-transition: all ease 0.5s; + -moz-transition: all ease 0.5s; + transition: all ease 0.5s; + opacity: 0; + background: rgba(254, 209, 54, 0.9); +} + +#portfolio .portfolio-item .portfolio-link .portfolio-hover:hover { + opacity: 1; +} + +#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content { + font-size: 20px; + position: absolute; + top: 50%; + width: 100%; + height: 20px; + margin-top: -12px; + text-align: center; + color: white; +} + +#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content i { + margin-top: -12px; +} + +#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h3, +#portfolio .portfolio-item .portfolio-link .portfolio-hover .portfolio-hover-content h4 { + margin: 0; +} + +#portfolio .portfolio-item .portfolio-caption { + max-width: 400px; + margin: 0 auto; + padding: 25px; + text-align: center; + background-color: #fff; +} + +#portfolio .portfolio-item .portfolio-caption h4 { + margin: 0; + text-transform: none; +} + +#portfolio .portfolio-item .portfolio-caption p { + font-size: 16px; + font-style: italic; + margin: 0; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +#portfolio * { + z-index: 2; +} + +@media (min-width: 767px) { + #portfolio .portfolio-item { + margin: 0 0 30px; + } +} + +.portfolio-modal { + padding-right: 0px !important; +} + +.portfolio-modal .modal-dialog { + margin: 1rem; + max-width: 100vw; +} + +.portfolio-modal .modal-content { + padding: 100px 0; + text-align: center; +} + +.portfolio-modal .modal-content h2 { + font-size: 3em; + margin-bottom: 15px; +} + +.portfolio-modal .modal-content p { + margin-bottom: 30px; +} + +.portfolio-modal .modal-content p.item-intro { + font-size: 16px; + font-style: italic; + margin: 20px 0 30px; + font-family: 'Droid Serif', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.portfolio-modal .modal-content ul.list-inline { + margin-top: 0; + margin-bottom: 30px; +} + +.portfolio-modal .modal-content img { + margin-bottom: 30px; +} + +.portfolio-modal .modal-content button { + cursor: pointer; +} + +.portfolio-modal .close-modal { + position: absolute; + top: 25px; + right: 25px; + width: 75px; + height: 75px; + cursor: pointer; + background-color: transparent; +} + +.portfolio-modal .close-modal:hover { + opacity: 0.3; +} + +.portfolio-modal .close-modal .lr { + /* Safari and Chrome */ + z-index: 1051; + width: 1px; + height: 75px; + margin-left: 35px; + /* IE 9 */ + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background-color: #212529; +} + +.portfolio-modal .close-modal .lr .rl { + /* Safari and Chrome */ + z-index: 1052; + width: 1px; + height: 75px; + /* IE 9 */ + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + background-color: #212529; +} + +.timeline { + position: relative; + padding: 0; + list-style: none; +} + +.timeline:before { + position: absolute; + top: 0; + bottom: 0; + left: 40px; + width: 2px; + margin-left: -1.5px; + content: ''; + background-color: #e9ecef; +} + +.timeline > li { + position: relative; + min-height: 50px; + margin-bottom: 50px; +} + +.timeline > li:after, .timeline > li:before { + display: table; + content: ' '; +} + +.timeline > li:after { + clear: both; +} + +.timeline > li .timeline-panel { + position: relative; + float: right; + width: 100%; + padding: 0 20px 0 100px; + text-align: left; +} + +.timeline > li .timeline-panel:before { + right: auto; + left: -15px; + border-right-width: 15px; + border-left-width: 0; +} + +.timeline > li .timeline-panel:after { + right: auto; + left: -14px; + border-right-width: 14px; + border-left-width: 0; +} + +.timeline > li .timeline-image { + position: absolute; + z-index: 100; + left: 0; + width: 80px; + height: 80px; + margin-left: 0; + text-align: center; + color: white; + border: 7px solid #e9ecef; + border-radius: 100%; + background-color: #fed136; +} + +.timeline > li .timeline-image h4 { + font-size: 10px; + line-height: 14px; + margin-top: 12px; +} + +.timeline > li.timeline-inverted > .timeline-panel { + float: right; + padding: 0 20px 0 100px; + text-align: left; +} + +.timeline > li.timeline-inverted > .timeline-panel:before { + right: auto; + left: -15px; + border-right-width: 15px; + border-left-width: 0; +} + +.timeline > li.timeline-inverted > .timeline-panel:after { + right: auto; + left: -14px; + border-right-width: 14px; + border-left-width: 0; +} + +.timeline > li:last-child { + margin-bottom: 0; +} + +.timeline .timeline-heading h4 { + margin-top: 0; + color: inherit; +} + +.timeline .timeline-heading h4.subheading { + text-transform: none; +} + +.timeline .timeline-body > ul, +.timeline .timeline-body > p { + margin-bottom: 0; +} + +@media (min-width: 768px) { + .timeline:before { + left: 50%; + } + + .timeline > li { + min-height: 100px; + margin-bottom: 100px; + } + + .timeline > li .timeline-panel { + float: left; + width: 41%; + padding: 0 20px 20px 30px; + text-align: right; + } + + .timeline > li .timeline-image { + left: 50%; + width: 100px; + height: 100px; + margin-left: -50px; + } + + .timeline > li .timeline-image h4 { + font-size: 13px; + line-height: 18px; + margin-top: 16px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + float: right; + padding: 0 30px 20px 20px; + text-align: left; + } +} + +@media (min-width: 992px) { + .timeline > li { + min-height: 150px; + } + + .timeline > li .timeline-panel { + padding: 0 20px 20px; + } + + .timeline > li .timeline-image { + width: 150px; + height: 150px; + margin-left: -75px; + } + + .timeline > li .timeline-image h4 { + font-size: 18px; + line-height: 26px; + margin-top: 30px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + padding: 0 20px 20px; + } +} + +@media (min-width: 1200px) { + .timeline > li { + min-height: 170px; + } + + .timeline > li .timeline-panel { + padding: 0 20px 20px 100px; + } + + .timeline > li .timeline-image { + width: 170px; + height: 170px; + margin-left: -85px; + } + + .timeline > li .timeline-image h4 { + margin-top: 40px; + } + + .timeline > li.timeline-inverted > .timeline-panel { + padding: 0 100px 20px 20px; + } +} + +.team-member { + margin-bottom: 50px; + text-align: center; +} + +.team-member img { + width: 225px; + height: 225px; + border: 7px solid #fff; +} + +.team-member h4 { + margin-top: 25px; + margin-bottom: 0; + text-transform: none; +} + +.team-member p { + margin-top: 0; +} + +section#contact { + background-color: #212529; + background-image: url("../img/map-image.png"); + background-repeat: no-repeat; + background-position: center; +} + +section#contact .section-heading { + color: #fff; +} + +section#contact .form-group { + margin-bottom: 25px; +} + +section#contact .form-group input, +section#contact .form-group textarea { + padding: 20px; +} + +section#contact .form-group input.form-control { + height: auto; +} + +section#contact .form-group textarea.form-control { + height: 248px; +} + +section#contact .form-control:focus { + border-color: #fed136; + box-shadow: none; +} + +section#contact ::-webkit-input-placeholder { + font-weight: 700; + color: #ced4da; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +section#contact :-moz-placeholder { + font-weight: 700; + color: #ced4da; + /* Firefox 18- */ + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +section#contact ::-moz-placeholder { + font-weight: 700; + color: #ced4da; + /* Firefox 19+ */ + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +section#contact :-ms-input-placeholder { + font-weight: 700; + color: #ced4da; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +footer { + padding: 25px 0; + text-align: center; +} + +footer span.copyright { + font-size: 90%; + line-height: 40px; + text-transform: none; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +footer ul.quicklinks { + font-size: 90%; + line-height: 40px; + margin-bottom: 0; + text-transform: none; + font-family: 'Montserrat', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +ul.social-buttons { + margin-bottom: 0; +} + +ul.social-buttons li a { + font-size: 20px; + line-height: 40px; + display: block; + width: 40px; + height: 40px; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + transition: all 0.3s; + color: white; + border-radius: 100%; + outline: none; + background-color: #212529; +} + +ul.social-buttons li a:active, ul.social-buttons li a:focus, ul.social-buttons li a:hover { + background-color: #fed136; +} + +.dashboard-card { + background: #ffffff none repeat scroll 0 0; + margin: 15px; + padding: 20px; + border: 0 solid #e7e7e7; + border-radius: 5px; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.toolbar-button { + width: 100%; + margin: 5px; +} + +/* --------------------------------------------------- + FEEDBACK STYLE +----------------------------------------------------- */ +.feedback-panel { + list-style-type: none; + padding: 5px; +} + +.feedback-panel .alert { + padding: 5px; + margin-bottom: 5px; + cursor: pointer; +} + +/* --------------------------------------------------- + MEDIAQUERIES +----------------------------------------------------- */ +@media (min-width: 768px) { + section h2.section-heading { + font-size: 3.5vw; + margin-top: 0; + margin-bottom: 15px; + } +} + +@media (min-width: 1500px) { + section h2.section-heading { + font-size: 1.5vw; + margin-top: 0; + margin-bottom: 15px; + } +} + +@media (min-width: 768px) { + .feedback-panel { + position: fixed; + overflow-y: hidden; + max-height: calc(100vh - 60px); + width: 250px; + top: 50px; + right: 15px; + opacity: .8; + z-index: 10000; + } + + .feedback-panel .alert { + padding: 10px; + margin-bottom: 10px; + overflow: hidden; + } + + .feedback-panel .alert:hover { + border-color: #888; + } } \ No newline at end of file diff --git a/src/main/resources/public/css/conference.css b/src/main/resources/public/css/conference.css index 06d8041..fa3666e 100644 --- a/src/main/resources/public/css/conference.css +++ b/src/main/resources/public/css/conference.css @@ -1,94 +1,138 @@ -.col-lg-12 a{ - position: absolute; - font-size: smaller; +body { + min-width: 400px; } -.col-lg-12 a img{ - width: 36px; - height: 33px; +.conference-row .col:hover { + background-color: #eaeaea; + border-radius: .25rem; +} + +.filter-option-inner-inner { + color: white; } -.form-group textarea{ + + + +.form-group textarea { min-height: 206px; max-height: 463px; } - -.deadline-list{ +.deadline-list { height: 200px; padding: 0px; overflow-y: scroll; } -.deadline{ +.deadline { margin: 0; + height: 40px; + min-height: 40px; } -.deadline-text{ +.deadline-text { flex: 1; } -.member-list{ +.member-list { height: 200px; padding: 0px; overflow-y: scroll; } -.member{ +.member { margin: 0; } -.paper-list{ +.paper-list { height: 200px; padding: 0px; overflow-y: scroll; } -.paper{ +.paper { margin: 0; + min-height: 40px; + height: 40px; } -.paper-name{ +.paper-name { flex: 1; } -.paper-name span{ +.paper-name span { margin: 6px 15px; display: inline-block; } -.icon{ +.icon { width: 38px; height: 38px; padding: 2px; cursor: pointer; } -.icon-delete{ +.icon-delete { background-color: #f44; + background-image: url(/img/conference/delete.png); + background-repeat: round; + color: transparent !important; +} + +.icon-delete:hover { + background-color: #ff2929; + transition: background-color .15s ease-in-out; } -.icon-paper{ - height: 26px; - width: 26px; - float: right; - margin: 5px; +.icon-paper { + height: 26px; + width: 26px; + float: right; + margin: 5px; } -.grey-border{ +.grey-border { color: #495057; background-clip: padding-box; border: 1px solid #ced4da; border-radius: .25rem; - transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; -} - -#add-paper{ - margin-left: 10px; + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; } -#cancel-button{ +#cancel-button { position: relative; font-size: 1rem; } +.paper-control button { + margin: 0 0 10px 10px; + float: right; +} + +@media (max-width: 1199px) and (min-width: 768px){ + .paper-control { + display: block!important; + } +} + +@media (max-width: 991px) { + + .dates-panel { + display: block!important; + } + + .date { + margin-bottom: 10px; + } +} + +@media (max-width: 768px) { + .dashboard-elements { + display: block!important; + } + + .dashboard-right { + margin-bottom: 10px; + } +} diff --git a/src/main/resources/public/css/odin.css b/src/main/resources/public/css/odin.css new file mode 100644 index 0000000..2bfeb75 --- /dev/null +++ b/src/main/resources/public/css/odin.css @@ -0,0 +1,226 @@ +.odin-unselectable { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.odin-kill-padding { + padding: 0; +} + +.odin-input-group { + padding-left: 15px !important; + padding-right: 15px !important; +} + +.odin-closable .fa { + font-size: 1.5em; + margin-top: -1px; +} + +.odin-closable .fa:hover:before { + content: "\f057"; +} + +/* + Odin Toolbar +*/ +.odin-toolbar { + padding-left: 1px; + padding-bottom: 4px; +} + +.odin-toolbar .odin-btn { + float: none !important; +} + +.odin-btn { + min-width: 112px; + margin-right: 3px; +} + +@media (min-width: 768px) { + .odin-btn { + min-width: 150px; + } +} + +/* + Odin Paginator +*/ +.odin-paginator { + margin: 0; + margin-top: 5px; + text-align: center; +} + +.odin-paginator-content { + display: inline-block; +} + +.odin-paginator-content a { + cursor: pointer; + color: black; + float: left; + padding: 6px 16px; + text-decoration: none; + transition: background-color .3s; + border-radius: 4px; +} + +.odin-paginator-content i { + color: black; + float: left; + padding: 6px 16px; +} + +.odin-paginator-content a.active { + background-color: #4CAF50; + color: white; +} + +.odin-paginator-content a:hover:not(.active) { + background-color: #ddd; +} + +/* + Odin Formatters +*/ +.odin-negative { + color: red; + font-weight: bold; +} + +/* + Odin Table +*/ +.odin-table { + min-height: 324px; + border: 1px solid #ddd; + padding: 0; + margin: 0 0 0 1px; + background-color: #f8f8f8; +} + +.odin-table > table { + margin-bottom: 0; +} + +.odin-table > table > tbody > tr { + background-color: #fff; +} + +.odin-table-pointed-line { + cursor: pointer; +} + +.odin-table-selected-line { + background-color: #5bc0de !important; +} + +.odin-table-selected-line:hover { + background-color: #6bd0ee !important; +} + +/* + Odin Form + */ +.odin-form { + display: none; + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1500; + background: rgba(0, 0, 0, 0.5); +} + +.odin-form .panel { + position: relative; + max-height: 95%; + max-width: 95%; + overflow: auto; +} + +@media (min-width: 768px) { + .odin-form .panel { + max-width: 55%; + } +} + +.odin-form .panel-footer { + padding: 5px 7px; +} + +.odin-form .odin-btn { + float: right; +} + +.odin-checkbox { + width: 20px; + height: 20px; +} + +.odin-form .tab-pane { + padding-top: 5px; +} + +/* + Odin Confirm Box + */ +.odin-confirm-box { + display: none; + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 2000; + background: rgba(0, 0, 0, 0.5); +} + +.odin-confirm-box .panel { + position: relative; + max-width: 95%; +} + +.odin-confirm-box .panel-footer { + text-align: center; +} + +@media (min-width: 768px) { + .odin-confirm-box .panel { + max-width: 25%; + } +} + +.odin-confirm-box .panel-body { + text-align: center; +} + +/* + Odin Table Box + */ +.odin-table-box { + z-index: 2500; +} + +/* + Odin Simple Box + */ + +@media (min-width: 768px) { + .odin-simple-form .panel { + max-width: 35%; + } +} \ No newline at end of file diff --git a/src/main/resources/public/css/tasks.css b/src/main/resources/public/css/tasks.css new file mode 100644 index 0000000..6d18c99 --- /dev/null +++ b/src/main/resources/public/css/tasks.css @@ -0,0 +1,65 @@ +.tags-container { + width: 100%; + padding: .375rem .75rem; + background-color: #fff; + border: 1px solid #ccc; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + display: inline-block; + color: #555; + vertical-align: middle; + border-radius: 4px; + max-width: 100%; + line-height: 22px; + cursor: text; +} + +.input-tag-name { + border: none; + box-shadow: none; + outline: none; + background-color: transparent; + padding: 0 6px; + margin: 0; + width: auto; + max-width: inherit; +} + +.tag { + display: inline-block; + padding: .2em .6em .3em; + background-color: orange; + border-radius: .25em; + margin-right: 4px; + margin-bottom: 4px; + + font-size: 75%; + font-weight: 700; + line-height: 1.5; + color: #fff; +} + +.tag-name span[data-role="remove"] { + margin-left: 8px; + cursor: pointer; +} + +.tag-name span[data-role="remove"]:after { + content: "x"; + padding: 0px 2px; +} + +.tag-name input[type="text"] { + background: transparent; + border: none; + display: inline-flex; + font-size: 100%; + font-weight: 700; + line-height: 1.5; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + outline: none; + cursor: default; + +} diff --git a/src/main/resources/public/js/conference.js b/src/main/resources/public/js/conference.js new file mode 100644 index 0000000..dd843ca --- /dev/null +++ b/src/main/resources/public/js/conference.js @@ -0,0 +1,41 @@ +$(document).ready(function () { + + $(".conference-row").mouseenter(function (event) { + var conferenceRow = $(event.target).closest(".conference-row"); + $(conferenceRow).css("background-color", "#f8f9fa"); + $(conferenceRow).find(".remove-conference").removeClass("d-none"); + + }); + $(".conference-row").mouseleave(function (event) { + var conferenceRow = $(event.target).closest(".conference-row"); + $(conferenceRow).css("background-color", "white"); + $(conferenceRow).closest(".conference-row").find(".remove-conference").addClass("d-none"); + }); + + $('a[data-confirm]').click(function(ev) { + var href = $(this).attr('href'); + if (!$('#dataConfirmModal').length) { + $('#modalDelete').append(''); + } + $('#dataConfirmModal').find('#myModalLabel').text($(this).attr('data-confirm')); + $('#dataConfirmOK').attr('href', href); + $('#dataConfirmModal').modal({show:true}); + return false; + }); +}); diff --git a/src/main/resources/public/js/config.js b/src/main/resources/public/js/config.js new file mode 100644 index 0000000..50d1b40 --- /dev/null +++ b/src/main/resources/public/js/config.js @@ -0,0 +1,13 @@ +/* exported contextPath */ +var contextPath = ""; +var apiVersion = "/api/1.0"; + +var basePath = contextPath + apiVersion; + +/* exported uiLocale */ +var uiLocale = "ru"; + +/* exported urlVersions */ +var urlVersions = basePath + "/versions"; +/* exported urlCommits */ +var urlCommits = basePath + "/commits"; \ No newline at end of file diff --git a/src/main/resources/public/js/core.js b/src/main/resources/public/js/core.js index 78256e0..58e68f4 100644 --- a/src/main/resources/public/js/core.js +++ b/src/main/resources/public/js/core.js @@ -89,20 +89,18 @@ function getFromRest(url, callBack, errorCallBack) { } /* exported getFromRestWithVersion */ -function getFromRestWithVersion(url, callBack, errorCallBack) { - getFromRestWithVersionAndParams(url, "", callBack, errorCallBack); +function getFromRest(url, callBack, errorCallBack) { + getFromRestWithParams(url, "", callBack, errorCallBack); } /* exported getFromRestWithVersionAndParams */ -function getFromRestWithVersionAndParams(url, params, callBack, errorCallBack) { - getCurrentVersion(function (version) { - $.ajax({ - url: url + "?versionId=" + version + params, - cache: false, - success: function (response) { - errorHandler(response, callBack, errorCallBack); - } - }); +function getFromRestWithParams(url, params, callBack, errorCallBack) { + $.ajax({ + url: url + "?" + params, + cache: false, + success: function (response) { + errorHandler(response, callBack, errorCallBack); + } }); } @@ -192,32 +190,6 @@ function deleteFromRest(url, callBack, completeCallback, errorCallBack) { }); } -/* exported getCurrentVersion */ -function getCurrentVersion(callBack, errorCallBack) { - var version = localStorage.getItem("currentVersion"); - - if (($("#select-versions")[0].options.length > 0) - && ($("#select-versions option[value='" + version + "']").length === 0)) { - localStorage.removeItem("currentVersion"); - version = ""; - } - - if (isEmpty(version)) { - getFromRest(urlVersions, - function (versions) { - if (isEmpty(versions) || versions.count === 0) { - return; - } - var currentVersion = versions.items[0]; - localStorage.setItem("currentVersion", currentVersion.id); - callBack(currentVersion.id); - }, - errorCallBack); - } else { - callBack(version); - } -} - /* exported fillSelect */ function fillSelect(selectElement, values) { $(selectElement).html(""); diff --git a/src/main/resources/public/js/odin.js b/src/main/resources/public/js/odin.js new file mode 100644 index 0000000..54d8832 --- /dev/null +++ b/src/main/resources/public/js/odin.js @@ -0,0 +1,1512 @@ +// form config.js +/* global uiLocale, urlVersions */ +// from core.js +/* global isEmpty, deleteFromRest, fillSelect, MessageTypesEnum, showFeedbackMessage, getCurrentVersionData */ +/* global getFromRestWithParams, getFromRest */ +/* global postToRestWithVersionAndParams, putToRestWithVersionAndParams */ + +// TODO: settings for table selection mode (single, multiple) + +var odinMetaList = "/meta/list"; +var odinMetaElement = "/meta/element"; +var odinCopy = "/copy-from-version"; + +/* exported OdinTableWithMeta */ +function OdinTableWithMeta(divId, dataUrl, disableCopy) { + return OdinTableWithMetaAndParams(divId, dataUrl, "", disableCopy, ""); +} + +/* exported OdinTableWithMetaAndGlobalCallback */ +function OdinTableWithMetaAndGlobalCallback(divId, dataUrl, disableCopy, globalSaveCallback, globalSelectCallback) { + return OdinTableWithMetaAndParams(divId, dataUrl, "", disableCopy, globalSaveCallback, globalSelectCallback); +} + +/* exported OdinTableWithMetaAndParams */ +function OdinTableWithMetaAndParams(divId, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback) { + return new OdinTable(divId, dataUrl + odinMetaList, dataUrl + odinMetaElement, dataUrl, + params, disableCopy, globalSaveCallback, globalSelectCallback); +} + +function OdinTable(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback) { + var CLICK_DELAY = 250; + var DATA_ATTR = "odin-data"; + var DATA_VIEW_ATTR = "odin-data-view"; + var TYPE_ATTR = "type"; + var ID_ATTR = "id"; + var DISABLED_ATTR = "disabled"; + var REQUIRED_ATTR = "required"; + var LABELS_SELECTOR = "label"; + var INPUTS_SELECTOR = ":input"; + var BUTTONS_SELECTOR = ":button"; + var CHECKED_ATTR = "checked"; + var NOT_CHECKED_ATTR = ""; + var PAGINATOR_ITEMS_PER_PAGE = 10; + var PAGINATOR_VISIBLE_BUTTONS_COUNT = 3; + var odinTable; + + var L10nEnum = { + VIEW: "Наименование", + COLLECTION: "Коллеция объектов", + GO_TO: "Перейти", + + BUTTON_ADD: "Добавить", + BUTTON_CREATE: "Создать", + BUTTON_EDIT: "Изменить", + BUTTON_DELETE: "Удалить", + + BUTTON_COPY: "Копировать", + + BUTTON_OK: "Продолжить", + BUTTON_CANCEL: "Отмена", + + BUTTON_SAVE: "Записать", + BUTTON_CLOSE: "Закрыть", + + CAPTION_CREATE_FORM: "Новый элемент", + CAPTION_EDIT_FORM: "(редактирование)", + CAPTION_CHOOSE_FORM: "Выбор объекта", + CAPTION_COPY_FORM: "Копирование данных", + CAPTION_DEFAULT_TAB: "Реквизиты", + CPATION_FORM_DEFAULT: "...", + + QUESTION_LOST_CHANGES: "Закрыть без сохранения?", + QUESTION_DELETE_ITEM: "Выполнить удаление?", + QUESTION_DEFAULT_CAPTION: "Продолжит?", + + ERROR_VERSIONS_MATCH: "Версии совпадают" + }; + Object.freeze(L10nEnum); + + var FieldTypesEnum = { + BOOLEAN: "boolean", + DATE: "date", + NUMERIC: "numeric", + STRING: "string", + COLLECTION: "collection", + OBJECT: "object" + }; + Object.freeze(FieldTypesEnum); + + var DateTypesEnum = { + DATETIME: "datetime", + DATE: "date", + TIME: "time" + }; + Object.freeze(DateTypesEnum); + + var StringTypesEnum = { + STRING: "string", + PASSWORD: "password", + TEXT: "text", + EMAIL: "email", + HREF: "href" + }; + Object.freeze(StringTypesEnum); + + var VisibleTypesEnum = { + ALL: "all", + ON_CREATE: "on_create", + ON_UPDATE: "on_update", + NONE: "none" + }; + Object.freeze(VisibleTypesEnum); + + var InputTypesEnum = { + CHECKBOX: "checkbox", + PASSWORD: "password", + TEXT: "text", + EMAIL: "email", + DATE: "date", + DATETIME: "datetime-local", + TIME: "time", + NUMBER: "number", + HIDDEN: "hidden", + SUBMIT: "submit" + }; + Object.freeze(InputTypesEnum); + + var FormTypesEnum = { + FORM: "form", + CONFIRM: "confirm", + SIMPLE: "simple" + }; + Object.freeze(FormTypesEnum); + + var NumberAttrEnum = { + MIN: "min", + MAX: "max", + STEP: "step" + }; + Object.freeze(NumberAttrEnum); + + var StringAttrEnum = { + MIN: "minlength", + MAX: "maxlength" + }; + Object.freeze(StringAttrEnum); + + $(document.body).append("
"); + $("#odinTemplates").load("/templates/odin.html", function () { + $("#" + divId).append("
" + + " " + + "
"); + odinTable = new OdinTableMain(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, params, disableCopy, globalSaveCallback, globalSelectCallback); + }); + + this.getSelectedItems = function () { + return odinTable.getSelectedItems(); + } + + function format(str, args) { + if (isEmpty(str) || isEmpty(args)) { + return str; + } + if (!Array.isArray(args)) { + throw "Odin: Format args is not array"; + } + return str.replace(/\{(\d+)\}/g, + function (match, number) { + return typeof args[number] !== "undefined" ? + args[number] : match; + }); + } + + function centerDiv(component) { + if (isEmpty(component)) { + return; + } + component.offset({ + top: ($(window).height() - component.height()) / 2, + left: ($(window).width() - component.width()) / 2 + }); + } + + function tmpl(id) { + var html = $("#odinTemplates").find("#" + id).html(); + if (isEmpty(html)) { + throw "Odin: Template load error"; + } + var template = format(html.trim(), Array.prototype.slice.call(arguments, 1)); + return $(template); + } + + function find(object, selector) { + var result = object.find(selector); + if (isEmpty(result)) { + throw "Can't find elements by selector " + selector; + } + return result; + } + + function findFiltered(object, selector, notSelector) { + var result = object.find(selector).not(notSelector); + if (isEmpty(result)) { + throw "Can't find elements by selector " + selector; + } + return result; + } + + function OdinFormCommon(type) { + var BUTTON_CLOSE_SELECTOR = "i.fa"; + var CAPTION_SELECTOR = "#caption"; + var PANEL_SELECTOR = ".panel"; + var FORM_INPUTS_SELECTOR = ":input"; + var DATA_CHANGED_ATTR = "changed"; + + var form; + var header; + var content; + var footer; + + var createForm = function () { + switch (type) { + case FormTypesEnum.FORM: + form = tmpl("form"); + break; + case FormTypesEnum.CONFIRM: + form = tmpl("formConfirm"); + break; + default: + form = tmpl("formSimple"); + } + if (isEmpty(form)) { + throw "Odin: Unknown form type"; + } + header = createFormHeader(); + content = createFormContent(); + footer = createFormFooter(); + var formPanel = tmpl("formPanel"); + formPanel.append(header); + formPanel.append(content); + formPanel.append(footer); + form.append(formPanel); + setCaption(L10nEnum.CPATION_FORM_DEFAULT); + $(document.body).append(form); + }; + + var createFormHeader = function () { + var fHeader = tmpl("formHeader"); + fHeader.find(BUTTON_CLOSE_SELECTOR).click(function () { + hideForm(); + }); + return fHeader; + }; + + var createFormContent = function () { + return tmpl("formContent"); + }; + + var createFormFooter = function () { + return tmpl("formFooter"); + }; + + var setCaption = function (caption) { + if (isEmpty(caption)) { + throw "Odin: Caption is not set"; + } + find(header, CAPTION_SELECTOR).text(caption); + }; + + var addFormClass = function (clazz) { + if (isEmpty(clazz)) { + throw "Odin: Additional class is not set"; + } + form.addClass(clazz); + }; + + var isModified = function () { + return form.data(DATA_CHANGED_ATTR); + }; + + var setFormSubmit = function (callBack) { + if (isEmpty(callBack)) { + throw "Odin: Callback is not set"; + } + form.submit(function () { + callBack(); + return false; + }); + }; + + var showForm = function () { + form.show(0, function () { + centerDiv(find(form, PANEL_SELECTOR)); + }); + if (type === FormTypesEnum.FORM) { + find(form, FORM_INPUTS_SELECTOR).change(function () { + form.data(DATA_CHANGED_ATTR, true); + }); + } + }; + + var hideForm = function () { + var callBack = function () { + form.hide(); + form.remove(); + }; + callBack(); + }; + + this._getHeader = function () { + return header; + }; + + this._getContent = function () { + return content; + }; + + this._getFooter = function () { + return footer; + }; + + this._setCaption = function (caption) { + setCaption(caption); + }; + + this._setFormSubmit = function (callBack) { + setFormSubmit(callBack); + }; + + this._addFormClass = function (clazz) { + addFormClass(clazz); + }; + + this._isModified = function () { + return isModified(); + }; + + this._show = function () { + showForm(); + }; + + this._hide = function () { + hideForm(); + }; + + createForm(); + } + + function OdinToolbar(tableDiv, createCallBack, editCallBack, deleteCallBack) { + var SELECTOR_ODIN_TABLE = ".odin-table"; + + var toolbar; + + var createButton; + var editButton; + var deleteButton; + + var initialize = function () { + if (isEmpty(tableDiv)) { + throw "Odin-Toolbar: Target div is not set"; + } + var table = find(tableDiv, SELECTOR_ODIN_TABLE); + toolbar = tmpl("toolbar"); + toolbar.insertBefore(table, null); + if (!isEmpty(createCallBack)) { + createButton = tmpl("createButton", L10nEnum.BUTTON_CREATE); + createButton.click(function () { + createCallBack(); + }); + toolbar.append(createButton); + } + if (!isEmpty(editCallBack)) { + editButton = tmpl("editButton", L10nEnum.BUTTON_EDIT); + editButton.click(function () { + editCallBack(); + }); + setEditButtonState(false); + toolbar.append(editButton); + } + if (!isEmpty(deleteCallBack)) { + deleteButton = tmpl("deleteButton", L10nEnum.BUTTON_DELETE); + deleteButton.click(function () { + deleteCallBack(); + }); + setDeleteButtonState(false); + toolbar.append(deleteButton); + } + }; + + var setEditButtonState = function (state) { + if (isEmpty(editButton)) { + return; + } + editButton.attr("disabled", !state); + }; + + var setDeleteButtonState = function (state) { + if (isEmpty(deleteButton)) { + return; + } + deleteButton.attr("disabled", !state); + }; + + this.addButton = function (caption, callBack) { + if (isEmpty(caption) || isEmpty(callBack)) { + throw "Odin-Toolbar: Caption or callback is not set"; + } + var button = tmpl("basicButton", caption); + button.click(function () { + callBack(); + }); + toolbar.append(button); + }; + + this.setEditButtonState = function (state) { + setEditButtonState(state); + }; + + this.setDeleteButtonState = function (state) { + setDeleteButtonState(state); + }; + + initialize(); + } + + function OdinPaginator(tableDiv, callBack) { + var SELECTOR_PAGINATOR_CONTENT = ".odin-paginator-content"; + var SELECTOR_PAGINATOR_BUTTON = "a"; + var PAGINATOR_BUTTON_PREFIX = "odin-paginator-"; + var PAGINATOR_SELECTED_BUTTON_CLASS = "active"; + + var initialize = function () { + if (isEmpty(tableDiv)) { + throw "Odin-Paginator: Target div is not set"; + } + if (isEmpty(callBack)) { + throw "Odin-Paginator: Callback is not set"; + } + tableDiv.append(tmpl("paginator")); + }; + + var drawPaginator = function (data, offset) { + var paginatorContentDiv = find(tableDiv, SELECTOR_PAGINATOR_CONTENT); + paginatorContentDiv.empty(); + if (isEmpty(data)) { + return; + } + $.each(data, function (index, value) { + if (value.name) { + paginatorContentDiv.append(tmpl("paginatorButton", value.offset, value.name)); + } else { + paginatorContentDiv.append(tmpl("paginatorSeparator")); + } + }); + var buttons = find(paginatorContentDiv, SELECTOR_PAGINATOR_BUTTON); + buttons.removeClass("active"); + buttons.click(function () { + $('#preloader').show(); + var offset = $(this).attr(ID_ATTR).replace(PAGINATOR_BUTTON_PREFIX, ""); + callBack(offset); + }); + find(paginatorContentDiv, "#" + PAGINATOR_BUTTON_PREFIX + offset) + .addClass(PAGINATOR_SELECTED_BUTTON_CLASS); + }; + + this.drawPaginator = function (offset, count) { + var currentOffset = parseInt(offset ? offset : "0"); + var buttonCount = Math.floor( + count <= PAGINATOR_ITEMS_PER_PAGE ? 0 : count / PAGINATOR_ITEMS_PER_PAGE) + + (count < PAGINATOR_ITEMS_PER_PAGE || + count % PAGINATOR_ITEMS_PER_PAGE === 0 ? 0 : 1); + var data = []; + Array.apply(0, Array(buttonCount)).forEach(function (element, index) { + if (index > 0 && index < buttonCount - 1 && + buttonCount > PAGINATOR_VISIBLE_BUTTONS_COUNT * 2) { + if (index === currentOffset - PAGINATOR_VISIBLE_BUTTONS_COUNT || + index === currentOffset + PAGINATOR_VISIBLE_BUTTONS_COUNT + 1) { + data.push({ + "offset": null, + "name": null + }); + } + if (index <= currentOffset - PAGINATOR_VISIBLE_BUTTONS_COUNT || + index > currentOffset + PAGINATOR_VISIBLE_BUTTONS_COUNT) { + return true; + } + } + data.push({ + "offset": index, + "name": index + 1 + }); + }); + drawPaginator(data, currentOffset); + }; + + initialize(); + } + + function OdinTableFormatter() { + var BOOLEAN_TRUE_CLASS = "fa-check"; + var BOOLEAN_FALSE_CLASS = "fa-times"; + + var formatBooleanValue = function (value) { + return tmpl("tableBoolean", (value ? BOOLEAN_TRUE_CLASS : BOOLEAN_FALSE_CLASS)); + }; + + var formatDateValue = function (value, dateType) { + var date = new Date(value); + var formatterSettings = {}; + switch (dateType) { + case DateTypesEnum.DATETIME: + formatterSettings = { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric" + }; + break; + case DateTypesEnum.TIME: + formatterSettings = { + hour: "numeric", + minute: "numeric", + second: "numeric" + }; + break; + } + var dateFormatter = new Intl.DateTimeFormat(uiLocale, formatterSettings); + return dateFormatter.format(date); + }; + + var formatNumericValue = function (value, positiveOnly, scale) { + var numberFormatter = new Intl.NumberFormat(uiLocale, { + useGrouping: false, + minimumFractionDigits: scale + }); + var formattedValue = numberFormatter.format(value); + if (positiveOnly && value < 0) { + return tmpl("tableNumericNegative", formattedValue); + } + return formattedValue; + }; + + var formatStringValue = function (value, maxLength, stringType) { + switch (stringType) { + case StringTypesEnum.HREF: + return tmpl("anchor", value, L10nEnum.GO_TO); + default: + if (isEmpty(maxLength) || maxLength === 0) { + return value; + } + return value.substring(0, Math.min(value.length, maxLength)); + } + }; + + this.formatValue = function (value, fieldParams) { + if (isEmpty(value) || isEmpty(fieldParams)) { + return ""; + } + if (fieldParams.hidden) { + return value; + } + switch (fieldParams.fieldType) { + case FieldTypesEnum.BOOLEAN: + return formatBooleanValue(value); + case FieldTypesEnum.DATE: + return formatDateValue(value, fieldParams.type); + case FieldTypesEnum.NUMERIC: + return formatNumericValue(value, fieldParams.positiveOnly, fieldParams.scale); + case FieldTypesEnum.STRING: + return formatStringValue(value, fieldParams.maxLength, fieldParams.type); + case FieldTypesEnum.COLLECTION: + return L10nEnum.COLLECTION; + case FieldTypesEnum.OBJECT: + return value.view; + default: + return value; + } + }; + } + + function OdinTableView(odinDiv, editCallback, globalSelectCallback) { + var TABLEVIEW_SELECTED_LINE_CLASS = "odin-table-selected-line"; + var TABLEVIEW_LINE_SELECTOR = "tr"; + var TABLEVIEW_SELECTED_LINE_SELECTOR = TABLEVIEW_LINE_SELECTOR + + "." + TABLEVIEW_SELECTED_LINE_CLASS; + var TABLEVIEW_LINE_DATA_SELECTOR = TABLEVIEW_LINE_SELECTOR + + "[" + DATA_ATTR + "=\"{0}\"]"; + var TABLEVIEW_CHOOSABLE_CLASS = "table-hover odin-unselectable"; + var TABLEVIEW_LINE_POINTED_CLASS = "odin-table-pointed-line"; + var TABLEVIEW_HEAD_COLUMN_SELECTOR = "th:nth-child({0})"; + var TABLEVIEW_BODY_COLUMN_SELECTOR = "td:nth-child({0})"; + + var tTable; + var tHead; + var tBody; + var choosable; + var formatter; + var toolbar; + + var createLine = function (idValue) { + var prevent = false; + var timer = 0; + var newLine = tmpl("tableLine"); + if (!isEmpty(idValue) && choosable) { + newLine.attr(DATA_ATTR, idValue); + newLine.click(function () { + var btn = $(this); + timer = setTimeout(function () { + if (!prevent) { + btn.toggleClass(TABLEVIEW_SELECTED_LINE_CLASS); + } + prevent = false; + if (!isEmpty(globalSelectCallback)) { + globalSelectCallback(getLines()); + } + if (!isEmpty(toolbar)) { + toolbar.setEditButtonState(!isEmpty(getLines())); + toolbar.setDeleteButtonState(!isEmpty(getLines())); + } + }, CLICK_DELAY); + }); + if (!isEmpty(editCallback)) { + newLine.dblclick(function () { + clearTimeout(timer); + prevent = true; + editCallback(idValue); + }); + } + newLine.addClass(TABLEVIEW_LINE_POINTED_CLASS); + } + return newLine; + }; + + var addLineToHead = function (line) { + tHead.append(line); + }; + + var addLineToBody = function (line) { + tBody.append(line); + }; + + var clearBody = function () { + tBody.empty(); + }; + + var initialize = function () { + if (isEmpty(odinDiv)) { + throw "Odin: Odin div is not set"; + } + odinDiv.append(tmpl("table")); + tTable = find(odinDiv, "table"); + tHead = find(odinDiv, "thead"); + tBody = find(odinDiv, "tbody"); + formatter = new OdinTableFormatter(); + }; + + var getLines = function (isAll) { + var selector = isAll ? TABLEVIEW_LINE_SELECTOR : TABLEVIEW_SELECTED_LINE_SELECTOR; + var selectedItems = []; + try { + find(tBody, selector).each(function () { + selectedItems.push({ + id: $(this).attr(DATA_ATTR), + view: $(this).attr(DATA_VIEW_ATTR) + }); + }); + } catch (e) { + // Ignore + } + return selectedItems; + }; + + var disableToolbar = function () { + if (isEmpty(toolbar)) { + return; + } + toolbar.setEditButtonState(false); + toolbar.setDeleteButtonState(false); + }; + + this.createHead = function (metaData, isOdinDto) { + if (isEmpty(metaData)) { + throw "Odin: Metadata is not set"; + } + choosable = isOdinDto; + if (choosable) { + tTable.addClass(TABLEVIEW_CHOOSABLE_CLASS); + } + var tHeadLine = createLine(); + $.each(metaData, function (index, value) { + tHeadLine.append(tmpl("tableHeadColumn", value.caption)); + }); + addLineToHead(tHeadLine); + $.each(metaData, function (columnIndex, columnParams) { + if (columnParams.visible === VisibleTypesEnum.NONE) { + var index = columnIndex + 1; + find(tHead, format(TABLEVIEW_HEAD_COLUMN_SELECTOR, [index])).hide(); + } + }); + }; + + this.fillBody = function (metaData, data) { + clearBody(); + $.each(data, function (index, value) { + var idValue = choosable ? value.id : null; + var line = createLine(idValue); + line.attr(DATA_VIEW_ATTR, value.view); + $.each(metaData, function (columnIndex, columnParams) { + var column = tmpl("tableBodyColumn"); + column.append( + formatter.formatValue(value[columnParams.fieldName], + columnParams)); + line.append(column); + }); + addLineToBody(line); + }); + if (!isEmpty(data)) { + $.each(metaData, function (columnIndex, columnParams) { + if (columnParams.visible === VisibleTypesEnum.NONE) { + var index = columnIndex + 1; + find(tBody, format(TABLEVIEW_BODY_COLUMN_SELECTOR, [index])).hide(); + } + }); + } + }; + + this.getSelectedItems = function () { + return getLines(false); + }; + + this.getItems = function () { + return getLines(true); + }; + + // Delete form view only! + this.deleteById = function (id) { + if (isEmpty(id)) { + return; + } + try { + find(tBody, format(TABLEVIEW_LINE_DATA_SELECTOR, [id])).remove(); + disableToolbar(); + } catch (e) { + // Ignore + } + }; + + this.setToolbar = function (toolbarVar) { + toolbar = toolbarVar; + }; + + this.disableToolbar = function () { + disableToolbar(); + }; + + initialize(); + } + + function OdinTableMain(divId, listMetaDataUrl, elementMetaDataUrl, dataUrl, args, disableCopy, globalSaveCallback, globalSelectCallback) { + if (isEmpty(divId) || isEmpty(listMetaDataUrl) || isEmpty(dataUrl)) { + throw "Odin: Not enough parameters"; + } + + var OFFSET_ARG = "&offset="; + var COUNT_ARG = "&count="; + + var odinDiv; + var tableView; + var paginator; + + var currentOffset; + + var listMetaData; + var elementMetaData; + + var params = isEmpty(args) ? "" : args; + + var drawBody = function (offset) { + if (isEmpty(listMetaData)) { + throw "Odin: Meta data is not defined"; + } + currentOffset = isEmpty(offset) ? 0 : parseInt(offset); + getFromRestWithParams(dataUrl, + OFFSET_ARG + (currentOffset * PAGINATOR_ITEMS_PER_PAGE) + + COUNT_ARG + PAGINATOR_ITEMS_PER_PAGE + params, function (data) { + if (isEmpty(data)) { + throw "Odin: Response data is null"; + } + tableView.fillBody(listMetaData, data.items); + if (data.count > 0) { + paginator.drawPaginator(offset, data.count); + } + tableView.disableToolbar(); + $('#preloader').hide(); + }); + }; + + var initialize = function () { + odinDiv = find($(document.body), "#" + divId); + tableView = new OdinTableView(odinDiv, editItem, globalSelectCallback); + paginator = new OdinPaginator(odinDiv, drawBody); + getFromRest(listMetaDataUrl, function (data) { + if (isEmpty(data) || isEmpty(data.fields)) { + throw "Odin: Can't load list meta data"; + } + var isOdinDto = data.odinDto; + listMetaData = data.fields; + if (isOdinDto && !isEmpty(elementMetaDataUrl)) { + getFromRest(elementMetaDataUrl, function (data) { + if (isEmpty(data) || isEmpty(data.fields)) { + throw "Odin: Can't load element meta data"; + } + elementMetaData = data.fields; + var toolbar = new OdinToolbar(odinDiv, createItem, editItem, deleteItem, globalSaveCallback); + tableView.setToolbar(toolbar); + if (!disableCopy) { + toolbar.addButton(L10nEnum.BUTTON_COPY, function () { + new OdinCopyBox(dataUrl, drawBody); + }); + } + }); + } + tableView.createHead(listMetaData, isOdinDto); + drawBody(); + }); + }; + + var updateTable = function () { + // TODO: recalculate offset before redraw table + drawBody(currentOffset); + }; + + var createItem = function () { + new OdinForm(elementMetaData, dataUrl, updateTable, null, params); + }; + + var editItem = function (value) { + if (isEmpty(elementMetaDataUrl)) { + return; + } + var curValue = value; + if (isEmpty(value)) { + var selectedItems = tableView.getSelectedItems(); + if (!isEmpty(selectedItems)) { + curValue = selectedItems[0].id; + } + } + if (isEmpty(curValue)) { + return; + } + new OdinForm(elementMetaData, dataUrl, updateTable, curValue, params); + }; + + var deleteItem = function () { + var selectedItems = tableView.getSelectedItems(); + if (isEmpty(selectedItems)) { + return; + } + var callBack = function () { + $.each(selectedItems, function (index, value) { + deleteFromRest(dataUrl + "/" + value.id, null, function () { + if (index === selectedItems.length - 1) { + updateTable(); + } + }); + }); + }; + new OdinConfirmBox(callBack, L10nEnum.QUESTION_DELETE_ITEM); + }; + + this.getSelectedItems = function () { + return tableView.getSelectedItems(); + }; + + initialize(); + } + + function OdinFormControlGenerator() { + this.createInputGroup = function () { + return tmpl("formInputGroup"); + }; + + this.createLabel = function (id, caption) { + return tmpl("formLabel", id, caption); + }; + + var createInput = function (id, value, type) { + if (isEmpty(id)) { + throw "Odin: Input id is not set"; + } + if (isEmpty(type)) { + throw "Odin: Input type is not set"; + } + var curValue = isEmpty(value) ? "" : value; + var wrapper = tmpl("formInputWrapper"); + if (type === InputTypesEnum.CHECKBOX) { + wrapper.append(tmpl("formInputCheckbox", + id, (curValue ? CHECKED_ATTR : NOT_CHECKED_ATTR))); + } else { + wrapper.append(tmpl("formInput", + id, curValue, type)); + } + return wrapper; + }; + + this.createBooleanInput = function (id, value) { + return createInput(id, value, InputTypesEnum.CHECKBOX); + }; + + var fixDateUnit = function (dateUnitValue) { + return dateUnitValue < 10 ? "0" + dateUnitValue : dateUnitValue; + }; + + var dateToStr = function (value, type) { + if (isEmpty(value)) { + return ""; + } + var date = new Date(value); + var year = fixDateUnit(date.getFullYear()); + var month = fixDateUnit(date.getMonth() + 1); + var day = fixDateUnit(date.getDate()); + var hour = fixDateUnit(date.getHours()); + var minute = fixDateUnit(date.getMinutes()); + var formattedDate; + switch (type) { + case DateTypesEnum.DATETIME: + formattedDate = year + "-" + month + "-" + day + "T" + hour + ":" + minute; + break; + case DateTypesEnum.TIME: + formattedDate = hour + ":" + minute; + break; + default: + formattedDate = year + "-" + month + "-" + day; + } + return formattedDate; + }; + + this.createDateInput = function (id, value, type) { + var dateType; + switch (type) { + case DateTypesEnum.DATETIME: + dateType = InputTypesEnum.DATETIME; + break; + case DateTypesEnum.TIME: + dateType = InputTypesEnum.TIME; + break; + default: + dateType = InputTypesEnum.DATE; + } + return createInput(id, dateToStr(value, type), dateType); + }; + + this.createNumericInput = function (id, value) { + return createInput(id, value, InputTypesEnum.NUMBER); + }; + + this.createStringInput = function (id, value, type) { + var stringType; + switch (type) { + case StringTypesEnum.PASSWORD: + stringType = InputTypesEnum.PASSWORD; + break; + case StringTypesEnum.EMAIL: + stringType = InputTypesEnum.EMAIL; + break; + default: + stringType = InputTypesEnum.TEXT; + } + return createInput(id, value, stringType); + }; + + this.dateToStr = function (value, type) { + return dateToStr(value, type); + }; + } + + function OdinFormContentGenerator(createNavTabCallBack, isCreateOperation) { + var PRECISION_CHAR = "9"; + var SCALE_PREFIX = "0."; + var SCALE_CHAR = "0"; + var SCALE_POSTFIX = "1"; + var OBJECT_INPUT_CLASS = "input-group odin-input-group"; + var CREATE_BUTTON_TEXT_SELECTOR = ".btn-success > span"; + var CHOOSE_BTN_SELECTOR = ".odin-choose-btn"; + var CLEAR_BTN_SELECTOR = ".odin-clear-btn"; + var CLEARABLE_INPUT_SELECTOR = ".form-control"; + + var controlGenerator = new OdinFormControlGenerator(); + + var createBooleanControl = function (id, caption, value) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(controlGenerator.createBooleanInput(id, value)); + return group; + }; + + var createDateControl = function (id, caption, value, type) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(controlGenerator.createDateInput(id, value, type)); + return group; + }; + + var createNumericControl = function (id, caption, value, positiveOnly, precision, scale) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + var control = controlGenerator.createNumericInput(id, value); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + if (positiveOnly) { + currentControl.attr(NumberAttrEnum.MIN, 0); + } + if (precision) { + currentControl + .attr(NumberAttrEnum.MAX, Array(precision + 1).join(PRECISION_CHAR)); + } + if (scale) { + var step = SCALE_PREFIX + Array(scale).join(SCALE_CHAR) + SCALE_POSTFIX; + currentControl.attr(NumberAttrEnum.STEP, step); + } + }); + group.append(control); + return group; + }; + + var createStringControl = function (id, caption, value, minLength, maxLength, type) { + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + var control = controlGenerator.createStringInput(id, value, type); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + currentControl.attr(StringAttrEnum.MIN, minLength); + currentControl.attr(StringAttrEnum.MAX, maxLength); + }); + group.append(control); + return group; + }; + + var createCollectionControl = function (id, caption, values, path) { + // TODO: add required check if needed + var content = $(tmpl("emptyDiv")); + var tabView = new OdinTableView(content, null, null); + var createAction = function () { + new OdinChooseBox(path, function (selectedItems) { + if (isEmpty(selectedItems)) { + return; + } + var currentValues = tabView.getItems(); + $.merge(currentValues, selectedItems); + var existingIDs = []; + currentValues = $.grep(currentValues, function (v) { + if ($.inArray(v.id, existingIDs) !== -1) { + return false; + } else { + existingIDs.push(v.id); + return true; + } + }); + tabView.fillBody(metaData, currentValues); + }); + }; + var deleteAction = function () { + var callBack = function () { + $.each(tabView.getSelectedItems(), function (index, value) { + tabView.deleteById(value.id); + }); + }; + new OdinConfirmBox(callBack, L10nEnum.QUESTION_DELETE_ITEM); + }; + content.append(tabView); + var metaData = [ + { + "fieldType": "string", + "fieldName": "id", + "caption": "id", + "visible": "none" + }, + { + "fieldType": "string", + "fieldName": "view", + "caption": L10nEnum.VIEW, + "visible": "all" + } + ]; + tabView.createHead(metaData, true); + tabView.fillBody(metaData, values); + createNavTabCallBack(id, caption, content); + var toolbar = new OdinToolbar(content, createAction, null, deleteAction); + tabView.setToolbar(toolbar); + find(content, CREATE_BUTTON_TEXT_SELECTOR).text(L10nEnum.BUTTON_ADD); + return null; + }; + + var createObjectControl = function (id, caption, value, path) { + // TODO: add required check if needed + var chooseInput = controlGenerator.createStringInput( + id, isEmpty(value) ? "" : value.view, StringTypesEnum.TEXT); + chooseInput.addClass(OBJECT_INPUT_CLASS); + var buttonGroup = tmpl("buttonGroup"); + chooseInput.append(buttonGroup); + var chooseButton = tmpl("chooseButton"); + buttonGroup.append(chooseButton); + find(buttonGroup, CHOOSE_BTN_SELECTOR).click(function () { + new OdinChooseBox(path, function (selectedItems) { + if (isEmpty(selectedItems)) { + return; + } + find(chooseInput, CLEARABLE_INPUT_SELECTOR).each(function () { + $(this) + .val(selectedItems[0].view) + .attr(DATA_ATTR, selectedItems[0].id); + }); + }); + }); + var clearButton = tmpl("clearButton"); + buttonGroup.append(clearButton); + find(buttonGroup, CLEAR_BTN_SELECTOR).click(function () { + find(chooseInput, CLEARABLE_INPUT_SELECTOR).val(""); + }); + find(chooseInput, CLEARABLE_INPUT_SELECTOR).each(function () { + $(this) + .attr(DISABLED_ATTR, true) + .attr(DATA_ATTR, isEmpty(value) ? "" : value.id); + }); + var group = controlGenerator.createInputGroup(); + group.append(controlGenerator.createLabel(id, caption)); + group.append(chooseInput); + return group; + }; + + var configureControl = function (control, hidden, readonly, notempty) { + if (isEmpty(control)) { + return; + } + find(control, LABELS_SELECTOR).each(function () { + var currentControl = $(this); + if (hidden) { + currentControl.hide(); + } + }); + find(control, INPUTS_SELECTOR).each(function () { + var currentControl = $(this); + if (hidden) { + currentControl.hide(); + currentControl.attr(TYPE_ATTR, InputTypesEnum.HIDDEN); + } + if (readonly) { + currentControl.attr(DISABLED_ATTR, true); + } + if (notempty) { + currentControl.attr(REQUIRED_ATTR, true); + } + }); + return control; + }; + + this.createFormControl = function (value, fieldParams) { + if (isEmpty(fieldParams)) { + return null; + } + var control; + switch (fieldParams.fieldType) { + case FieldTypesEnum.BOOLEAN: + control = createBooleanControl(fieldParams.fieldName, + fieldParams.caption, value); + break; + case FieldTypesEnum.DATE: + control = createDateControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.type); + break; + case FieldTypesEnum.NUMERIC: + control = createNumericControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.positiveOnly, + fieldParams.precision, fieldParams.scale); + break; + case FieldTypesEnum.STRING: + control = createStringControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.minLength, + fieldParams.maxLength, fieldParams.type); + break; + case FieldTypesEnum.COLLECTION: + control = createCollectionControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.path); + break; + case FieldTypesEnum.OBJECT: + control = createObjectControl(fieldParams.fieldName, + fieldParams.caption, value, fieldParams.path); + break; + default: + return null; + } + var hidden; + switch (fieldParams.visible) { + case VisibleTypesEnum.NONE: + hidden = true; + break; + case VisibleTypesEnum.ON_CREATE: + case VisibleTypesEnum.ON_UPDATE: + hidden = isCreateOperation; + break; + default: + hidden = false; + } + return configureControl(control, hidden, fieldParams.readOnly, fieldParams.notEmpty); + }; + + this.getCurrentDateStr = function () { + return controlGenerator.dateToStr(new Date(), DateTypesEnum.DATE); + }; + } + + function OdinForm(metaData, dataUrl, tableCallBack, id, args) { + if (isEmpty(metaData) || isEmpty(dataUrl) || isEmpty(tableCallBack)) { + throw "Odin: Not enough parameters"; + } + + var DEFAULT_TAB_SELECTOR = "odin-values"; + var TABS_HEADER_SELECTOR = ".nav-tabs"; + var TABS_CONTENT_SELECTOR = ".tab-content"; + var TAB_SELECTOR = ".tab-pane"; + var COLLECTION_ITEM_SELECTOR = "tbody > tr"; + + OdinFormCommon.call(this, FormTypesEnum.FORM); + + var self = this; + + var generator; + + var params = isEmpty(args) ? "" : args; + + var isCreateForm = function () { + return isEmpty(id); + }; + + var formToJson = function () { + var formData = {}; + try { + findFiltered(self._getContent(), TAB_SELECTOR, "#" + DEFAULT_TAB_SELECTOR) + .each(function () { + var tabData = []; + try { + find($(this), COLLECTION_ITEM_SELECTOR) + .each(function () { + if ($(this).attr(DATA_ATTR)) { + tabData.push({ + id: $(this).attr(DATA_ATTR), + view: $(this).attr(DATA_VIEW_ATTR) + }); + return true; + } + }); + } catch (e) { + // Ignore + } + formData[$(this).attr(ID_ATTR)] = tabData; + }); + } catch (e) { + // Ignore + } + findFiltered(self._getContent(), INPUTS_SELECTOR, BUTTONS_SELECTOR) + .each(function () { + if (isEmpty($(this).val())) { + return true; + } + if ($(this).attr(DATA_ATTR)) { + formData[$(this).attr(ID_ATTR)] = { + id: $(this).attr(DATA_ATTR), + view: $(this).val() + }; + return true; + } + switch ($(this).attr(TYPE_ATTR)) { + case InputTypesEnum.CHECKBOX: + formData[$(this).attr(ID_ATTR)] = $(this).is(":" + CHECKED_ATTR); + break; + case InputTypesEnum.DATE: + case InputTypesEnum.DATETIME: + formData[$(this).attr(ID_ATTR)] = isEmpty($(this).val) ? + "" : Date.parse($(this).val()); + break; + case InputTypesEnum.TIME: + formData[$(this).attr(ID_ATTR)] = isEmpty($(this).val) ? + "" : Date.parse(generator.getCurrentDateStr() + + "T" + $(this).val()); + break; + case InputTypesEnum.NUMBER: + formData[$(this).attr(ID_ATTR)] = parseFloat($(this).val()); + break; + default: + formData[$(this).attr(ID_ATTR)] = $(this).val(); + } + }); + return JSON.stringify(formData); + }; + + var initialize = function () { + generator = new OdinFormContentGenerator(createNavTab, isCreateForm()); + self._getContent() + .append(tmpl("formTabs", DEFAULT_TAB_SELECTOR, L10nEnum.CAPTION_DEFAULT_TAB)); + var okBtn = tmpl("okButton", L10nEnum.BUTTON_SAVE); + okBtn.attr(TYPE_ATTR, InputTypesEnum.SUBMIT); + var closeBtn = tmpl("cancelButton", L10nEnum.BUTTON_CLOSE); + self._getFooter().append(closeBtn); + self._getFooter().append(okBtn); + if (isCreateForm()) { + configureAndShowForm(); + } else { + getFromRest(dataUrl + "/" + id, function (data) { + if (isEmpty(data)) { + throw format("Odin: Can't load data by id {0} and url {1}", [id, dataUrl]); + } + configureAndShowForm(data); + }); + } + closeBtn.click(function () { + closeForm(); + }); + }; + + var configureAndShowForm = function (data) { + var saveCallBack = function () { + saveData(formToJson()); + }; + self._setCaption(isEmpty(data) ? + L10nEnum.CAPTION_CREATE_FORM : + data.view + " " + L10nEnum.CAPTION_EDIT_FORM); + self._setFormSubmit(saveCallBack); + $.each(metaData, function (fieldIndex, fieldParams) { + var value = isEmpty(data) ? + null : data[fieldParams.fieldName]; + find(self._getContent(), "#" + DEFAULT_TAB_SELECTOR) + .append(generator.createFormControl(value, fieldParams)); + }); + self._show(); + }; + + var createNavTab = function (id, caption, content) { + find(self._getContent(), TABS_HEADER_SELECTOR).append(tmpl("formTab", id, caption)); + var tab = tmpl("formTabContent", id); + tab.append(content); + find(self._getContent(), TABS_CONTENT_SELECTOR).append(tab); + }; + + var saveData = function (data) { + var saveCallback = function (responseData) { + if (isEmpty(responseData)) { + throw "Odin: Error on data save"; + } + self._hide(); + tableCallBack(); + if (!isEmpty(globalSaveCallback)) { + globalSaveCallback(); + } + }; + if (isCreateForm()) { + postToRestWithParams(dataUrl, data, params, saveCallback); + } else { + putToRestWithParams(dataUrl, data, params, saveCallback); + } + }; + + var closeForm = function () { + if (self._isModified()) { + new OdinConfirmBox(self._hide, L10nEnum.QUESTION_LOST_CHANGES); + return; + } + self._hide(); + }; + + initialize(); + } + + function OdinConfirmBox(callBack, questionText) { + if (isEmpty(callBack) || isEmpty(questionText)) { + throw "Odin: Not enough parameters"; + } + + OdinFormCommon.call(this, FormTypesEnum.CONFIRM); + + var self = this; + + var initialize = function () { + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._setCaption(L10nEnum.QUESTION_DEFAULT_CAPTION); + self._getContent().text(questionText); + self._getFooter().append(yesBtn); + self._getFooter().append(cancelBtn); + self._show(); + + yesBtn.click(function () { + self._hide(); + callBack(); + if (!isEmpty(globalSaveCallback)) { + globalSaveCallback(); + } + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } + + function OdinChooseBox(path, callBack) { + if (isEmpty(path) || isEmpty(callBack)) { + throw "Odin: Not enough parameters"; + } + + var ADDITIONAL_FORM_CLASS = "odin-table-box"; + var ADDITIONAL_CONTENT_CLASS = "odin-kill-padding"; + var CONTENT_ID_VALUE = "table-block"; + + OdinFormCommon.call(this); + + var self = this; + + var initialize = function () { + self._addFormClass(ADDITIONAL_FORM_CLASS); + self._getContent().addClass(ADDITIONAL_CONTENT_CLASS); + self._getContent().attr(ID_ATTR, CONTENT_ID_VALUE); + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._getFooter().append(cancelBtn); + self._getFooter().append(yesBtn); + var table = new OdinTableMain("table-block", path + odinMetaList, null, path); + self._show(); + + yesBtn.click(function () { + self._hide(); + callBack(table.getSelectedItems()); + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } + + function OdinCopyBox(dataUrl, tableRedrawCallback) { + if (isEmpty(dataUrl) || isEmpty(tableRedrawCallback)) { + throw "Odin: Not enough parameters"; + } + + var ADDITIONAL_FORM_CLASS = "odin-simple-form"; + var FROM_VERSION_SELECTOR = "#fromVersion"; + var VERSION_SELECTOR = "#version"; + + OdinFormCommon.call(this); + + var self = this; + + var initialize = function () { + self._setCaption(L10nEnum.CAPTION_COPY_FORM); + self._addFormClass(ADDITIONAL_FORM_CLASS); + self._getContent().append(tmpl("copyForm")); + var from = find(self._getContent(), FROM_VERSION_SELECTOR); + var to = find(self._getContent(), VERSION_SELECTOR); + var yesBtn = tmpl("okButton", L10nEnum.BUTTON_OK); + var cancelBtn = tmpl("cancelButton", L10nEnum.BUTTON_CANCEL); + self._getFooter().append(cancelBtn); + self._getFooter().append(yesBtn); + + /*getFromRest(urlVersions, function (versions) { + if (isEmpty(versions) || versions.count === 0) { + return; + } + var versionsData = []; + versions.items.forEach(function (version) { + versionsData.push({ + id: version.id, + name: version.name + " от " + + new Date(version.date).toLocaleDateString(uiLocale) + }); + }); + fillSelect(from, versionsData.sort(function (a, b) { + return a.id - b.id; + })); + var currentVersion = getCurrentVersionData(); + if (!isEmpty(currentVersion)) { + to.attr(DATA_ATTR, currentVersion.id); + to.val(currentVersion.name); + } + self._show(); + });*/ + + yesBtn.click(function () { + if (isEmpty(from.val()) || isEmpty(to.val())) { + return; + } + if (from.val() === to.attr(DATA_ATTR)) { + showFeedbackMessage(L10nEnum.ERROR_VERSIONS_MATCH, MessageTypesEnum.DANGER); + return; + } + postToRestWithVersionAndParams(dataUrl + odinCopy, null, + "&fromVersionId=" + from.val(), function () { + self._hide(); + tableRedrawCallback(); + }); + }); + cancelBtn.click(function () { + self._hide(); + }); + }; + + initialize(); + } +} diff --git a/src/main/resources/public/js/tasks.js b/src/main/resources/public/js/tasks.js new file mode 100644 index 0000000..b333c31 --- /dev/null +++ b/src/main/resources/public/js/tasks.js @@ -0,0 +1,113 @@ +/*") + .attr("id", 'tags' + tagNumber) + .addClass("tag"); + // контейнер id + var idInput = $("") + .attr("type", "hidden") + .attr("id", "tags" + tagNumber + ".id") + .attr("name", "tags[" + tagNumber + "].id") + .attr("value", ''); + // контейнер текста + var conDiv = $("
") + .addClass("tag-name"); + // текст тега + var nameInput = $("") + .attr("type", "text") + .attr("id", "tags" + tagNumber + ".tagName") + .attr("name", "tags[" + tagNumber + "].tagName") + .attr("value", tagName) + .attr("readonly", "true") + .attr("size", tagName.length); + // кнопка удалить тег + var removeSpan = $("") + .attr("data-role", "remove") + .bind("click", removeTag); + + conDiv.append(nameInput); + conDiv.append(removeSpan); + newTagRow.append(idInput); + newTagRow.append(conDiv); + $(this).before(newTagRow); + } + + $(this).val(''); + } + }); + + $("span[data-role=remove]").click(removeTag); + $(".task-row").mouseenter(function (event) { + var taskRow = $(event.target).closest(".task-row"); + $(taskRow).css("background-color", "#f8f9fa"); + $(taskRow).find(".remove-task").removeClass("d-none"); + + }); + $(".task-row").mouseleave(function (event) { + var taskRow = $(event.target).closest(".task-row"); + $(taskRow).css("background-color", "white"); + $(taskRow).closest(".task-row").find(".remove-task").addClass("d-none"); + }); + + $('a[data-confirm]').click(function(ev) { + var href = $(this).attr('href'); + if (!$('#dataConfirmModal').length) { + $('#modalDelete').append(''); + } + $('#dataConfirmModal').find('#myModalLabel').text($(this).attr('data-confirm')); + $('#dataConfirmOK').attr('href', href); + $('#dataConfirmModal').modal({show:true}); + return false; + }); + +}); +/*]]>*/ \ No newline at end of file diff --git a/src/main/resources/public/templates/odin.html b/src/main/resources/public/templates/odin.html new file mode 100644 index 0000000..1f9f9d9 --- /dev/null +++ b/src/main/resources/public/templates/odin.html @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/admin/commits.html b/src/main/resources/templates/admin/commits.html index a8b8e85..a3eba20 100644 --- a/src/main/resources/templates/admin/commits.html +++ b/src/main/resources/templates/admin/commits.html @@ -1,24 +1,28 @@ - - - - - -
-
- -
-
- - - - + + + + + +
+
+
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/conferences/actual.html b/src/main/resources/templates/conferences/actual.html new file mode 100644 index 0000000..7e99e74 --- /dev/null +++ b/src/main/resources/templates/conferences/actual.html @@ -0,0 +1,29 @@ + + + + + + +
+
+
+
+
+

Актуальные конференции

+
+
+
+
+
+
+ + +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/conferences/conference.html b/src/main/resources/templates/conferences/conference.html index 078e697..27c602a 100644 --- a/src/main/resources/templates/conferences/conference.html +++ b/src/main/resources/templates/conferences/conference.html @@ -1,7 +1,7 @@ + layout:decorator="default" xmlns:th=""> @@ -11,55 +11,66 @@
- - - Назад к списку конференций -

Редактирование конференции

+

-
-
+ +
- +
- +

Incorrect title

+

-
-
-
-
- - - Удалить +
+ +
+ + + +
+

Incorrect title

+

+
-
@@ -69,39 +80,47 @@
-
- - - - +
+
+ +
+ +
+ +
-
-
-
+
+
+
Пользователь 1
-
+
очная
-
+
статья
-
-
- Пользователь 2 + +
+
+ Пользователь 1
-
- заочная +
+ очная
-
+
доклад
+
@@ -114,24 +133,31 @@
-
- -
- +
+ + + + Отмена + +
+
+
+
+
+
+
Дата создания:
+
+
+ + text + +
+
+
+
+
+
+
Дата изменения:
+
+
+ + text + +
+
+
+
+
+ +
+
+
+ + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/students/tasks.html b/src/main/resources/templates/students/tasks.html new file mode 100644 index 0000000..b1177d8 --- /dev/null +++ b/src/main/resources/templates/students/tasks.html @@ -0,0 +1,55 @@ + + + + + + + +
+
+ +
+
+
+
+

Работа со студентами

+
+
+
+
+
+
+ +
+ +
+
+
+
Фильтр:
+ + +
+
+
+
+
+ +
+ + +
+ + \ No newline at end of file diff --git a/src/test/java/IndexPageTest.java b/src/test/java/IndexPageTest.java new file mode 100644 index 0000000..1817240 --- /dev/null +++ b/src/test/java/IndexPageTest.java @@ -0,0 +1,52 @@ +import com.google.common.collect.ImmutableMap; +import core.PageObject; +import core.TestTemplate; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import paper.PaperPage; +import paper.PapersDashboardPage; +import paper.PapersPage; +import ru.ulstu.NgTrackerApplication; +import ru.ulstu.configuration.ApplicationProperties; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NgTrackerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class IndexPageTest extends TestTemplate { + private final Map> navigationHolder = ImmutableMap.of( + new PapersPage(), Arrays.asList("СТАТЬИ", "/papers/papers"), + new PaperPage(), Arrays.asList("РЕДАКТИРОВАНИЕ СТАТЬИ", "/papers/paper?id=0"), + new PapersDashboardPage(), Arrays.asList("СТАТЬИ", "/papers/dashboard") + ); + + @Autowired + private ApplicationProperties applicationProperties; + + @Test + public void testStartApplication() { + getContext().goTo(applicationProperties.getBaseUrl()); + Assertions + .assertThat(getContext().getTitle()) + .isEqualTo("NG-Tracker"); + } + + @Test + public void testModulesNavigation() { + navigationHolder.entrySet() + .stream() + .forEach(navigationEntry -> { + getContext().goTo(applicationProperties.getBaseUrl() + navigationEntry.getValue().get(1)); + PageObject pageObject = getContext().initPage(navigationEntry.getKey()); + Assertions + .assertThat(pageObject.getSubTitle()) + .isEqualToIgnoringCase(navigationEntry.getValue().get(0)); + }); + } +} diff --git a/src/test/java/context/ChromeContext.java b/src/test/java/context/ChromeContext.java new file mode 100644 index 0000000..899d51d --- /dev/null +++ b/src/test/java/context/ChromeContext.java @@ -0,0 +1,27 @@ +package context; + +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; + +public class ChromeContext extends Context { + private final static String WINDOWS_DRIVER = "chromedriver.exe"; + private final static String LINUX_DRIVER = "chromedriver"; + private final static String DRIVER_TYPE = "webdriver.chrome.driver"; + + @Override + protected void createDriver() { + final ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.addArguments("--headless"); + driver = new ChromeDriver(chromeOptions); + } + + @Override + protected String getDriverExecutable(boolean isWindows) { + return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER; + } + + @Override + protected String getDriverType() { + return DRIVER_TYPE; + } +} diff --git a/src/test/java/context/Context.java b/src/test/java/context/Context.java new file mode 100644 index 0000000..b32c08e --- /dev/null +++ b/src/test/java/context/Context.java @@ -0,0 +1,67 @@ +package context; + +import core.PageObject; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebDriver; + +import java.util.concurrent.TimeUnit; + +//import org.openqa.selenium.support.PageFactory; + +public abstract class Context { + private final static String DRIVER_LOCATION = "drivers/%s"; + + protected WebDriver driver; + + protected WebDriver getDriver() { + if (driver != null) { + return driver; + } + throw new IllegalStateException("WebDriver is not initialized"); + } + + public void start() { + System.setProperty(getDriverType(), getDriverExecutablePath()); + + createDriver(); + // это плохая инструкция для автотестов, т.к. лучше задавать для конкретного элемента или кейса + getDriver().manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); + } + + public void close() { + if (driver != null) { + driver.quit(); + } + } + + public void goTo(String url) { + getDriver().get(url); + } + + public String getTitle() { + return getDriver().getTitle(); + } + + public T initPage(T pageObject) { + return (T) pageObject.setDriver(getDriver()); + } + + protected abstract void createDriver(); + + protected abstract String getDriverType(); + + protected abstract String getDriverExecutable(boolean windows); + + private String getDriverExecutablePath() { + return Context.class.getClassLoader().getResource( + String.format(DRIVER_LOCATION, getDriverExecutable(isWindows()))).getFile(); + } + + private boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("windows"); + } + + public void setSize(Dimension dimension) { + driver.manage().window().setSize(dimension); + } +} diff --git a/src/test/java/context/FirefoxContext.java b/src/test/java/context/FirefoxContext.java new file mode 100644 index 0000000..1d5c163 --- /dev/null +++ b/src/test/java/context/FirefoxContext.java @@ -0,0 +1,24 @@ +package context; + +import org.openqa.selenium.firefox.FirefoxDriver; + +public class FirefoxContext extends Context { + private final static String WINDOWS_DRIVER = "geckodriver.exe"; + private final static String LINUX_DRIVER = "geckodriver"; + private final static String DRIVER_TYPE = "webdriver.gecko.driver"; + + @Override + protected void createDriver() { + driver = new FirefoxDriver(); + } + + @Override + protected String getDriverExecutable(boolean isWindows) { + return isWindows ? WINDOWS_DRIVER : LINUX_DRIVER; + } + + @Override + protected String getDriverType() { + return DRIVER_TYPE; + } +} diff --git a/src/test/java/core/PageObject.java b/src/test/java/core/PageObject.java new file mode 100644 index 0000000..d1fae83 --- /dev/null +++ b/src/test/java/core/PageObject.java @@ -0,0 +1,14 @@ +package core; + +import org.openqa.selenium.WebDriver; + +public abstract class PageObject { + protected WebDriver driver; + + public abstract String getSubTitle(); + + public PageObject setDriver(WebDriver driver) { + this.driver = driver; + return this; + } +} diff --git a/src/test/java/core/TestTemplate.java b/src/test/java/core/TestTemplate.java new file mode 100644 index 0000000..a72a8c9 --- /dev/null +++ b/src/test/java/core/TestTemplate.java @@ -0,0 +1,29 @@ +package core; + +import context.ChromeContext; +import context.Context; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.openqa.selenium.Dimension; + +public abstract class TestTemplate { + private static Context context; + + public static Context getContext() { + return context; + } + + @BeforeClass + public static void setup() { + context = new ChromeContext(); + context.start(); + context.setSize(new Dimension(1600, 900)); + } + + @AfterClass + public static void quit() { + if (context != null) { + context.close(); + } + } +} diff --git a/src/test/java/paper/PaperPage.java b/src/test/java/paper/PaperPage.java new file mode 100644 index 0000000..9c3c357 --- /dev/null +++ b/src/test/java/paper/PaperPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PaperPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +} diff --git a/src/test/java/paper/PapersDashboardPage.java b/src/test/java/paper/PapersDashboardPage.java new file mode 100644 index 0000000..51d7cb8 --- /dev/null +++ b/src/test/java/paper/PapersDashboardPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PapersDashboardPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +} diff --git a/src/test/java/paper/PapersPage.java b/src/test/java/paper/PapersPage.java new file mode 100644 index 0000000..f191d9b --- /dev/null +++ b/src/test/java/paper/PapersPage.java @@ -0,0 +1,11 @@ +package paper; + +import core.PageObject; +import org.openqa.selenium.By; + +public class PapersPage extends PageObject { + + public String getSubTitle() { + return driver.findElement(By.tagName("h2")).getText(); + } +}