Compare commits
15 Commits
689a601c8c
...
master
Author | SHA1 | Date | |
---|---|---|---|
d95b3a1600 | |||
2ef8a48488 | |||
5dcc64ac74 | |||
23bab9a2b4 | |||
1d532e322c | |||
c757bb5916 | |||
c4615765a5 | |||
5b296d39e9 | |||
a4bcc00363 | |||
4820232b31 | |||
f8f501f1b2 | |||
a52793de46 | |||
e38f62fa72 | |||
1f71d9edeb | |||
b779590309 |
32
build.gradle
32
build.gradle
@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '3.1.3'
|
||||
id 'org.springframework.boot' version '3.4.3'
|
||||
id 'java'
|
||||
id 'war'
|
||||
}
|
||||
@ -7,7 +7,7 @@ plugins {
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'de.jottyfan'
|
||||
version = '1.4.3'
|
||||
version = '1.5.7'
|
||||
|
||||
description = """timetrack"""
|
||||
|
||||
@ -23,28 +23,26 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'de.jottyfan:timetrackjooq:20240105'
|
||||
implementation 'de.jottyfan:timetrackjooq:20240109'
|
||||
|
||||
implementation 'org.apache.logging.log4j:log4j-api:latest.release'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:latest.release'
|
||||
implementation 'org.apache.logging.log4j:log4j-to-slf4j:latest.release'
|
||||
implementation 'org.apache.logging.log4j:log4j-api:2.24.3'
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.24.3'
|
||||
implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.3'
|
||||
|
||||
implementation 'org.webjars:bootstrap:5.3.1'
|
||||
implementation 'org.webjars:font-awesome:6.4.2'
|
||||
implementation 'org.webjars:bootstrap:5.3.3'
|
||||
implementation 'org.webjars:font-awesome:6.7.2'
|
||||
implementation 'org.webjars:jquery:3.7.1'
|
||||
implementation 'org.webjars:popper.js:2.11.7'
|
||||
implementation 'org.webjars:datatables:1.13.5'
|
||||
implementation 'org.webjars:jquery-ui:1.13.2'
|
||||
implementation 'org.webjars:fullcalendar:5.11.3'
|
||||
implementation 'org.webjars:datatables:2.1.8'
|
||||
implementation 'org.webjars:jquery-ui:1.14.1'
|
||||
implementation 'org.webjars:fullcalendar:6.1.9'
|
||||
|
||||
implementation 'com.google.code.gson:gson:latest.release';
|
||||
|
||||
implementation 'org.webjars.bowergithub.datatables:datatables:1.10.21'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jooq'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
|
||||
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.1.2'
|
||||
implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.4.2'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
@ -67,9 +65,9 @@ war {
|
||||
"Implementation-Timestamp": new Date())
|
||||
}
|
||||
}
|
||||
baseName = project.name
|
||||
version = version
|
||||
archiveName = 'timetrack.war'
|
||||
archiveBaseName = project.name
|
||||
archiveVersion = version
|
||||
archiveFileName = 'timetrack.war'
|
||||
}
|
||||
|
||||
test {
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,5 +1,7 @@
|
||||
package de.jottyfan.timetrack;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
@ -8,15 +10,17 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableTransactionManagement
|
||||
public class TimetrackApplication extends SpringBootServletInitializer {
|
||||
public class Main extends SpringBootServletInitializer {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(Main.class);
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(
|
||||
SpringApplicationBuilder application) {
|
||||
return application.sources(TimetrackApplication.class);
|
||||
return application.sources(Main.class);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TimetrackApplication.class, args);
|
||||
SpringApplication.run(Main.class, args);
|
||||
}
|
||||
}
|
@ -39,8 +39,9 @@ public class InitialConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void disableLogo() {
|
||||
public Boolean disableLogo() {
|
||||
System.setProperty("org.jooq.no-logo", "true");
|
||||
return true;
|
||||
}
|
||||
|
||||
public DefaultConfiguration configuration() {
|
||||
|
@ -45,7 +45,7 @@ public class IndexController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/")
|
||||
public String getIndex(@ModelAttribute DoneModel doneModel, Model model, OAuth2AuthenticationToken token) {
|
||||
public String getIndex(@ModelAttribute("doneModel") DoneModel doneModel, Model model, OAuth2AuthenticationToken token) {
|
||||
String username = provider.getName();
|
||||
Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file
|
||||
LocalDate day = LocalDate.now();
|
||||
|
@ -61,14 +61,13 @@ public class CalendarDoneRepository {
|
||||
String billing = r.get(T_BILLING.NAME);
|
||||
LocalDateTime start = r.get(T_DONE.TIME_FROM);
|
||||
LocalDateTime end = r.get(T_DONE.TIME_UNTIL);
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append(billing).append(billing == null ? "" : "; ");
|
||||
buf.append(job).append(job == null ? "" : " - ");
|
||||
buf.append(module).append(module == null ? "" : ": ");
|
||||
buf.append(project);
|
||||
String title = buf.toString();
|
||||
String title = String.format("%s %s %s %s", blankIfNull(billing, "; "), blankIfNull(project, " - "), blankIfNull(module, ": "), blankIfNull(job, "")).trim();
|
||||
list.add(EventBean.ofEvent(id, title, start, end));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private final String blankIfNull(String s, String appendix) {
|
||||
return s == null ? "" : s.concat(appendix);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public class ContactController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/contact/edit/{id}")
|
||||
public String toItem(@PathVariable Integer id, Model model) {
|
||||
public String toItem(@PathVariable("id") Integer id, Model model) {
|
||||
ContactBean bean = contactService.getBean(id);
|
||||
if (bean == null) {
|
||||
bean = new ContactBean(); // the add case
|
||||
@ -62,14 +62,14 @@ public class ContactController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/contact/upsert")
|
||||
public String doUpsert(Model model, @ModelAttribute ContactBean bean) {
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") ContactBean bean) {
|
||||
Integer amount = contactService.doUpsert(bean);
|
||||
return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/contact/delete/{id}")
|
||||
public String doDelete(@PathVariable Integer id, Model model) {
|
||||
public String doDelete(@PathVariable("id") Integer id, Model model) {
|
||||
Integer amount = contactService.doDelete(id);
|
||||
return amount.equals(1) ? getList(model) : toItem(id, model);
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ 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.SlotBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.SlotRangeBean;
|
||||
import de.jottyfan.timetrack.modules.done.model.SummaryBean;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -55,6 +57,11 @@ public class DoneController extends CommonController {
|
||||
SummaryBean sumBean = new SummaryBean(list, day, maxWorkTime);
|
||||
SummaryBean weekBean = new SummaryBean(week, day, maxWorkTime);
|
||||
model.addAttribute("doneList", list);
|
||||
Duration sumtimeDuration = Duration.ofMinutes(0);
|
||||
for (DoneBean bean : list) {
|
||||
sumtimeDuration = sumtimeDuration.plus(bean.getTimeDiffDuration());
|
||||
}
|
||||
model.addAttribute("sumtime", String.format("%02d:%02d", sumtimeDuration.toHours(), sumtimeDuration.toMinutes() % 60));
|
||||
model.addAttribute("sum", sumBean);
|
||||
model.addAttribute("daysum", doneService.getDaysum(day, username));
|
||||
model.addAttribute("overtimeBean", doneService.getOvertimeBean(username));
|
||||
@ -72,21 +79,26 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/done/list")
|
||||
public String getListForDate(Model model, @ModelAttribute("day") LocalDate day) {
|
||||
DoneModel doneModel = new DoneModel();
|
||||
doneModel.setDay(day);
|
||||
public String getListForDate(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
|
||||
return getList(model, doneModel);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/update/{id}")
|
||||
public String updateField(@PathVariable("id") Integer fkDone, @PathParam("field") String field, @PathParam("value") Integer value) {
|
||||
doneService.updateField(fkDone, field, value);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/abort/{day}")
|
||||
public String abort(@PathVariable String day, Model model) {
|
||||
public String abort(@PathVariable("day") String day, Model model) {
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/add/{day}")
|
||||
public String toAdd(@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate day, Model model) {
|
||||
public String toAdd(@PathVariable("day") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate day, Model model) {
|
||||
DoneBean bean = new DoneBean();
|
||||
bean.setLocalDate(day);
|
||||
return toItem(bean, model);
|
||||
@ -103,7 +115,7 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/edit/{id}")
|
||||
public String toItem(@PathVariable Integer id, Model model) {
|
||||
public String toItem(@PathVariable("id") Integer id, Model model) {
|
||||
DoneBean bean = doneService.getBean(id);
|
||||
if (bean == null) {
|
||||
bean = new DoneBean(); // the add case; typically, only add from today
|
||||
@ -113,7 +125,7 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/end/{id}")
|
||||
public String end(@PathVariable Integer id, Model model) {
|
||||
public String end(@PathVariable("id") Integer id, Model model) {
|
||||
DoneBean bean = doneService.getBean(id);
|
||||
String username = provider.getName();
|
||||
doneService.endToNow(bean, username);
|
||||
@ -122,7 +134,7 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/copy/{id}")
|
||||
public String copyFromNow(@PathVariable Integer id, Model model) {
|
||||
public String copyFromNow(@PathVariable("id") Integer id, Model model) {
|
||||
DoneBean bean = doneService.getBean(id);
|
||||
String username = provider.getName();
|
||||
doneService.copyFromNow(bean, username);
|
||||
@ -131,7 +143,7 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/done/upsert")
|
||||
public String doUpsert(Model model, @ModelAttribute DoneBean bean) {
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") DoneBean bean) {
|
||||
String username = provider.getName();
|
||||
Integer amount = doneService.doUpsert(bean, username);
|
||||
return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(bean.getPk(), model);
|
||||
@ -139,7 +151,7 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/addrecent/{id}")
|
||||
public String addRecent(Model model, @PathVariable Integer id) {
|
||||
public String addRecent(Model model, @PathVariable("id") Integer id) {
|
||||
String username = provider.getName();
|
||||
DoneBean bean = doneService.getBean(id);
|
||||
doneService.addRecent(bean, username);
|
||||
@ -164,28 +176,28 @@ public class DoneController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/delete/{id}")
|
||||
public String doDelete(@PathVariable Integer id, Model model) {
|
||||
public String doDelete(@PathVariable("id") Integer id, Model model) {
|
||||
Integer amount = doneService.doDelete(id);
|
||||
return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(id, model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/favorize/{id}")
|
||||
public String favorize(@PathVariable Integer id) {
|
||||
public String favorize(@PathVariable("id") Integer id) {
|
||||
doneService.favorize(id);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/unfavorize/{id}")
|
||||
public String unfavorize(@PathVariable Integer id) {
|
||||
public String unfavorize(@PathVariable("id") Integer id) {
|
||||
doneService.unfavorize(id);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/usefav/{id}")
|
||||
public String usefavorite(@PathVariable Integer id) {
|
||||
public String usefavorite(@PathVariable("id") Integer id) {
|
||||
doneService.usefavorite(id);
|
||||
return "redirect:/done/list";
|
||||
}
|
||||
@ -217,13 +229,44 @@ public class DoneController extends CommonController {
|
||||
@PostMapping("/done/slot/upsert")
|
||||
public String upsertSlot(@ModelAttribute("bean") SlotBean bean, Model model) {
|
||||
doneService.upsert(bean, provider.getName());
|
||||
return "redirect:/done/list";
|
||||
return "redirect:/done/list#div_slot";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/slot/{id}/delete")
|
||||
public String deleteSlot(@PathVariable("id") Integer slotId) {
|
||||
doneService.delete(slotId, provider.getName());
|
||||
return "redirect:/done/list";
|
||||
return "redirect:/done/list#div_slot";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/slot/range")
|
||||
public String toAddRange(Model model) {
|
||||
model.addAttribute("bean", new SlotRangeBean());
|
||||
return "/done/slot/range";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/done/slot/addrange")
|
||||
public String addRange(@ModelAttribute("bean") SlotRangeBean bean) {
|
||||
doneService.addSlotRange(bean.getMinutes(), bean.getFrom(), bean.getUntil(), bean.getReason(), provider.getName(),
|
||||
bean.getIncludeSaturday(), bean.getIncludeSunday());
|
||||
return "redirect:/done/list#div_slot";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/slot/back")
|
||||
public String oneMonthBack(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
|
||||
LocalDate day = doneModel.getDay();
|
||||
doneModel.setDay(day.minusMonths(1));
|
||||
return "redirect:/done/list#div_slot";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/slot/forward")
|
||||
public String oneMonthForward(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
|
||||
LocalDate day = doneModel.getDay();
|
||||
doneModel.setDay(day.plusMonths(1));
|
||||
return "redirect:/done/list#div_slot";
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package de.jottyfan.timetrack.modules.done;
|
||||
|
||||
import static de.jottyfan.timetrack.db.done.Tables.T_DONE;
|
||||
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;
|
||||
@ -9,8 +10,10 @@ import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -21,19 +24,23 @@ import org.jooq.DeleteConditionStep;
|
||||
import org.jooq.Field;
|
||||
import org.jooq.InsertOnDuplicateSetMoreStep;
|
||||
import org.jooq.InsertOnDuplicateStep;
|
||||
import org.jooq.InsertReturningStep;
|
||||
import org.jooq.Record1;
|
||||
import org.jooq.Record3;
|
||||
import org.jooq.Record4;
|
||||
import org.jooq.Record5;
|
||||
import org.jooq.Row4;
|
||||
import org.jooq.SelectConditionStep;
|
||||
import org.jooq.SelectHavingStep;
|
||||
import org.jooq.SelectSeekStep1;
|
||||
import org.jooq.TableField;
|
||||
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.TDoneRecord;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TOvertimeRecord;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TRequiredWorktimeRecord;
|
||||
import de.jottyfan.timetrack.modules.done.model.DaysumBean;
|
||||
@ -91,12 +98,12 @@ public class DoneRepository {
|
||||
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))
|
||||
.select(T_OVERTIME.OVERTIME_MINUTES.plus(DSL.sum(DSL.extract(V_DAY.WORKTIME, DatePart.EPOCH).div(60).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)))
|
||||
.where(T_OVERTIME.IMPACT.le(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);
|
||||
@ -214,6 +221,33 @@ public class DoneRepository {
|
||||
r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES), r.get(T_REQUIRED_WORKTIME.REASON));
|
||||
}
|
||||
|
||||
private String nullIfEmpty(String s) {
|
||||
return s == null ? null : (s.isBlank() ? null : s);
|
||||
}
|
||||
|
||||
public void addSlotRange(Integer minutes, String login, String reason, List<LocalDate> days) {
|
||||
Integer fkLogin = jooq.select(T_LOGIN.PK).from(T_LOGIN).where(T_LOGIN.LOGIN.eq(login)).fetchOne(T_LOGIN.PK);
|
||||
|
||||
List<Row4<LocalDate, Integer, String, Integer>> rows = new ArrayList<Row4<LocalDate,Integer,String,Integer>>();
|
||||
for(LocalDate day : days) {
|
||||
rows.add(DSL.row(day, minutes, nullIfEmpty(reason), fkLogin));
|
||||
}
|
||||
InsertReturningStep<TRequiredWorktimeRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.insertInto(T_REQUIRED_WORKTIME,
|
||||
T_REQUIRED_WORKTIME.DAY,
|
||||
T_REQUIRED_WORKTIME.REQUIRED_MINUTES,
|
||||
T_REQUIRED_WORKTIME.REASON,
|
||||
T_REQUIRED_WORKTIME.FK_LOGIN)
|
||||
.valuesOfRows(rows)
|
||||
.onConflict(T_REQUIRED_WORKTIME.FK_LOGIN, T_REQUIRED_WORKTIME.DAY)
|
||||
.doUpdate()
|
||||
.setAllToExcluded();
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public void addSlot(SlotBean bean, String login) {
|
||||
InsertOnDuplicateSetMoreStep<TRequiredWorktimeRecord> sql = jooq
|
||||
// @formatter:off
|
||||
@ -223,13 +257,13 @@ public class DoneRepository {
|
||||
T_REQUIRED_WORKTIME.REASON,
|
||||
T_REQUIRED_WORKTIME.FK_LOGIN)
|
||||
.select(jooq
|
||||
.select(DSL.val(bean.getDay()), DSL.val(bean.getMinutes()), DSL.val(bean.getReason()), T_LOGIN.PK)
|
||||
.select(DSL.val(bean.getDay()), DSL.val(bean.getMinutes()), DSL.val(nullIfEmpty(bean.getReason())), T_LOGIN.PK)
|
||||
.from(T_LOGIN)
|
||||
.where(T_LOGIN.LOGIN.eq(login)))
|
||||
.onConflict(T_REQUIRED_WORKTIME.FK_LOGIN, T_REQUIRED_WORKTIME.DAY)
|
||||
.doUpdate()
|
||||
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
|
||||
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason());
|
||||
.set(T_REQUIRED_WORKTIME.REASON, nullIfEmpty(bean.getReason()));
|
||||
// @formatter:off
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
@ -240,7 +274,7 @@ public class DoneRepository {
|
||||
// @formatter:off
|
||||
.update(T_REQUIRED_WORKTIME)
|
||||
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
|
||||
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason())
|
||||
.set(T_REQUIRED_WORKTIME.REASON, nullIfEmpty(bean.getReason()))
|
||||
.where(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(bean.getId()))
|
||||
.and(T_REQUIRED_WORKTIME.FK_LOGIN.in(jooq
|
||||
.select(T_LOGIN.PK)
|
||||
@ -264,4 +298,22 @@ public class DoneRepository {
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* update the field only
|
||||
*
|
||||
* @param fkDone the ID
|
||||
* @param value the value
|
||||
* @param tableField the field
|
||||
*/
|
||||
public void updateField(Integer fkDone, Integer value, TableField<TDoneRecord, Integer> tableField) {
|
||||
UpdateConditionStep<TDoneRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.update(T_DONE)
|
||||
.set(tableField, value)
|
||||
.where(T_DONE.PK.eq(fkDone));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
sql.execute();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package de.jottyfan.timetrack.modules.done;
|
||||
|
||||
import static de.jottyfan.timetrack.db.done.Tables.T_DONE;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
@ -305,4 +308,37 @@ public class DoneService {
|
||||
public void delete(Integer slotId, String username) {
|
||||
repository.deleteSlot(slotId, username);
|
||||
}
|
||||
|
||||
public void addSlotRange(Integer minutes, LocalDate from, LocalDate until, String reason, String username, Boolean includeSaturdays, Boolean includeSundays) {
|
||||
List<LocalDate> days = new ArrayList<>();
|
||||
if (!from.isBefore(until)) {
|
||||
LocalDate tmp = from;
|
||||
from = until;
|
||||
until = tmp;
|
||||
}
|
||||
includeSaturdays = includeSaturdays == null ? false : includeSaturdays;
|
||||
includeSundays = includeSundays == null ? false : includeSundays;
|
||||
for (LocalDate i = from; i.isBefore(until.plusDays(1)); i = i.plusDays(1)) {
|
||||
if (i.getDayOfWeek().equals(DayOfWeek.SUNDAY) && !includeSundays) {
|
||||
// ignore
|
||||
} else if (i.getDayOfWeek().equals(DayOfWeek.SATURDAY) && !includeSaturdays) {
|
||||
// ignore
|
||||
} else {
|
||||
days.add(i);
|
||||
}
|
||||
}
|
||||
repository.addSlotRange(minutes, username, reason, days);
|
||||
}
|
||||
|
||||
public void updateField(Integer fkDone, String field, Integer value) {
|
||||
if ("project".equals(field)) {
|
||||
repository.updateField(fkDone, value, T_DONE.FK_PROJECT);
|
||||
} else if ("module".equals(field)) {
|
||||
repository.updateField(fkDone, value, T_DONE.FK_MODULE);
|
||||
} else if ("job".equals(field)) {
|
||||
repository.updateField(fkDone, value, T_DONE.FK_JOB);
|
||||
} else {
|
||||
LOGGER.error("field {} not supported yet", field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,11 @@ 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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TJobRecord;
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
import de.jottyfan.timetrack.modules.done.DoneController;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneModel;
|
||||
import de.jottyfan.timetrack.modules.profile.ProfileService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@ -27,9 +24,6 @@ public class JobController extends CommonController {
|
||||
@Autowired
|
||||
private JobService jobService;
|
||||
|
||||
@Autowired
|
||||
private DoneController doneController;
|
||||
|
||||
@Autowired
|
||||
private OAuth2Provider provider;
|
||||
|
||||
@ -38,7 +32,7 @@ public class JobController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/edit/job/{id}")
|
||||
public String toJob(@PathVariable Integer id, Model model) {
|
||||
public String toJob(@PathVariable("id") Integer id, Model model) {
|
||||
String username = provider.getName();
|
||||
TJobRecord job = jobService.get(id);
|
||||
model.addAttribute("jobBean", job);
|
||||
@ -47,21 +41,21 @@ public class JobController extends CommonController {
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/upsert/job", method = RequestMethod.POST)
|
||||
public String doUpsert(Model model, @ModelAttribute TJobRecord bean) {
|
||||
@PostMapping("/done/upsert/job")
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") TJobRecord bean) {
|
||||
Integer amount = jobService.doUpsert(bean);
|
||||
return amount.equals(1) ? "redirect:/done/list": toJob(bean.getPk(), model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/add/job", method = RequestMethod.GET)
|
||||
@GetMapping("/done/add/job")
|
||||
public String toAddJob(Model model) {
|
||||
return toJob(null, model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/delete/job/{id}")
|
||||
public String doDeleteJob(@PathVariable Integer id, Model model) {
|
||||
public String doDeleteJob(@PathVariable("id") Integer id, Model model) {
|
||||
Integer amount = jobService.doDelete(id);
|
||||
return amount.equals(1) ? "redirect:/done/list" : toJob(id, model);
|
||||
}
|
||||
|
@ -0,0 +1,104 @@
|
||||
package de.jottyfan.timetrack.modules.done.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class SlotRangeBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer minutes;
|
||||
private LocalDate from;
|
||||
private LocalDate until;
|
||||
private String reason;
|
||||
private Boolean includeSaturday;
|
||||
private Boolean includeSunday;
|
||||
|
||||
/**
|
||||
* @return the minutes
|
||||
*/
|
||||
public Integer getMinutes() {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param minutes the minutes to set
|
||||
*/
|
||||
public void setMinutes(Integer minutes) {
|
||||
this.minutes = minutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the from
|
||||
*/
|
||||
public LocalDate getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param from the from to set
|
||||
*/
|
||||
public void setFrom(LocalDate from) {
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the until
|
||||
*/
|
||||
public LocalDate getUntil() {
|
||||
return until;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param until the until to set
|
||||
*/
|
||||
public void setUntil(LocalDate until) {
|
||||
this.until = until;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the reason
|
||||
*/
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param reason the reason to set
|
||||
*/
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the includeSaturday
|
||||
*/
|
||||
public Boolean getIncludeSaturday() {
|
||||
return includeSaturday;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param includeSaturday the includeSaturday to set
|
||||
*/
|
||||
public void setIncludeSaturday(Boolean includeSaturday) {
|
||||
this.includeSaturday = includeSaturday;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the includeSunday
|
||||
*/
|
||||
public Boolean getIncludeSunday() {
|
||||
return includeSunday;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param includeSunday the includeSunday to set
|
||||
*/
|
||||
public void setIncludeSunday(Boolean includeSunday) {
|
||||
this.includeSunday = includeSunday;
|
||||
}
|
||||
}
|
@ -6,14 +6,11 @@ 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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord;
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
import de.jottyfan.timetrack.modules.done.DoneController;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneModel;
|
||||
import de.jottyfan.timetrack.modules.profile.ProfileService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@ -28,9 +25,6 @@ public class ModuleController extends CommonController {
|
||||
@Autowired
|
||||
private ModuleService moduleService;
|
||||
|
||||
@Autowired
|
||||
private DoneController doneController;
|
||||
|
||||
@Autowired
|
||||
private OAuth2Provider provider;
|
||||
|
||||
@ -39,7 +33,7 @@ public class ModuleController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/edit/module/{id}")
|
||||
public String toModule(@PathVariable Integer id, Model model) {
|
||||
public String toModule(@PathVariable("id") Integer id, Model model) {
|
||||
String username = provider.getName();
|
||||
TModuleRecord module = moduleService.get(id);
|
||||
model.addAttribute("moduleBean", module);
|
||||
@ -48,21 +42,21 @@ public class ModuleController extends CommonController {
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/upsert/module", method = RequestMethod.POST)
|
||||
public String doUpsert(Model model, @ModelAttribute TModuleRecord bean) {
|
||||
@PostMapping("/done/upsert/module")
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") TModuleRecord bean) {
|
||||
Integer amount = moduleService.doUpsert(bean);
|
||||
return amount.equals(1) ? "redirect:/done/list" : toModule(bean.getPk(), model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/add/module", method = RequestMethod.GET)
|
||||
@GetMapping("/done/add/module")
|
||||
public String toAddModule(Model model) {
|
||||
return toModule(null, model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/delete/module/{id}")
|
||||
public String doDeleteModule(@PathVariable Integer id, Model model) {
|
||||
public String doDeleteModule(@PathVariable("id") Integer id, Model model) {
|
||||
Integer amount = moduleService.doDelete(id);
|
||||
return amount.equals(1) ? "redirect:/done/list" : toModule(id, model);
|
||||
}
|
||||
|
@ -6,14 +6,11 @@ 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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord;
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
import de.jottyfan.timetrack.modules.done.DoneController;
|
||||
import de.jottyfan.timetrack.modules.done.model.DoneModel;
|
||||
import de.jottyfan.timetrack.modules.profile.ProfileService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
@ -27,9 +24,6 @@ public class ProjectController extends CommonController {
|
||||
@Autowired
|
||||
private ProjectService projectService;
|
||||
|
||||
@Autowired
|
||||
private DoneController doneController;
|
||||
|
||||
@Autowired
|
||||
private OAuth2Provider provider;
|
||||
|
||||
@ -38,7 +32,7 @@ public class ProjectController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/done/edit/project/{id}")
|
||||
public String toProject(@PathVariable Integer id, Model model) {
|
||||
public String toProject(@PathVariable("id") Integer id, Model model) {
|
||||
String username = provider.getName();
|
||||
TProjectRecord project = projectService.get(id);
|
||||
model.addAttribute("projectBean", project);
|
||||
@ -47,21 +41,21 @@ public class ProjectController extends CommonController {
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/upsert/project", method = RequestMethod.POST)
|
||||
public String doUpsert(Model model, @ModelAttribute TProjectRecord bean) {
|
||||
@PostMapping("/done/upsert/project")
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") TProjectRecord bean) {
|
||||
Integer amount = projectService.doUpsert(bean);
|
||||
return amount.equals(1) ? "redirect:/done/list" : toProject(bean.getPk(), model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@RequestMapping(value = "/done/add/project", method = RequestMethod.GET)
|
||||
@GetMapping("/done/add/project")
|
||||
public String toAddProject(Model model) {
|
||||
return toProject(null, model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/done/delete/project/{id}")
|
||||
public String doDeleteProject(@PathVariable Integer id, Model model) {
|
||||
public String doDeleteProject(@PathVariable("id") Integer id, Model model) {
|
||||
Integer amount = projectService.doDelete(id);
|
||||
return amount.equals(1) ? "redirect:/done/list" : toProject(id, model);
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class NoteController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/note/edit/{id}")
|
||||
public String toItem(@PathVariable Integer id, Model model) {
|
||||
public String toItem(@PathVariable("id") Integer id, Model model) {
|
||||
NoteBean bean = noteService.getBean(id);
|
||||
if (bean == null) {
|
||||
bean = new NoteBean(); // the add case
|
||||
@ -57,14 +57,14 @@ public class NoteController extends CommonController {
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/note/upsert")
|
||||
public String doUpsert(Model model, @ModelAttribute NoteBean bean, OAuth2AuthenticationToken token) {
|
||||
public String doUpsert(Model model, @ModelAttribute("bean") NoteBean bean, OAuth2AuthenticationToken token) {
|
||||
Integer amount = noteService.doUpsert(bean);
|
||||
return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model);
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping(value = "/note/delete/{id}")
|
||||
public String doDelete(@PathVariable Integer id, Model model, OAuth2AuthenticationToken token) {
|
||||
public String doDelete(@PathVariable("id") Integer id, Model model, OAuth2AuthenticationToken token) {
|
||||
Integer amount = noteService.doDelete(id);
|
||||
return amount.equals(1) ? getList(model) : toItem(id, model);
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class ProfileController extends CommonController {
|
||||
private ProfileService service;
|
||||
|
||||
@PostMapping("/profile/{theme}")
|
||||
public String setTheme(@PathVariable String theme) {
|
||||
public String setTheme(@PathVariable("theme") String theme) {
|
||||
String username = provider.getName();
|
||||
service.setTheme(username, theme);
|
||||
return "redirect:/";
|
||||
|
@ -0,0 +1,29 @@
|
||||
package de.jottyfan.timetrack.modules.style;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import de.jottyfan.timetrack.component.OAuth2Provider;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
public class DynamicStyleController {
|
||||
|
||||
@Autowired
|
||||
private DynamicStyleService service;
|
||||
|
||||
@Autowired
|
||||
private OAuth2Provider provider;
|
||||
|
||||
@GetMapping(value = "/public/dynamicstyle.css", produces = "text/css")
|
||||
public @ResponseBody String getDynamicCss(HttpServletRequest request) {
|
||||
return service.getDynamicCssOf(provider.getName());
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package de.jottyfan.timetrack.modules.style;
|
||||
|
||||
import static de.jottyfan.timetrack.db.profile.Tables.T_PROFILE;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.Record1;
|
||||
import org.jooq.SelectConditionStep;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class DynamicStyleRepository {
|
||||
private static final Logger LOGGER = LogManager.getLogger(DynamicStyleRepository.class);
|
||||
|
||||
@Autowired
|
||||
private DSLContext jooq;
|
||||
|
||||
public String getDynamicStyle(String name) {
|
||||
SelectConditionStep<Record1<String>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_PROFILE.DYNAMIC_CSS)
|
||||
.from(T_PROFILE)
|
||||
.where(T_PROFILE.USERNAME.eq(name));
|
||||
// @formatter:on
|
||||
LOGGER.trace(sql);
|
||||
String result = sql.fetchOne(T_PROFILE.DYNAMIC_CSS);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package de.jottyfan.timetrack.modules.style;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class DynamicStyleService {
|
||||
|
||||
@Autowired
|
||||
private DynamicStyleRepository repository;
|
||||
|
||||
public String getDynamicCssOf(String name) {
|
||||
return name == null ? "" : repository.getDynamicStyle(name);
|
||||
}
|
||||
}
|
@ -44,8 +44,8 @@ body {
|
||||
|
||||
@media(min-width:1600px) {
|
||||
.tabdivblurred {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
width: 1111px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,6 +210,15 @@ body {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.ES {
|
||||
color: black;
|
||||
background: radial-gradient(rgb(111, 255, 209), rgb(1, 113, 52)) !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .ES {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
@ -275,48 +284,62 @@ body {
|
||||
}
|
||||
|
||||
.emphgreen {
|
||||
font-weight: bolder;
|
||||
color: #136600;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.emphblue {
|
||||
font-weight: bolder;
|
||||
color: #1a5fb4;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.emphorange {
|
||||
font-weight: bolder;
|
||||
color: #c64600;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.emphred {
|
||||
font-weight: bolder;
|
||||
color: #a51d2d;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.emphpink {
|
||||
font-weight: bolder;
|
||||
color: #613583;
|
||||
border: 1px solid gray;
|
||||
border-radius: 8px;
|
||||
background-image: linear-gradient(to left, #e6e6e6, white);
|
||||
}
|
||||
|
||||
.emphgray {
|
||||
color: #5e5c64;
|
||||
background-image: linear-gradient(to left, #959595, #e6e6e6);
|
||||
}
|
||||
|
||||
.unround-border {
|
||||
padding: 4px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.border-frame {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.round-border {
|
||||
border-radius: 8px;
|
||||
font-weight: bolder;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.round-border-right {
|
||||
font-weight: bolder;
|
||||
padding: 4px;
|
||||
border-radius: 0px 8px 8px 0px;
|
||||
}
|
||||
|
||||
.sumfield {
|
||||
border: 1px solid;
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
padding-right: 0px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tab-pane-table {
|
||||
|
@ -53,12 +53,16 @@ class Schedule {
|
||||
}
|
||||
|
||||
time2pixel = function (time, hourHeight) {
|
||||
if (time == null) {
|
||||
return 0;
|
||||
} else {
|
||||
var timeArray = time.split(":");
|
||||
var hours = parseInt(timeArray[0]);
|
||||
var minutes = parseInt(timeArray[1]);
|
||||
var pixels = parseInt((hours + (minutes / 60)) * hourHeight);
|
||||
return pixels;
|
||||
}
|
||||
}
|
||||
|
||||
drawSlot = function(ctx, slotNr, from, until, color, fillColor) {
|
||||
ctx.strokeStyle = color;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<!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}">
|
||||
<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>Arbeitszeit</title>
|
||||
</head>
|
||||
@ -8,7 +7,9 @@
|
||||
<font layout:fragment="title">Arbeitszeit</font>
|
||||
<ul layout:fragment="menuitem">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-primary btn-white-text" th:href="@{/done/list/previousday}" style="width: 50px; float: left;"><i class="fa fa-chevron-left"></i></a>
|
||||
<a class="nav-link btn btn-primary btn-white-text" th:href="@{/done/list/previousday}" style="width: 50px; float: left;">
|
||||
<i class="fa fa-chevron-left"></i>
|
||||
</a>
|
||||
<form th:action="@{/done/list}" th:object="${doneModel}" method="post" style="float: left;">
|
||||
<div class="nav-link" style="padding-top: 5px !important; padding-bottom: 0px !important">
|
||||
<div class="input-group input-group-sm mb-3" style="margin-bottom: 0px !important">
|
||||
@ -17,20 +18,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<a class="nav-link btn btn-primary btn-white-text" th:href="@{/done/list/nextday}" style="width: 50px; float: left;"><i class="fa fa-chevron-right"></i></a>
|
||||
<a class="nav-link btn btn-primary btn-white-text" th:href="@{/done/list/nextday}" style="width: 50px; float: left;">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<table>
|
||||
<tr>
|
||||
<td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer
|
||||
Eintrag</a></td>
|
||||
<td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer Eintrag</a></td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-white-text dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Letzte Einträge
|
||||
</button>
|
||||
<button class="btn btn-white-text dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Letzte Einträge</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li th:each="recent : ${recentList}">
|
||||
<a class="dropdown-item" th:href="@{/done/addrecent/{id}(id=${recent.pk})}"
|
||||
@ -39,24 +39,53 @@
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding-left: 8px"><a class="nav-link btn-list" th:href="@{/done/list}"><i class="fas fa-sync"></i></a></td>
|
||||
<td style="padding-left: 8px"><a class="nav-link btn-list" th:href="@{/done/list}">
|
||||
<i class="fas fa-sync"></i>
|
||||
</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<ul class="nav nav-tabs navback" role="tablist">
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle active" data-bs-toggle="tab" href="#div_list">Liste</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_schedule">Kalender</a></li>
|
||||
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_project">Projekt</a></li>
|
||||
<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 id="worktimetabs" class="nav nav-tabs navback" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link navlinkstyle active" data-bs-toggle="tab" href="#div_list">Liste</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_schedule">Kalender</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_project">Projekt</a>
|
||||
</li>
|
||||
<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">
|
||||
<script th:inline="javascript">
|
||||
function submitDropdown(field) {
|
||||
const value = field.value;
|
||||
const id = field.getAttribute("data-id");
|
||||
const fld = field.getAttribute("data-field");
|
||||
|
||||
const url_prefix = /*[[@{/done/update/}]]*/ "#";
|
||||
const url = url_prefix + id + "?field=" + fld + "&value=" + value;
|
||||
window.location.href = url;
|
||||
}
|
||||
</script>
|
||||
<table class="table table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -71,7 +100,9 @@
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Favoriten</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li th:each="f : ${favorites}">
|
||||
<a class="dropdown-item" th:href="@{/done/usefav/{id}(id=${f.fkFavorite})}"><span th:text="${f.project} + ' ' + ${f.module} + ' ' + ${f.job}"></span></a>
|
||||
<a class="dropdown-item" th:href="@{/done/usefav/{id}(id=${f.fkFavorite})}">
|
||||
<span th:text="${f.project} + ' ' + ${f.module} + ' ' + ${f.job}"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -80,45 +111,53 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="done : ${doneList}">
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span th:text="${done.timeNote}"></span></a>
|
||||
<a th:if="${done.timeUntil == null}" style="margin-left: 4px" class="btn-list" th:href="@{/done/end/{id}(id=${done.pk})}" title="aktuelle Uhrzeit setzen"><i class="fa fa-clock"></i></a>
|
||||
</td>
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span th:text="${done.timeDiff}"></span></a></td>
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span class="boldtext"
|
||||
th:text="${done.project?.name}"></span></a></td>
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span class="boldtext"
|
||||
th:text="${done.module?.name}"></span></a></td>
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span class="boldtext"
|
||||
th:text="${done.activity?.name}"></span></a></td>
|
||||
<td><span th:text="${done.billing.shortcut}" th:class="'billing ' + ${done.billing.csskey}"
|
||||
th:if="${done.billing != null}"></span></td>
|
||||
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}">
|
||||
<span th:text="${done.timeNote}"></span>
|
||||
</a> <a th:if="${done.timeUntil == null}" style="margin-left: 4px" class="btn-list" th:href="@{/done/end/{id}(id=${done.pk})}" title="aktuelle Uhrzeit setzen">
|
||||
<i class="fa fa-clock"></i>
|
||||
</a></td>
|
||||
<td><span th:text="${done.timeDiff}"></span></td>
|
||||
<td>
|
||||
<a class="btn-list" th:href="@{/done/copy/{id}(id=${done.pk})}" title="Aufgabe neu beginnen"><i class="fa fa-copy"></i></a>
|
||||
<select onchange="submitDropdown(this)" th:data-id="${done.pk}" data-field="project">
|
||||
<option value="">---</option>
|
||||
<option th:each="p : ${projectList}" th:value="${p.pk}" th:text="${p.name}" th:selected="${done.project?.name == p.name ? 'selected' : 'false'}"></option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select onchange="submitDropdown(this)" th:data-id="${done.pk}" data-field="module">
|
||||
<option value="">---</option>
|
||||
<option th:each="m : ${moduleList}" th:value="${m.pk}" th:text="${m.name}" th:selected="${done.module?.name == m.name ? 'selected' : 'false'}"></option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select onchange="submitDropdown(this)" th:data-id="${done.pk}" data-field="job">
|
||||
<option value="">---</option>
|
||||
<option th:each="j : ${jobList}" th:value="${j.pk}" th:text="${j.name}" th:selected="${done.activity?.name == j.name ? 'selected' : 'false'}"></option>
|
||||
</select>
|
||||
</td>
|
||||
<td><span th:text="${done.billing.shortcut}" th:class="'billing ' + ${done.billing.csskey}" th:if="${done.billing != null}"></span></td>
|
||||
<td><a class="btn-list" th:href="@{/done/copy/{id}(id=${done.pk})}" title="Aufgabe neu beginnen"><i class="fa fa-copy"></i></a>
|
||||
<a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"><i class="fa fa-edit"></i></a>
|
||||
<a class="btn-list" th:href="@{/done/favorize/{id}(id=${done.pk})}" title="als Favorit speichern" th:if="${!done.isFavorite}"><i class="far fa-star golden"></i></a>
|
||||
<a class="btn-list" th:href="@{/done/unfavorize/{id}(id=${done.pk})}" title="Favoritenstatus entfernen" th:if="${done.isFavorite}"><i class="fas fa-star golden"></i></a>
|
||||
</td>
|
||||
<a class="btn-list" th:href="@{/done/unfavorize/{id}(id=${done.pk})}" title="Favoritenstatus entfernen" th:if="${done.isFavorite}"><i class="fas fa-star golden"></i></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr th:if="${daysum}">
|
||||
<td>Start: <span class="emphgreen" th:text="${#temporals.format(daysum.daytimeFrom, 'HH:mm')}" th:if="${daysum.daytimeFrom}"></span></td>
|
||||
<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.printDayOvertime()}"></span></td>
|
||||
<td colspan="2">Überstunden total: <span class="emphpink" th:text="${daysum.printTotalOvertime()}" th:if="${daysum.totalOvertime}"></span></td>
|
||||
<td colspan="7"><span class="sumfield">Start: <span class="emphgreen round-border-right" th:text="${#temporals.format(daysum.daytimeFrom, 'HH:mm')}" th:if="${daysum.daytimeFrom}"></span></span>
|
||||
<span class="sumfield">Ende: <span class="emphgreen round-border-right" th:text="${#temporals.format(daysum.daytimeUntil, 'HH:mm')}" th:if="${daysum.daytimeUntil}"></span></span>
|
||||
<span class="sumfield">Tagessumme: <span class="emphblue unround-border" th:text="${#temporals.format(daysum.dayworktime, 'HH:mm')}" th:if="${daysum.dayworktime}"></span><span class="emphgray round-border-right" th:text="'/ ' + ${sumtime}"></span></span>
|
||||
<span class="sumfield">Pausezeit total: <span class="emphorange round-border-right" th:text="${#temporals.format(daysum.breaks, 'HH:mm')}" th:if="${daysum.breaks}"></span></span>
|
||||
<span class="sumfield">Überstunden heute: <span class="emphred round-border-right" th:text="${daysum.printDayOvertime()}"></span></span>
|
||||
<span class="sumfield">Überstunden total: <span class="emphpink round-border-right" th:text="${daysum.printTotalOvertime()}"></span></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP2') != '0,0 h'}"><span class="billing WP2">WP2</span><span
|
||||
th:text="${sum.getBillingTime('WP2')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP4') != '0,0 h'}"><span class="billing WP4">WP4</span><span
|
||||
th:text="${sum.getBillingTime('WP4')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP5') != '0,0 h'}"><span class="billing WP5">WP5</span><span
|
||||
th:text="${sum.getBillingTime('WP5')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('TA3') != '0,0 h'}"><span class="billing TA3">TA3</span><span
|
||||
th:text="${sum.getBillingTime('TA3')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP2') != '0,0 h'}"><span class="billing WP2">WP2</span><span th:text="${sum.getBillingTime('WP2')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP4') != '0,0 h'}"><span class="billing WP4">WP4</span><span th:text="${sum.getBillingTime('WP4')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('WP5') != '0,0 h'}"><span class="billing WP5">WP5</span><span th:text="${sum.getBillingTime('WP5')}" class="distfat"></span></span></td>
|
||||
<td><span th:if="${sum.getBillingTime('TA3') != '0,0 h'}"><span class="billing TA3">TA3</span><span th:text="${sum.getBillingTime('TA3')}" class="distfat"></span></span></td>
|
||||
<td colspan="2"><span class="billing">X</span><span th:text="${sum.getBillingTime(null)}" class="distfat"></span></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@ -140,13 +179,14 @@
|
||||
<tr th:each="project : ${projectList}">
|
||||
<td><span th:text="${project.name}"></span></td>
|
||||
<td><span th:text="${project.percentUsage}"></span></td>
|
||||
<td><a th:href="@{/done/edit/project/{id}(id=${project.pk})}" th:title="${project.pk}"><i class="fa fa-edit"></i></a></td>
|
||||
<td><a th:href="@{/done/edit/project/{id}(id=${project.pk})}" th:title="${project.pk}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3"><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/project}">neues Projekt</a>
|
||||
</td>
|
||||
<td colspan="3"><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/project}">neues Projekt</a></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@ -164,7 +204,9 @@
|
||||
<tr th:each="module : ${moduleList}">
|
||||
<td><span th:text="${module.name}"></span></td>
|
||||
<td><span th:text="${module.percentUsage}"></span></td>
|
||||
<td><a th:href="@{/done/edit/module/{id}(id=${module.pk})}" th:title="${module.pk}"><i class="fa fa-edit"></i></a></td>
|
||||
<td><a th:href="@{/done/edit/module/{id}(id=${module.pk})}" th:title="${module.pk}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@ -187,7 +229,9 @@
|
||||
<tr th:each="job : ${jobList}">
|
||||
<td><span th:text="${job.name}"></span></td>
|
||||
<td><span th:text="${job.percentUsage}"></span></td>
|
||||
<td><a th:href="@{/done/edit/job/{id}(id=${job.pk})}" th:title="${job.pk}"><i class="fa fa-edit"></i></a></td>
|
||||
<td><a th:href="@{/done/edit/job/{id}(id=${job.pk})}" th:title="${job.pk}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@ -242,9 +286,8 @@
|
||||
</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.
|
||||
Die Überstundenberechnung hängt von der Vollständigkeit der vorhandenen Slots ab; fehlen Slots, wird die Arbeitszeit jener Tage nicht eingerechnet.<br />
|
||||
Hier werden nur die Slots für diesen Monat angezeigt.
|
||||
Zur Berechnung der täglichen Überstunden müssen Slots angelegt werden, die definieren, an welchen Tagen wieviele Stunden zu arbeiten ist. Die Überstundenberechnung hängt von der
|
||||
Vollständigkeit der vorhandenen Slots ab; fehlen Slots, wird die Arbeitszeit jener Tage nicht eingerechnet.<br /> Hier werden nur die Slots für diesen Monat angezeigt.
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row row-weekday">
|
||||
@ -259,45 +302,65 @@
|
||||
<div class="row row-weekday">
|
||||
<div class="col slot_badge" th:each="o : ${slotOffset}"></div>
|
||||
<div class="col slot_badge" th:each="s : ${slots}">
|
||||
<span class="slot_badge_left" th:text="${#temporals.format(s.day, 'dd.MM.')}">
|
||||
</span><a th:href="@{/done/slot/{id}(id=${s.id})}" class="slot_badge_middle" th:if="${s.id}">
|
||||
<span class="slot_badge_left" th:text="${#temporals.format(s.day, 'dd.MM.')}"></span><a th:href="@{/done/slot/{id}(id=${s.id})}" class="slot_badge_middle" th:if="${s.id}">
|
||||
<i class="fas fa-pencil"></i>
|
||||
</a><a th:href="@{/done/slot/add?day={d}(d=${s.day})}" class="slot_badge_middle" th:unless="${s.id}">
|
||||
<i class="fas fa-plus"></i>
|
||||
</a>
|
||||
<span class="slot_badge_middle slot_reason" th:if="${s.reason}" th:text="${s.reason}"></span><span th:class="${s.reason != null ? 'slot_badge_right' : 'slot_badge_right boldy'}" th:text="${s.printTime()}" th:if="${s.id}"></span><span
|
||||
class="slot_badge_right" th:unless="${s.id}"> --:-- </span>
|
||||
<span class="slot_badge_middle slot_reason" th:if="${s.reason}" th:text="${s.reason}"></span><span th:class="${s.reason != null ? 'slot_badge_right' : 'slot_badge_right boldy'}"
|
||||
th:text="${s.printTime()}" th:if="${s.id}"></span><span class="slot_badge_right" th:unless="${s.id}"> --:-- </span>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col-2"><a th:href="@{/done/slot/back}" class="btn btn-outline-primary"><- zurück</a></div>
|
||||
<div class="col-8">
|
||||
<a th:href="@{/done/slot/range}" class="btn btn-outline-primary">mehrere Slots auf einmal anlegen</a>
|
||||
</div>
|
||||
<div class="col-2"><a th:href="@{/done/slot/forward}" class="btn btn-outline-primary">weiter -></a></div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row alert alert-info">
|
||||
<div class="col-sm-12">
|
||||
<span style="text-decoration: underline">Legende</span>
|
||||
</div>
|
||||
<div class="col">
|
||||
Üb: Überstunden, Mehrarbeit<br />
|
||||
Ur: Urlaub, Sonderurlaub, Kur<br />
|
||||
gF: gesetzlicher Feiertag<br />
|
||||
Üb: Überstunden, Mehrarbeit<br /> Ur: Urlaub, Sonderurlaub, Kur<br /> gF: gesetzlicher Feiertag<br />
|
||||
</div>
|
||||
<div class="col">
|
||||
Kr: Arbeits- und Dienstunfähigkeit<br />
|
||||
Gl: Freistellung aus Gleitzeitguthaben<br />
|
||||
Ar: Arbeits- und Dienstbefreiung<br />
|
||||
Kr: Arbeits- und Dienstunfähigkeit<br /> Gl: Freistellung aus Gleitzeitguthaben<br /> Ar: Arbeits- und Dienstbefreiung<br />
|
||||
</div>
|
||||
<div class="col">
|
||||
mK: "mit Kind krank"<br />
|
||||
Di: Dienstreise, Dienstgänge<br />
|
||||
mK: "mit Kind krank"<br /> Di: Dienstreise, Dienstgänge<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
let width = parseInt($("#schedule").css("min-width"));
|
||||
let height = parseInt($("#schedule").css("min-height"));
|
||||
var schedule = new Schedule("#schedule", width, height);
|
||||
var ctx = $("#scheduleCanvas")[0].getContext("2d");
|
||||
var currentDayRecords = JSON.parse('[(${schedule})]');
|
||||
$(document)
|
||||
.ready(
|
||||
function() {
|
||||
// the tab deeplink functionality
|
||||
let url = location.href.replace(/\/$/, "");
|
||||
if (location.hash) {
|
||||
const hash = url.split("#");
|
||||
$('#worktimetabs a[href="#' + hash[1] + '"]').tab("show");
|
||||
url = location.href.replace(/\/#/, "#");
|
||||
history.replaceState(null, null, url);
|
||||
}
|
||||
|
||||
// the schedule
|
||||
let width = parseInt($("#schedule").css(
|
||||
"min-width"));
|
||||
let height = parseInt($("#schedule").css(
|
||||
"min-height"));
|
||||
var schedule = new Schedule("#schedule", width,
|
||||
height);
|
||||
var ctx = $("#scheduleCanvas")[0]
|
||||
.getContext("2d");
|
||||
var currentDayRecords = JSON
|
||||
.parse('[(${schedule})]');
|
||||
var scheduleRecords = currentDayRecords.schedule;
|
||||
for (var i = 0; i < scheduleRecords.length; i++) {
|
||||
var r = scheduleRecords[i];
|
||||
@ -313,7 +376,9 @@
|
||||
color = "#00aa00";
|
||||
}
|
||||
/* daySlot 7 = sunday, but this should be slot 0 */
|
||||
schedule.drawSlot(ctx, r.daySlot > 6 ? 0 : r.daySlot, r.from, r.until, "black", color);
|
||||
schedule.drawSlot(ctx, r.daySlot > 6 ? 0
|
||||
: r.daySlot, r.from, r.until,
|
||||
"black", color);
|
||||
}
|
||||
var localeUrl = '[[@{/js/dataTables/de.json}]]';
|
||||
$("#project_table").DataTable({
|
||||
|
@ -6,7 +6,7 @@
|
||||
<body>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-outline-primary btn-white-text" th:href="@{/done/list}">zur Arbeitszeit</a>
|
||||
<a class="nav-link btn btn-outline-primary btn-white-text" th:href="@{/done/list#div_slot}">zur Slotübersicht</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
|
59
src/main/resources/templates/done/slot/range.html
Normal file
59
src/main/resources/templates/done/slot/range.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!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">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-outline-primary btn-white-text" th:href="@{/done/list#div_slot}">zur Slotübersicht</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="container formpane">
|
||||
<form th:action="@{/done/slot/addrange}" method="post" th:object="${bean}">
|
||||
<div class="row g-2" th:if="${bean}">
|
||||
<div class="col-sm-3">ab</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" th:field="*{from}" class="form-control" />
|
||||
</div>
|
||||
<div class="col-sm-3">bis</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" th:field="*{until}" class="form-control" />
|
||||
</div>
|
||||
<div class="col-sm-3">vereinbarte Arbeitszeit in Minuten</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="number" th:field="*{minutes}" class="form-control">
|
||||
</div>
|
||||
<div class="col-sm-3">Abweichungsgrund</div>
|
||||
<div class="col-sm-9">
|
||||
<select th:field="*{reason}" class="form-select">
|
||||
<option value="">-</option>
|
||||
<option value="Ar">Arbeits- und Dienstbefreiung</option>
|
||||
<option value="Di">Dienstreise, Dienstgänge</option>
|
||||
<option value="gF">gesetzlicher Feiertag</option>
|
||||
<option value="Gl">Freistellung aus Gleitzeitguthaben</option>
|
||||
<option value="Kr">Arbeits- und Dienstunfähigkeit</option>
|
||||
<option value="mK">"mit Kind krank"</option>
|
||||
<option value="Ur">Urlaub, Sonderurlaub, Kur</option>
|
||||
<option value="Üb">Überstunden, Mehrarbeit</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-3">inklusive Samstage</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="checkbox" th:checked="*{includeSaturday}" name="includeSaturday" />
|
||||
</div>
|
||||
<div class="col-sm-3">inklusive Sonntage</div>
|
||||
<div class="col-sm-9">
|
||||
<input type="checkbox" th:checked="*{includeSunday}" name="includeSunday" />
|
||||
</div>
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
<button type="submit" class="btn btn-outline-primary">Anlegen</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -5,19 +5,19 @@
|
||||
|
||||
<title>Timetrack</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.3.1/css/bootstrap.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/1.13.5/css/dataTables.bootstrap5.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/6.4.2/css/all.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/fullcalendar/5.11.3/main.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.3.3/css/bootstrap.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/2.1.8/css/dataTables.dataTables.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/6.7.2/css/all.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/fullcalendar/6.1.9/main.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/css/style.css}">
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/public/dynamicstyle.css}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" th:href="@{/png/favicon/favicon-32x32.png}" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.png}" />
|
||||
|
||||
<script th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script>
|
||||
<script th:src="@{/webjars/bootstrap/5.3.1/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/webjars/datatables/1.13.5/js/jquery.dataTables.min.js}"></script>
|
||||
<script th:src="@{/webjars/datatables/1.13.5/js/dataTables.bootstrap5.min.js}"></script>
|
||||
<script th:src="@{/webjars/fullcalendar/5.11.3/main.js}"></script>
|
||||
<script th:src="@{/webjars/bootstrap/5.3.3/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/webjars/datatables/2.1.8/js/dataTables.dataTables.min.js}"></script>
|
||||
<script th:src="@{/webjars/fullcalendar/6.1.9/main.js}"></script>
|
||||
<script th:src="@{/js/helper.js}"></script>
|
||||
<script th:src="@{/js/clock.js}"></script>
|
||||
<script th:src="@{/js/schedule.js}"></script>
|
||||
|
@ -17,15 +17,15 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-8"><span class="spanlabel">Start:</span></div>
|
||||
<div class="col-4"><span class="emphgreen" th:text="${sum.start}"></span></div>
|
||||
<div class="col-4"><span class="emphgreen round-border border-frame" th:text="${sum.start}"></span></div>
|
||||
<div class="col-8"><span class="spanlabel">Ende:</span></div>
|
||||
<div class="col-4"><span class="emphgreen" th:text="${sum.end}"></span></div>
|
||||
<div class="col-4"><span class="emphgreen round-border border-frame" th:text="${sum.end}"></span></div>
|
||||
<div class="col-8"><span class="spanlabel">Arbeitszeit total:</span></div>
|
||||
<div class="col-4"><span class="emphblue" th:text="${sum.total}"></span></div>
|
||||
<div class="col-4"><span class="emphblue round-border border-frame" th:text="${sum.total}"></span></div>
|
||||
<div class="col-8"><span class="spanlabel">Pausezeit total:</span></div>
|
||||
<div class="col-4"><span class="emphorange" th:text="${sum.pause}"></span></div>
|
||||
<div class="col-4"><span class="emphorange round-border border-frame" th:text="${sum.pause}"></span></div>
|
||||
<div class="col-8"><span class="spanlabel">Überstunden:</span></div>
|
||||
<div class="col-4"><span class="emphred" th:text="${sum.overdue}"></span></div>
|
||||
<div class="col-4"><span class="emphred round-border border-frame" th:text="${sum.overdue}"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user