This commit is contained in:
Jottyfan
2023-11-28 22:44:00 +01:00
parent 48525b7e20
commit 690f28f4fe
15 changed files with 300 additions and 35 deletions

View File

@ -25,6 +25,11 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.springframework.ide.eclipse.boot.validation.springbootbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>

View File

@ -0,0 +1,2 @@
boot.validation.initialized=true
eclipse.preferences.version=1

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>BiCO</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@ -8,7 +8,7 @@ plugins {
}
group = 'de.jottyfan.bico'
version = '0.0.2'
version = '0.0.3'
description = """BibleClassOrganizer"""

View File

@ -0,0 +1,45 @@
package de.jottyfan.bico.modules.lesson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import de.jottyfan.bico.db.tables.records.TLessonRecord;
import de.jottyfan.bico.modules.CommonController;
/**
*
* @author jotty
*
*/
@Controller
public class LessonController extends CommonController {
@Autowired
private LessonService service;
@GetMapping("/lesson")
public String getPerson(@RequestParam("slotId") Integer slotId, Model model) {
model.addAttribute("bean", service.getLesson(slotId, true));
model.addAttribute("persons", service.getPersons());
model.addAttribute("slotDay", service.getSlotDay(slotId));
return "/lesson/item";
}
@PostMapping("/lesson/{id}")
public String updateLesson(@PathVariable("id") Integer lessonId, @ModelAttribute("bean") TLessonRecord bean, Model model) {
bean.setPkLesson(lessonId);
service.updateLesson(bean);
return "redirect:/sheet";
}
@GetMapping("/lesson/{id}/remove")
public String removeLesson(@PathVariable("id") Integer lessonId) {
service.removeLesson(lessonId);
return "redirect:/sheet";
}
}

View File

@ -0,0 +1,100 @@
package de.jottyfan.bico.modules.lesson;
import static de.jottyfan.bico.db.Tables.T_LESSON;
import static de.jottyfan.bico.db.Tables.T_PERSON;
import static de.jottyfan.bico.db.Tables.T_SLOT;
import java.time.LocalDate;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.InsertResultStep;
import org.jooq.Record1;
import org.jooq.SelectConditionStep;
import org.jooq.SelectWhereStep;
import org.jooq.UpdateConditionStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import de.jottyfan.bico.db.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TPersonRecord;
/**
*
* @author jotty
*
*/
@Repository
public class LessonRepository {
private final static Logger LOGGER = LogManager.getLogger(LessonRepository.class);
@Autowired
private DSLContext jooq;
public TLessonRecord getLesson(Integer slotId, Boolean createIfNecessary) {
SelectConditionStep<TLessonRecord> sql = jooq
// @formatter:off
.selectFrom(T_LESSON)
.where(T_LESSON.FK_SLOT.eq(slotId));
// @formatter:on
LOGGER.trace(sql);
TLessonRecord r = sql.fetchOne();
if (r == null && createIfNecessary) {
InsertResultStep<TLessonRecord> sql2 = jooq
// @formatter:off
.insertInto(T_LESSON,
T_LESSON.FK_SLOT)
.values(slotId)
.returning(T_LESSON.PK_LESSON);
// @formatter:on
LOGGER.trace(sql);
Integer pkLesson = sql2.fetchOne(T_LESSON.PK_LESSON);
r = new TLessonRecord();
r.setPkLesson(pkLesson);
r.setFkSlot(slotId);
}
return r;
}
public List<TPersonRecord> getPersons() {
SelectWhereStep<TPersonRecord> sql = jooq.selectFrom(T_PERSON);
LOGGER.trace(sql);
return sql.fetchStream().toList();
}
public LocalDate getSlotDay(Integer slotId) {
SelectConditionStep<Record1<LocalDate>> sql = jooq
// @formatter:off
.select(T_SLOT.SLOT_DAY)
.from(T_SLOT)
.where(T_SLOT.PK_SLOT.eq(slotId));
// @formatter:on
LOGGER.trace(sql);
return sql.fetchOne(T_SLOT.SLOT_DAY);
}
public void updateLesson(TLessonRecord bean) {
UpdateConditionStep<TLessonRecord> sql = jooq
// @formatter:off
.update(T_LESSON)
.set(T_LESSON.NOTES, bean.getNotes())
.set(T_LESSON.FK_PERSON, bean.getFkPerson())
.where(T_LESSON.PK_LESSON.eq(bean.getPkLesson()));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
public void removeLesson(Integer lessonId) {
DeleteConditionStep<TLessonRecord> sql = jooq
// @formatter:off
.deleteFrom(T_LESSON)
.where(T_LESSON.PK_LESSON.eq(lessonId));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
}

View File

@ -0,0 +1,42 @@
package de.jottyfan.bico.modules.lesson;
import java.time.LocalDate;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import de.jottyfan.bico.db.tables.records.TLessonRecord;
import de.jottyfan.bico.db.tables.records.TPersonRecord;
/**
*
* @author jotty
*
*/
@Service
public class LessonService {
@Autowired
private LessonRepository repository;
public TLessonRecord getLesson(Integer slotId, Boolean createIfNecessary) {
return repository.getLesson(slotId, createIfNecessary);
}
public List<TPersonRecord> getPersons() {
return repository.getPersons();
}
public LocalDate getSlotDay(Integer slotId) {
return repository.getSlotDay(slotId);
}
public void updateLesson(TLessonRecord bean) {
repository.updateLesson(bean);
}
public void removeLesson(Integer lessonId) {
repository.removeLesson(lessonId);
}
}

View File

@ -32,6 +32,7 @@ public class SlotController extends CommonController {
@GetMapping("/slot/{id}")
public String load(@PathVariable Integer id, Model model) {
model.addAttribute("bean", id == null ? new SlotBean() : service.loadSlot(id));
model.addAttribute("hasLesson", service.slotHasLesson(id));
return "/slot/item";
}

View File

@ -126,4 +126,20 @@ public class SlotRepository {
return null;
}
}
/**
* return true if this slot already has at least one lesson
*
* @param slotId the ID of the slot
* @return true or false
*/
public Boolean getHasLesson(Integer slotId) {
SelectConditionStep<TLessonRecord> sql = jooq
// @formatter:off
.selectFrom(T_LESSON)
.where(T_LESSON.FK_SLOT.eq(slotId));
// @formatter:on
LOGGER.trace(sql);
return sql.fetch().size() > 0;
}
}

View File

@ -36,4 +36,8 @@ public class SlotService {
public SlotBean loadDeletableSlot(Integer id) {
return repository.getSlotIfDeletable(id);
}
public Boolean slotHasLesson(Integer id) {
return id == null ? false : repository.getHasLesson(id);
}
}

View File

@ -1,16 +1,9 @@
/*
html {
width: 100%;
height: 100%;
}
*/
body {
background-color: #abc;
}
[data-bs-theme=dark] body {
background-color: rgb(36, 31, 49);
background-color: #001213;
}
.borderdist {
@ -24,3 +17,21 @@ body {
[data-bs-theme=dark] .borderdist {
background-color: #333;
}
.headerback {
background-color: #eee;
border-bottom: 1px solid silver;
}
[data-bs-theme=dark] .headerback {
background-color: #333;
}
.rightaligned {
right: 5px;
position: absolute;
}
.rightpadding64 {
margin-right: 64px;
}

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" layout:decorate="~{template}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<body>
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container">
<div class="row g-2">
<div class="col-sm-12">
<h2>Dozent-Reservierung</h2>
</div>
</div>
<form th:action="@{/lesson/{id}(id=${bean.pkLesson})}" method="post" th:object="${bean}">
<div class="row g-3">
<div class="col-sm-3">Tag</div>
<div class="col-sm-9">
<span th:text="${#temporals.format(slotDay, 'dd.MM.yyyy')}"></span>
</div>
<div class="col-sm-3">Dozent</div>
<div class="col-sm-9">
<select th:field="*{fkPerson}" class="form-select">
<option value="">--- bitte wählen ---</option>
<option th:each="p : ${persons}" th:value="${p.pkPerson}" th:text="${p.forename + ' ' + p.surname}"></option>
</select>
</div>
<div class="col-sm-3">Anmerkungen</div>
<div class="col-sm-9">
<textarea th:field="*{notes}" class="form-control"></textarea>
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-success">Speichern</button>&nbsp;
<a th:href="@{/}" class="btn btn-outline-secondary">Abbrechen</a>&nbsp;
<a th:href="@{/lesson/{id}/remove(id=${bean.pkLesson})}" class="btn btn-outline-danger">Löschen</a>
</div>
</div>
</form>
</div>
</div>
</th:block>
</body>
</html>

View File

@ -7,29 +7,26 @@
<thead>
<tr>
<th>Tag</th>
<th>Kürzel</th>
<th>Wer</th>
<th>Thema</th>
<th>Notiz</th>
<th></th>
</tr>
</thead>
<tbody>
<tr th:each="s : ${list}">
<td><span th:text="${#temporals.format(s.slotDay, 'yyyy-MM-dd')}" th:if="${s.pkLesson}"></span> <a th:href="@{/slot/{id}(id=${s.pkSlot})}"
th:text="${#temporals.format(s.slotDay, 'yyyy-MM-dd')}" class="btn btn-outline-secondary" th:unless="${s.pkLesson}"></a></td>
<td><a th:href="@{/person?slotId={id}(id=${s.pkSlot})}" class="btn btn-outline-secondary"> <span th:text="${s.abbreviation}" th:if="${s.abbreviation}"></span> <i
<td><span th:text="${#temporals.format(s.slotDay, 'yyyy-MM-dd')}"></span></td>
<td><a th:href="@{/lesson?slotId={id}(id=${s.pkSlot})}" class="btn btn-outline-secondary"> <span th:text="${s.abbreviation}" th:if="${s.abbreviation}"></span> <i
class="bi bi-pencil" th:if="${s.abbreviation == null || s.abbreviation.isBlank()}"></i>
</a></td>
<td th:text="${s.theme}"></td>
<td><a th:href="@{/slot/{id}(id=${s.pkSlot})}" class="btn btn-outline-secondary"> <span th:text="${s.slotNotes}"></span> <i class="bi bi-pencil"
th:if="${s.slotNotes == null || s.slotNotes.isBlank()}"></i>
</a></td>
<td>
<td><div th:text="${s.theme}"></div>
<div th:text="${s.subtheme}"></div></td>
<td><span th:text="${s.slotNotes}" class="rightpadding64"></span><a th:href="@{/slot/{id}(id=${s.pkSlot})}" class="btn btn-outline-secondary rightaligned"><i
class="bi bi-pencil"></i></a></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5"><a th:href="@{/slot}" class="btn btn-outline-success">einen neues Datum anlegen</a></td>
<td colspan="4"><a th:href="@{/slot}" class="btn btn-outline-success">einen neues Datum anlegen</a></td>
</tr>
</tfoot>
</table>

View File

@ -4,6 +4,11 @@
<th:block layout:fragment="content">
<div class="borderdist">
<div class="container">
<div class="row g-2">
<div class="col-sm-12">
<h2>Terminfestlegung</h2>
</div>
</div>
<form th:action="@{/slot/save}" th:object="${bean}" method="post">
<input type="hidden" th:field="*{pkSlot}" />
<div class="row g-2">
@ -19,7 +24,15 @@
<div class="col-sm-2"></div>
<div class="col-sm-10">
<button type="submit" class="btn btn-outline-primary">Speichern</button>
<a th:href="@{/slot/{id}/delete(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Löschen</a>
<th:block th:unless="${hasLesson}">
<a th:href="@{/slot/{id}/delete(id=${bean.pkSlot})}" class="btn btn-outline-danger" th:if="${bean.pkSlot}">Löschen</a>
</th:block>
</div>
<div class="col-sm-2" th:if="${hasLesson}">Hinweis</div>
<div class="col-sm-10" th:if="${hasLesson}">
<div class="alert alert-primary">Termine, die bereits für einen Dozenten reserviert wurden, können nicht gelöscht werden.
<a th:href="@{/lesson?slotId={id}(id=${bean.pkSlot})}" class="btn btn-outline-secondary">Reservierung bearbeiten</a>
</div>
</div>
</div>
</form>

View File

@ -15,14 +15,14 @@
<script th:src="@{/js/stylehelp.js}"></script>
</head>
<body>
<nav class="navbar sticky-top navbar-expand-lg headerlayout">
<nav class="navbar sticky-top navbar-expand-lg headerlayout headerback">
<button class="navbar-toggler" style="margin-right: 40px" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent" style="margin-right: 20px">
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/}">Startseite</a></li>
<li class="nav-item"><a class="btn btn-outline-secondary" th:href="@{/}" style="margin-left: 12px">Startseite</a></li>
</ul>
<ul layout:fragment="header"></ul>
<ul class="nav navbar-nav ms-auto">