slots basic info

This commit is contained in:
Jörg Henke
2024-01-04 21:53:13 +01:00
parent 8be05b8afc
commit 568dfc8a64
7 changed files with 227 additions and 4 deletions

View File

@ -60,6 +60,7 @@ public class DoneController extends CommonController {
model.addAttribute("sum", sumBean);
model.addAttribute("daysum", doneService.getDaysum(day, username));
model.addAttribute("overtimeBean", doneService.getOvertimeBean(username));
model.addAttribute("slots", doneService.getSlots(username));
model.addAttribute("schedule", weekBean.toJson());
model.addAttribute("recentList", doneService.getListRecent(username, 10));
model.addAttribute("projectList", doneService.getProjects(false));
@ -200,4 +201,12 @@ public class DoneController extends CommonController {
doneService.upsertOvertime(bean, username);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/{id}")
public String loadSlot(@PathVariable("id") Integer id, Model model) {
String username = provider.getName();
model.addAttribute("bean", doneService.getSlot(id, username));
return "/done/slot/item";
}
}

View File

@ -9,6 +9,9 @@ import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -21,6 +24,7 @@ import org.jooq.Record3;
import org.jooq.Record5;
import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep;
import org.jooq.SelectSeekStep1;
import org.jooq.UpdateConditionStep;
import org.jooq.impl.DSL;
import org.jooq.types.YearToSecond;
@ -30,6 +34,7 @@ import org.springframework.stereotype.Repository;
import de.jottyfan.timetrack.db.done.tables.records.TOvertimeRecord;
import de.jottyfan.timetrack.modules.done.model.DaysumBean;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SlotBean;
/**
*
@ -46,7 +51,7 @@ public class DoneRepository {
public DaysumBean getDaysum(LocalDate day, String login) {
Field<LocalTime> WORKTIME = DSL.field("worktime", LocalTime.class);
Field<LocalTime> BREAKTIME = DSL.field("breaktime", LocalTime.class);
SelectConditionStep<Record5<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond>> sql = jooq
// @formatter:off
.select(V_DAY.STARTTIME,
@ -71,12 +76,13 @@ public class DoneRepository {
bean.setBreaks(r.get(BREAKTIME));
YearToSecond dayOvertime = r.get(V_DAY.DAY_OVERTIME);
Duration dayOvertimeDuration = dayOvertime == null ? null : dayOvertime.toDuration();
bean.setDayOvertime(dayOvertimeDuration == null ? null : Long.valueOf(dayOvertimeDuration.toMinutes()).intValue());
bean.setDayOvertime(
dayOvertimeDuration == null ? null : Long.valueOf(dayOvertimeDuration.toMinutes()).intValue());
bean.setTotalOvertime(getOvertimeOf(day, login));
return bean;
}
}
private Integer getOvertimeOf(LocalDate day, String login) {
Field<Integer> OVERTIME = DSL.field("overtime", Integer.class);
SelectHavingStep<Record1<Integer>> sql = jooq
@ -149,4 +155,51 @@ public class DoneRepository {
LOGGER.trace(sql);
sql.execute();
}
public List<SlotBean> getSlots(String login) {
SelectSeekStep1<Record3<Integer, LocalDate, Integer>, LocalDate> sql = jooq
// @formatter:off
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
.from(T_REQUIRED_WORKTIME)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
.where(T_LOGIN.LOGIN.eq(login))
.orderBy(T_REQUIRED_WORKTIME.DAY);
// @formatter:on
LOGGER.trace(sql);
Iterator<Record3<Integer, LocalDate, Integer>> i = sql.fetch().iterator();
List<SlotBean> list = new ArrayList<>();
while (i.hasNext()) {
Record3<Integer, LocalDate, Integer> n = i.next();
list.add(SlotBean.of(n.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), n.get(T_REQUIRED_WORKTIME.DAY),
n.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES)));
}
return list;
}
/**
* get slot if login fits
*
* @param id the ID of the slot
* @param login the login
* @return the slot or null
*/
public SlotBean getSlot(Integer id, String login) {
SelectConditionStep<Record3<Integer, LocalDate, Integer>> sql = jooq
// @formatter:off
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
.from(T_REQUIRED_WORKTIME)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
.where(T_LOGIN.LOGIN.eq(login))
.and(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(id));
// @formatter:on
LOGGER.trace(sql);
Record3<Integer, LocalDate, Integer> r = sql.fetchOne();
return r == null ? null
: SlotBean.of(r.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), r.get(T_REQUIRED_WORKTIME.DAY),
r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES));
}
}

View File

@ -23,6 +23,7 @@ import de.jottyfan.timetrack.modules.done.model.DaysumBean;
import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.FavoriteBean;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SlotBean;
import de.jottyfan.timetrack.modules.note.NoteService;
/**
@ -244,4 +245,12 @@ public class DoneService {
public void upsertOvertime(OvertimeBean bean, String username) {
repository.upsertOvertime(bean.getId(), username, bean.getImpact(), bean.getOvertimeMinutes());
}
public List<SlotBean> getSlots(String username) {
return repository.getSlots(username);
}
public SlotBean getSlot(Integer id, String username) {
return repository.getSlot(id, username);
}
}

View File

@ -0,0 +1,79 @@
package de.jottyfan.timetrack.modules.done.model;
import java.io.Serializable;
import java.time.LocalDate;
/**
*
* @author jotty
*
*/
public class SlotBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private LocalDate day;
private Integer minutes;
public static final SlotBean of(Integer id, LocalDate day, Integer minutes) {
SlotBean bean = new SlotBean();
bean.setId(id);
bean.setDay(day);
bean.setMinutes(minutes);
return bean;
}
public String printTime() {
Integer hours = 0;
Integer mins = 0;
if (minutes != null) {
hours = minutes / 60;
mins = minutes % 60;
}
return String.format("%2d:%02d", hours, mins);
}
/**
* @return the day
*/
public LocalDate getDay() {
return day;
}
/**
* @param day the day to set
*/
public void setDay(LocalDate day) {
this.day = day;
}
/**
* @return the minutes
*/
public Integer getMinutes() {
return minutes;
}
/**
* @param minutes the minutes to set
*/
public void setMinutes(Integer minutes) {
this.minutes = minutes;
}
/**
* @return the id
*/
public Integer getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(Integer id) {
this.id = id;
}
}

View File

@ -402,4 +402,45 @@ body {
.golden {
color: darkgoldenrod;
}
}
.slot_badge {
white-space: nowrap;
margin-bottom: 2px;
}
.slot_badge_left {
border: 1px solid silver;
border-radius: 12px 0px 0px 12px;
background-color: #ccc;
color: black;
padding-left: 2px;
}
[data-bs-theme=dark] .slot_badge_left {
background-color: gray;
}
.slot_badge_middle {
border-top: 1px solid silver;
border-bottom: 1px solid silver;
padding-left: 2px;
padding-right: 2px;
}
.slot_badge_middle:hover {
color: white;
background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64);
}
.slot_badge_right {
border: 1px solid silver;
border-radius: 0px 12px 12px 0px;
background-color: transparent;
color: black;
padding-right: 2px;
}
[data-bs-theme=dark] .slot_badge_right {
color: white;
}

View File

@ -244,7 +244,16 @@
<div class="alert alert-info">
Zur Berechnung der täglichen Überstunden müssen Slots angelegt werden, die definieren, an welchen Tagen wieviele Stunden zu arbeiten ist.
Urlaub und Arbeitsbefreiung können durch das Entfernen des jeweiligen Slots ermöglicht werden.
Die Überstundenberechnung hängt von der Vollständigkeit der vorhandenen Slots ab; fehlen Slots, wird die Arbeitszeit jener Tage nicht eingerechnet.
</div>
<div class="container">
<div class="row">
<div class="col slot_badge" th:each="s : ${slots}">
<span class="slot_badge_left" th:text="${#temporals.format(s.day, 'EEE, dd.MM.yyyy')}"></span><a th:href="@{/done/slot/{id}(id=${s.id})}" class="slot_badge_middle"><i class="fas fa-pencil"></i></a><span class="slot_badge_right" th:text="${s.printTime()}"></span>
</div>
</div>
</div>
TODO: nur die Slots dieses Monats anzeigen, damit die Ladezeit nicht unnötig belastet wird
</div>
</div>
<script type="text/javascript">

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head>
<title>Slot aktualisieren</title>
</head>
<body>
<ul layout:fragment="menu">
</ul>
<main layout:fragment="content">
<div class="container formpane">
<div class="row" th:if="${bean}">
<div class="col-sm-3">ID</div>
<div class="col-sm-9" th:text="${bean.id}"></div>
<div class="col-sm-3">Tag</div>
<div class="col-sm-9" th:text="${#temporals.format(bean.day, 'EEEE, dd.MM.yyyy')}"></div>
<div class="col-sm-3">vereinbarte Arbeitszeit</div>
<div class="col-sm-9" th:text="${bean.printTime()}"></div>
</div>
</div>
</main>
</body>
</html>