Compare commits

..

3 Commits

Author SHA1 Message Date
568dfc8a64 slots basic info 2024-01-04 21:53:13 +01:00
8be05b8afc overtime calculation optimized 2024-01-04 20:35:26 +01:00
742446e46e corrected total overtime calculation 2024-01-04 10:01:58 +01:00
14 changed files with 502 additions and 26 deletions

View File

@ -23,7 +23,7 @@ repositories {
}
dependencies {
implementation 'de.jottyfan:timetrackjooq:20240103d'
implementation 'de.jottyfan:timetrackjooq:20240104b'
implementation 'org.apache.logging.log4j:log4j-api:latest.release'
implementation 'org.apache.logging.log4j:log4j-core:latest.release'

View File

@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.modules.done.DoneBean;
import de.jottyfan.timetrack.modules.done.DoneService;
import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.DoneModel;
import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import de.jottyfan.timetrack.modules.profile.ProfileService;

View File

@ -8,10 +8,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
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.SessionAttributes;
import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.modules.CommonController;
import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.DoneModel;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed;
@ -52,6 +59,8 @@ public class DoneController extends CommonController {
model.addAttribute("doneList", list);
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));
@ -62,6 +71,14 @@ public class DoneController extends CommonController {
model.addAttribute("favorites", doneService.getFavorites(username));
return "done/list";
}
@RolesAllowed("timetrack_user")
@PostMapping("/done/list")
public String getListForDate(Model model, @ModelAttribute("day") LocalDate day) {
DoneModel doneModel = new DoneModel();
doneModel.setDay(day);
return getList(model, doneModel);
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/abort/{day}")
@ -176,4 +193,20 @@ public class DoneController extends CommonController {
doneService.usefavorite(id);
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@PostMapping(value = "/done/overtime/update")
public String upsertOvertime(@ModelAttribute("overtimeBean") OvertimeBean bean) {
String username = provider.getName();
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

@ -45,6 +45,7 @@ import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord;
import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord;
import de.jottyfan.timetrack.db.profile.tables.records.TLoginRecord;
import de.jottyfan.timetrack.help.LocalDateHelper;
import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.FavoriteBean;
/**

View File

@ -1,25 +1,40 @@
package de.jottyfan.timetrack.modules.done;
import static de.jottyfan.timetrack.db.done.Tables.V_CURRENT_OVERTIME;
import static de.jottyfan.timetrack.db.done.Tables.T_OVERTIME;
import static de.jottyfan.timetrack.db.done.Tables.T_REQUIRED_WORKTIME;
import static de.jottyfan.timetrack.db.done.Tables.V_DAY;
import static de.jottyfan.timetrack.db.profile.Tables.T_LOGIN;
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;
import org.jooq.DSLContext;
import org.jooq.DatePart;
import org.jooq.Field;
import org.jooq.Record6;
import org.jooq.InsertOnDuplicateStep;
import org.jooq.Record1;
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;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
*
@ -36,23 +51,21 @@ 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<Record6<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond, LocalTime>> sql = jooq
SelectConditionStep<Record5<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond>> sql = jooq
// @formatter:off
.select(V_DAY.STARTTIME,
V_DAY.ENDTIME,
V_DAY.WORKTIME.cast(LocalTime.class).as(WORKTIME),
V_DAY.BREAKTIME.cast(LocalTime.class).as(BREAKTIME),
V_DAY.DAY_OVERTIME,
V_CURRENT_OVERTIME.OVERTIME.plus(DSL.coalesce(V_DAY.DAY_OVERTIME, YearToSecond.valueOf(0))).as(V_CURRENT_OVERTIME.OVERTIME))
V_DAY.DAY_OVERTIME)
.from(V_DAY)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(V_DAY.FK_LOGIN))
.leftJoin(V_CURRENT_OVERTIME).on(V_CURRENT_OVERTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN))
.where(V_DAY.DAY.eq(day))
.and(T_LOGIN.LOGIN.eq(login));
// @formatter:on
LOGGER.trace(sql);
Record6<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond, LocalTime> r = sql.fetchOne();
Record5<LocalTime, LocalTime, LocalTime, LocalTime, YearToSecond> r = sql.fetchOne();
if (r == null) {
return null;
} else {
@ -63,10 +76,130 @@ public class DoneRepository {
bean.setBreaks(r.get(BREAKTIME));
YearToSecond dayOvertime = r.get(V_DAY.DAY_OVERTIME);
Duration dayOvertimeDuration = dayOvertime == null ? null : dayOvertime.toDuration();
String dayOvertimeString = dayOvertimeDuration == null ? null : String.format("%3d:%2d", dayOvertimeDuration.toHours(), Math.abs(dayOvertimeDuration.toMinutes() % 60));
bean.setDayOvertime(dayOvertimeString == null ? "?" : dayOvertimeString);
bean.setTotalOvertime(r.get(V_CURRENT_OVERTIME.OVERTIME));
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
// @formatter:off
.select(T_OVERTIME.OVERTIME_MINUTES.plus(DSL.sum(DSL.extract(V_DAY.WORKTIME, DatePart.MINUTE).minus(T_REQUIRED_WORKTIME.REQUIRED_MINUTES))).as(OVERTIME))
.from(V_DAY)
.innerJoin(T_REQUIRED_WORKTIME).on(T_REQUIRED_WORKTIME.DAY.eq(V_DAY.DAY).and(T_REQUIRED_WORKTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN)))
.innerJoin(T_OVERTIME).on(T_OVERTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN))
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(V_DAY.FK_LOGIN))
.where(T_OVERTIME.IMPACT.ge(V_DAY.DAY.cast(LocalDateTime.class)))
.and(V_DAY.DAY.le(day))
.and(T_LOGIN.LOGIN.eq(login))
.groupBy(V_DAY.FK_LOGIN, T_OVERTIME.OVERTIME_MINUTES);
// @formatter:on
LOGGER.trace(sql);
return sql.fetchOne(OVERTIME);
}
public OvertimeBean getOvertimeBean(String login) {
SelectConditionStep<Record3<Integer, LocalDateTime, Integer>> sql = jooq
// @formatter:off
.select(T_OVERTIME.PK_OVERTIME,
T_OVERTIME.IMPACT,
T_OVERTIME.OVERTIME_MINUTES)
.from(T_OVERTIME)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_OVERTIME.FK_LOGIN))
.where(T_LOGIN.LOGIN.eq(login));
// @formatter:on
LOGGER.trace(sql);
Record3<Integer, LocalDateTime, Integer> r = sql.fetchOne();
OvertimeBean bean = new OvertimeBean();
if (r == null) {
bean.setImpact(LocalDate.now());
bean.setOvertimeMinutes(0);
} else {
bean.setId(r.get(T_OVERTIME.PK_OVERTIME));
bean.setImpact(r.get(T_OVERTIME.IMPACT).toLocalDate());
bean.setOvertimeMinutes(r.get(T_OVERTIME.OVERTIME_MINUTES));
}
return bean;
}
public void upsertOvertime(Integer pkOvertime, String login, LocalDate impact, Integer overtimeMinutes) {
if (pkOvertime == null) {
InsertOnDuplicateStep<TOvertimeRecord> sql = jooq
// @formatter:off
.insertInto(T_OVERTIME,
T_OVERTIME.IMPACT,
T_OVERTIME.OVERTIME_MINUTES,
T_OVERTIME.FK_LOGIN)
.select(jooq
.select(DSL.val(impact == null ? null : impact.atStartOfDay()), DSL.val(overtimeMinutes), T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login)));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
UpdateConditionStep<TOvertimeRecord> sql = jooq
// @formatter:off
.update(T_OVERTIME)
.set(T_OVERTIME.IMPACT, impact == null ? null : impact.atStartOfDay())
.set(T_OVERTIME.OVERTIME_MINUTES, overtimeMinutes)
.where(T_OVERTIME.PK_OVERTIME.eq(pkOvertime))
.and(T_OVERTIME.FK_LOGIN.in(jooq
.select(T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login))));
// @formatter:on
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

@ -20,7 +20,10 @@ import de.jottyfan.timetrack.db.done.tables.records.VJobRecord;
import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord;
import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord;
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;
/**
@ -234,4 +237,20 @@ public class DoneService {
public DaysumBean getDaysum(LocalDate day, String login) {
return repository.getDaysum(day, login);
}
public OvertimeBean getOvertimeBean(String login) {
return repository.getOvertimeBean(login);
}
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

@ -15,8 +15,44 @@ public class DaysumBean implements Serializable {
private LocalTime daytimeUntil;
private LocalTime dayworktime;
private LocalTime breaks;
private String dayOvertime;
private LocalTime totalOvertime;
private Integer dayOvertime;
private Integer totalOvertime;
private String lz(Integer i) {
if (i < 10) {
return "0" + i;
} else {
return i.toString();
}
}
public String printTotalOvertime() {
StringBuilder buf = new StringBuilder();
if (totalOvertime == null) {
buf.append("?");
} else {
Boolean isNegative = totalOvertime < 0;
buf.append(isNegative ? "-" : "");
buf.append(Math.abs(totalOvertime) / 60);
buf.append(":");
buf.append(lz(Math.abs(totalOvertime) % 60));
}
return buf.toString();
}
public String printDayOvertime() {
StringBuilder buf = new StringBuilder();
if (dayOvertime == null) {
buf.append("?");
} else {
Boolean isNegative = dayOvertime < 0;
buf.append(isNegative ? "-" : "");
buf.append(Math.abs(dayOvertime) / 60);
buf.append(":");
buf.append(lz(Math.abs(dayOvertime) % 60));
}
return buf.toString();
}
/**
* @return the daytimeFrom
@ -77,28 +113,28 @@ public class DaysumBean implements Serializable {
/**
* @return the dayOvertime
*/
public String getDayOvertime() {
public Integer getDayOvertime() {
return dayOvertime;
}
/**
* @param dayOvertime the dayOvertime to set
*/
public void setDayOvertime(String dayOvertime) {
public void setDayOvertime(Integer dayOvertime) {
this.dayOvertime = dayOvertime;
}
/**
* @return the totalovertime
*/
public LocalTime getTotalOvertime() {
public Integer getTotalOvertime() {
return totalOvertime;
}
/**
* @param totalovertime the totalovertime to set
*/
public void setTotalOvertime(LocalTime totalOvertime) {
public void setTotalOvertime(Integer totalOvertime) {
this.totalOvertime = totalOvertime;
}

View File

@ -1,4 +1,4 @@
package de.jottyfan.timetrack.modules.done;
package de.jottyfan.timetrack.modules.done.model;
import java.io.Serializable;
import java.time.Duration;

View File

@ -0,0 +1,62 @@
package de.jottyfan.timetrack.modules.done.model;
import java.io.Serializable;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
/**
*
* @author jotty
*
*/
public class OvertimeBean implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
@DateTimeFormat(pattern="yyyy-MM-dd")
private LocalDate impact;
private Integer overtimeMinutes;
/**
* @return the id
*/
public Integer getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(Integer id) {
this.id = id;
}
/**
* @return the impact
*/
public LocalDate getImpact() {
return impact;
}
/**
* @param impact the impact to set
*/
public void setImpact(LocalDate impact) {
this.impact = impact;
}
/**
* @return the overtimeMinutes
*/
public Integer getOvertimeMinutes() {
return overtimeMinutes;
}
/**
* @param overtimeMinutes the overtimeMinutes to set
*/
public void setOvertimeMinutes(Integer overtimeMinutes) {
this.overtimeMinutes = overtimeMinutes;
}
}

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

@ -6,8 +6,6 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import de.jottyfan.timetrack.modules.done.DoneBean;
/**
*
* @author henkej

View File

@ -310,6 +310,15 @@ body {
padding: 4px;
}
.emphpink {
font-weight: bolder;
color: #613583;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white);
padding: 4px;
}
.tab-pane-table {
background-color: white;
padding: 8px;
@ -393,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

@ -52,6 +52,8 @@
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_module">Modul</a></li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_job">Aufgabe</a></li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_billing">Abrechnung</a></li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_overtime">Überstunden</a></li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_slot">Slots</a></li>
</ul>
<div class="tabdivblurred tab-content">
<div id="div_list" class="tab-pane active tab-pane-table">
@ -104,8 +106,8 @@
<td>Ende: <span class="emphgreen" th:text="${#temporals.format(daysum.daytimeUntil, 'HH:mm')}" th:if="${daysum.daytimeUntil}"></span></td>
<td>Arbeitszeit total: <span class="emphblue" th:text="${#temporals.format(daysum.dayworktime, 'HH:mm')}" th:if="${daysum.dayworktime}"></span></td>
<td>Pausezeit total: <span class="emphorange" th:text="${#temporals.format(daysum.breaks, 'HH:mm')}" th:if="${daysum.breaks}"></span></td>
<td>Überstunden heute: <span class="emphred" th:text="${daysum.dayOvertime}"></span></td>
<td colspan="2">Überstunden total: <span class="emphred" th:text="${#temporals.format(daysum.totalOvertime, 'HH:mm')}" th:if="${daysum.totalOvertime}"></span></td>
<td>Überstunden heute: <span class="emphred" th:text="${daysum.printDayOvertime()}"></span></td>
<td colspan="2">Überstunden total: <span class="emphpink" th:text="${daysum.printTotalOvertime()}" th:if="${daysum.totalOvertime}"></span></td>
</tr>
<tr>
<td></td>
@ -213,6 +215,46 @@
</tbody>
</table>
</div>
<div id="div_overtime" class="tab-pane fade tab-pane-table">
<form th:action="@{/done/overtime/update}" method="post" th:object="${overtimeBean}">
<input type="hidden" th:field="*{id}" />
<div class="container">
<div class="row g-3">
<div class="col-sm-12">
<div class="alert alert-info">Hier werden die Überstunden einmalig angegeben. Dabei wird für einen bestimmten Tagesbeginn, an dem die Überstunden bekannt sind, der Wert gesetzt. Alle
nachfolgenden Zeiten werden bei der Anzeige der Überstunden während der Datenerfassung berücksichtigt und einberechnet.</div>
</div>
<div class="col-sm-3">Tagesbeginn</div>
<div class="col-sm-9">
<input type="date" th:field="*{impact}" class="form-control" />
</div>
<div class="col-sm-3">Überstunden (min)</div>
<div class="col-sm-9">
<input type="number" th:field="*{overtimeMinutes}" class="form-control" />
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-primary">Übernehmen</button>
</div>
</div>
</div>
</form>
</div>
<div id="div_slot" class="tab-pane fade tab-pane-table">
<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">
$(document).ready(function() {

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>