Compare commits

...

13 Commits

Author SHA1 Message Date
d95b3a1600 library upgrades 2025-03-03 22:54:44 +01:00
2ef8a48488 finetuning 2025-01-09 19:04:31 +01:00
5dcc64ac74 support lange desktops 2025-01-09 13:41:16 +01:00
23bab9a2b4 added new billing ElementSearcher style 2025-01-06 14:37:30 +01:00
1d532e322c editing inline in table by direct request 2024-12-02 17:40:46 +01:00
c757bb5916 library upgrades 2024-06-11 21:25:48 +02:00
c4615765a5 another finetuning 2024-01-10 09:49:01 +01:00
5b296d39e9 finetuning 2024-01-10 09:40:04 +01:00
a4bcc00363 display finetuning 2024-01-09 17:04:48 +01:00
4820232b31 summary time calculation enhanced 2024-01-09 16:44:05 +01:00
f8f501f1b2 added dynamic css 2024-01-09 10:17:00 +01:00
a52793de46 corrected date change 2024-01-05 21:26:53 +01:00
e38f62fa72 fixed overtime calculation 2024-01-05 21:18:32 +01:00
25 changed files with 470 additions and 254 deletions

View File

@ -1,5 +1,5 @@
plugins { plugins {
id 'org.springframework.boot' version '3.1.3' id 'org.springframework.boot' version '3.4.3'
id 'java' id 'java'
id 'war' id 'war'
} }
@ -7,7 +7,7 @@ plugins {
apply plugin: 'io.spring.dependency-management' apply plugin: 'io.spring.dependency-management'
group = 'de.jottyfan' group = 'de.jottyfan'
version = '1.4.5' version = '1.5.7'
description = """timetrack""" description = """timetrack"""
@ -23,28 +23,26 @@ repositories {
} }
dependencies { 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-api:2.24.3'
implementation 'org.apache.logging.log4j:log4j-core:latest.release' implementation 'org.apache.logging.log4j:log4j-core:2.24.3'
implementation 'org.apache.logging.log4j:log4j-to-slf4j:latest.release' implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.24.3'
implementation 'org.webjars:bootstrap:5.3.1' implementation 'org.webjars:bootstrap:5.3.3'
implementation 'org.webjars:font-awesome:6.4.2' implementation 'org.webjars:font-awesome:6.7.2'
implementation 'org.webjars:jquery:3.7.1' implementation 'org.webjars:jquery:3.7.1'
implementation 'org.webjars:popper.js:2.11.7' implementation 'org.webjars:popper.js:2.11.7'
implementation 'org.webjars:datatables:1.13.5' implementation 'org.webjars:datatables:2.1.8'
implementation 'org.webjars:jquery-ui:1.13.2' implementation 'org.webjars:jquery-ui:1.14.1'
implementation 'org.webjars:fullcalendar:5.11.3' implementation 'org.webjars:fullcalendar:6.1.9'
implementation 'com.google.code.gson:gson:latest.release'; 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-jooq'
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
implementation "org.springframework.boot:spring-boot-starter-oauth2-client" 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-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-test'
@ -67,9 +65,9 @@ war {
"Implementation-Timestamp": new Date()) "Implementation-Timestamp": new Date())
} }
} }
baseName = project.name archiveBaseName = project.name
version = version archiveVersion = version
archiveName = 'timetrack.war' archiveFileName = 'timetrack.war'
} }
test { test {

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,5 +1,7 @@
package de.jottyfan.timetrack; 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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
@ -8,15 +10,17 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication @SpringBootApplication
@EnableTransactionManagement @EnableTransactionManagement
public class TimetrackApplication extends SpringBootServletInitializer { public class Main extends SpringBootServletInitializer {
public static final Logger LOGGER = LogManager.getLogger(Main.class);
@Override @Override
protected SpringApplicationBuilder configure( protected SpringApplicationBuilder configure(
SpringApplicationBuilder application) { SpringApplicationBuilder application) {
return application.sources(TimetrackApplication.class); return application.sources(Main.class);
} }
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(TimetrackApplication.class, args); SpringApplication.run(Main.class, args);
} }
} }

View File

@ -39,8 +39,9 @@ public class InitialConfiguration {
} }
@Bean @Bean
public void disableLogo() { public Boolean disableLogo() {
System.setProperty("org.jooq.no-logo", "true"); System.setProperty("org.jooq.no-logo", "true");
return true;
} }
public DefaultConfiguration configuration() { public DefaultConfiguration configuration() {

View File

@ -45,7 +45,7 @@ public class IndexController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/") @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(); String username = provider.getName();
Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file Duration maxWorkTime = Duration.ofHours(8); // TODO: to the configuration file
LocalDate day = LocalDate.now(); LocalDate day = LocalDate.now();

View File

@ -61,14 +61,13 @@ public class CalendarDoneRepository {
String billing = r.get(T_BILLING.NAME); String billing = r.get(T_BILLING.NAME);
LocalDateTime start = r.get(T_DONE.TIME_FROM); LocalDateTime start = r.get(T_DONE.TIME_FROM);
LocalDateTime end = r.get(T_DONE.TIME_UNTIL); LocalDateTime end = r.get(T_DONE.TIME_UNTIL);
StringBuilder buf = new StringBuilder(); String title = String.format("%s %s %s %s", blankIfNull(billing, "; "), blankIfNull(project, " - "), blankIfNull(module, ": "), blankIfNull(job, "")).trim();
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();
list.add(EventBean.ofEvent(id, title, start, end)); list.add(EventBean.ofEvent(id, title, start, end));
} }
return list; return list;
} }
private final String blankIfNull(String s, String appendix) {
return s == null ? "" : s.concat(appendix);
}
} }

View File

@ -50,7 +50,7 @@ public class ContactController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/contact/edit/{id}") @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); ContactBean bean = contactService.getBean(id);
if (bean == null) { if (bean == null) {
bean = new ContactBean(); // the add case bean = new ContactBean(); // the add case
@ -62,14 +62,14 @@ public class ContactController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@PostMapping("/contact/upsert") @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); Integer amount = contactService.doUpsert(bean);
return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model); return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/contact/delete/{id}") @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); Integer amount = contactService.doDelete(id);
return amount.equals(1) ? getList(model) : toItem(id, model); return amount.equals(1) ? getList(model) : toItem(id, model);
} }

View File

@ -24,6 +24,7 @@ import de.jottyfan.timetrack.modules.done.model.SlotBean;
import de.jottyfan.timetrack.modules.done.model.SlotRangeBean; import de.jottyfan.timetrack.modules.done.model.SlotRangeBean;
import de.jottyfan.timetrack.modules.done.model.SummaryBean; import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.websocket.server.PathParam;
/** /**
* *
@ -56,6 +57,11 @@ public class DoneController extends CommonController {
SummaryBean sumBean = new SummaryBean(list, day, maxWorkTime); SummaryBean sumBean = new SummaryBean(list, day, maxWorkTime);
SummaryBean weekBean = new SummaryBean(week, day, maxWorkTime); SummaryBean weekBean = new SummaryBean(week, day, maxWorkTime);
model.addAttribute("doneList", list); 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("sum", sumBean);
model.addAttribute("daysum", doneService.getDaysum(day, username)); model.addAttribute("daysum", doneService.getDaysum(day, username));
model.addAttribute("overtimeBean", doneService.getOvertimeBean(username)); model.addAttribute("overtimeBean", doneService.getOvertimeBean(username));
@ -73,21 +79,26 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@PostMapping("/done/list") @PostMapping("/done/list")
public String getListForDate(Model model, @ModelAttribute("day") LocalDate day) { public String getListForDate(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
DoneModel doneModel = new DoneModel();
doneModel.setDay(day);
return getList(model, 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") @RolesAllowed("timetrack_user")
@GetMapping("/done/abort/{day}") @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"; return "redirect:/done/list";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/add/{day}") @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(); DoneBean bean = new DoneBean();
bean.setLocalDate(day); bean.setLocalDate(day);
return toItem(bean, model); return toItem(bean, model);
@ -104,7 +115,7 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/edit/{id}") @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); DoneBean bean = doneService.getBean(id);
if (bean == null) { if (bean == null) {
bean = new DoneBean(); // the add case; typically, only add from today bean = new DoneBean(); // the add case; typically, only add from today
@ -114,7 +125,7 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/end/{id}") @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); DoneBean bean = doneService.getBean(id);
String username = provider.getName(); String username = provider.getName();
doneService.endToNow(bean, username); doneService.endToNow(bean, username);
@ -123,7 +134,7 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/copy/{id}") @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); DoneBean bean = doneService.getBean(id);
String username = provider.getName(); String username = provider.getName();
doneService.copyFromNow(bean, username); doneService.copyFromNow(bean, username);
@ -132,7 +143,7 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@PostMapping("/done/upsert") @PostMapping("/done/upsert")
public String doUpsert(Model model, @ModelAttribute DoneBean bean) { public String doUpsert(Model model, @ModelAttribute("bean") DoneBean bean) {
String username = provider.getName(); String username = provider.getName();
Integer amount = doneService.doUpsert(bean, username); Integer amount = doneService.doUpsert(bean, username);
return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(bean.getPk(), model); return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(bean.getPk(), model);
@ -140,7 +151,7 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/addrecent/{id}") @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(); String username = provider.getName();
DoneBean bean = doneService.getBean(id); DoneBean bean = doneService.getBean(id);
doneService.addRecent(bean, username); doneService.addRecent(bean, username);
@ -165,28 +176,28 @@ public class DoneController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/delete/{id}") @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); Integer amount = doneService.doDelete(id);
return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(id, model); return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(id, model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/favorize/{id}") @GetMapping(value = "/done/favorize/{id}")
public String favorize(@PathVariable Integer id) { public String favorize(@PathVariable("id") Integer id) {
doneService.favorize(id); doneService.favorize(id);
return "redirect:/done/list"; return "redirect:/done/list";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/unfavorize/{id}") @GetMapping(value = "/done/unfavorize/{id}")
public String unfavorize(@PathVariable Integer id) { public String unfavorize(@PathVariable("id") Integer id) {
doneService.unfavorize(id); doneService.unfavorize(id);
return "redirect:/done/list"; return "redirect:/done/list";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/usefav/{id}") @GetMapping(value = "/done/usefav/{id}")
public String usefavorite(@PathVariable Integer id) { public String usefavorite(@PathVariable("id") Integer id) {
doneService.usefavorite(id); doneService.usefavorite(id);
return "redirect:/done/list"; return "redirect:/done/list";
} }
@ -218,14 +229,14 @@ public class DoneController extends CommonController {
@PostMapping("/done/slot/upsert") @PostMapping("/done/slot/upsert")
public String upsertSlot(@ModelAttribute("bean") SlotBean bean, Model model) { public String upsertSlot(@ModelAttribute("bean") SlotBean bean, Model model) {
doneService.upsert(bean, provider.getName()); doneService.upsert(bean, provider.getName());
return "redirect:/done/list"; return "redirect:/done/list#div_slot";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/slot/{id}/delete") @GetMapping("/done/slot/{id}/delete")
public String deleteSlot(@PathVariable("id") Integer slotId) { public String deleteSlot(@PathVariable("id") Integer slotId) {
doneService.delete(slotId, provider.getName()); doneService.delete(slotId, provider.getName());
return "redirect:/done/list"; return "redirect:/done/list#div_slot";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@ -240,6 +251,22 @@ public class DoneController extends CommonController {
public String addRange(@ModelAttribute("bean") SlotRangeBean bean) { public String addRange(@ModelAttribute("bean") SlotRangeBean bean) {
doneService.addSlotRange(bean.getMinutes(), bean.getFrom(), bean.getUntil(), bean.getReason(), provider.getName(), doneService.addSlotRange(bean.getMinutes(), bean.getFrom(), bean.getUntil(), bean.getReason(), provider.getName(),
bean.getIncludeSaturday(), bean.getIncludeSunday()); bean.getIncludeSaturday(), bean.getIncludeSunday());
return "redirect:/done/list"; 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";
} }
} }

View File

@ -1,5 +1,6 @@
package de.jottyfan.timetrack.modules.done; 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_OVERTIME;
import static de.jottyfan.timetrack.db.done.Tables.T_REQUIRED_WORKTIME; 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.done.Tables.V_DAY;
@ -32,12 +33,14 @@ import org.jooq.Row4;
import org.jooq.SelectConditionStep; import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep; import org.jooq.SelectHavingStep;
import org.jooq.SelectSeekStep1; import org.jooq.SelectSeekStep1;
import org.jooq.TableField;
import org.jooq.UpdateConditionStep; import org.jooq.UpdateConditionStep;
import org.jooq.impl.DSL; import org.jooq.impl.DSL;
import org.jooq.types.YearToSecond; import org.jooq.types.YearToSecond;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; 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.TOvertimeRecord;
import de.jottyfan.timetrack.db.done.tables.records.TRequiredWorktimeRecord; import de.jottyfan.timetrack.db.done.tables.records.TRequiredWorktimeRecord;
import de.jottyfan.timetrack.modules.done.model.DaysumBean; import de.jottyfan.timetrack.modules.done.model.DaysumBean;
@ -95,12 +98,12 @@ public class DoneRepository {
Field<Integer> OVERTIME = DSL.field("overtime", Integer.class); Field<Integer> OVERTIME = DSL.field("overtime", Integer.class);
SelectHavingStep<Record1<Integer>> sql = jooq SelectHavingStep<Record1<Integer>> sql = jooq
// @formatter:off // @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) .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_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_OVERTIME).on(T_OVERTIME.FK_LOGIN.eq(V_DAY.FK_LOGIN))
.innerJoin(T_LOGIN).on(T_LOGIN.PK.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(V_DAY.DAY.le(day))
.and(T_LOGIN.LOGIN.eq(login)) .and(T_LOGIN.LOGIN.eq(login))
.groupBy(V_DAY.FK_LOGIN, T_OVERTIME.OVERTIME_MINUTES); .groupBy(V_DAY.FK_LOGIN, T_OVERTIME.OVERTIME_MINUTES);
@ -295,4 +298,22 @@ public class DoneRepository {
LOGGER.trace(sql); LOGGER.trace(sql);
sql.execute(); 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();
}
} }

View File

@ -1,5 +1,7 @@
package de.jottyfan.timetrack.modules.done; package de.jottyfan.timetrack.modules.done;
import static de.jottyfan.timetrack.db.done.Tables.T_DONE;
import java.time.DayOfWeek; import java.time.DayOfWeek;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -327,4 +329,16 @@ public class DoneService {
} }
repository.addSlotRange(minutes, username, reason, days); 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);
}
}
} }

View File

@ -6,14 +6,11 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.db.done.tables.records.TJobRecord; import de.jottyfan.timetrack.db.done.tables.records.TJobRecord;
import de.jottyfan.timetrack.modules.CommonController; 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 de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
@ -27,9 +24,6 @@ public class JobController extends CommonController {
@Autowired @Autowired
private JobService jobService; private JobService jobService;
@Autowired
private DoneController doneController;
@Autowired @Autowired
private OAuth2Provider provider; private OAuth2Provider provider;
@ -38,7 +32,7 @@ public class JobController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/edit/job/{id}") @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(); String username = provider.getName();
TJobRecord job = jobService.get(id); TJobRecord job = jobService.get(id);
model.addAttribute("jobBean", job); model.addAttribute("jobBean", job);
@ -47,21 +41,21 @@ public class JobController extends CommonController {
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/upsert/job", method = RequestMethod.POST) @PostMapping("/done/upsert/job")
public String doUpsert(Model model, @ModelAttribute TJobRecord bean) { public String doUpsert(Model model, @ModelAttribute("bean") TJobRecord bean) {
Integer amount = jobService.doUpsert(bean); Integer amount = jobService.doUpsert(bean);
return amount.equals(1) ? "redirect:/done/list": toJob(bean.getPk(), model); return amount.equals(1) ? "redirect:/done/list": toJob(bean.getPk(), model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/add/job", method = RequestMethod.GET) @GetMapping("/done/add/job")
public String toAddJob(Model model) { public String toAddJob(Model model) {
return toJob(null, model); return toJob(null, model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/delete/job/{id}") @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); Integer amount = jobService.doDelete(id);
return amount.equals(1) ? "redirect:/done/list" : toJob(id, model); return amount.equals(1) ? "redirect:/done/list" : toJob(id, model);
} }

View File

@ -6,14 +6,11 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord; import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord;
import de.jottyfan.timetrack.modules.CommonController; 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 de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
@ -28,9 +25,6 @@ public class ModuleController extends CommonController {
@Autowired @Autowired
private ModuleService moduleService; private ModuleService moduleService;
@Autowired
private DoneController doneController;
@Autowired @Autowired
private OAuth2Provider provider; private OAuth2Provider provider;
@ -39,7 +33,7 @@ public class ModuleController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/edit/module/{id}") @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(); String username = provider.getName();
TModuleRecord module = moduleService.get(id); TModuleRecord module = moduleService.get(id);
model.addAttribute("moduleBean", module); model.addAttribute("moduleBean", module);
@ -48,21 +42,21 @@ public class ModuleController extends CommonController {
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/upsert/module", method = RequestMethod.POST) @PostMapping("/done/upsert/module")
public String doUpsert(Model model, @ModelAttribute TModuleRecord bean) { public String doUpsert(Model model, @ModelAttribute("bean") TModuleRecord bean) {
Integer amount = moduleService.doUpsert(bean); Integer amount = moduleService.doUpsert(bean);
return amount.equals(1) ? "redirect:/done/list" : toModule(bean.getPk(), model); return amount.equals(1) ? "redirect:/done/list" : toModule(bean.getPk(), model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/add/module", method = RequestMethod.GET) @GetMapping("/done/add/module")
public String toAddModule(Model model) { public String toAddModule(Model model) {
return toModule(null, model); return toModule(null, model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/delete/module/{id}") @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); Integer amount = moduleService.doDelete(id);
return amount.equals(1) ? "redirect:/done/list" : toModule(id, model); return amount.equals(1) ? "redirect:/done/list" : toModule(id, model);
} }

View File

@ -6,14 +6,11 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord; import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord;
import de.jottyfan.timetrack.modules.CommonController; 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 de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
@ -27,9 +24,6 @@ public class ProjectController extends CommonController {
@Autowired @Autowired
private ProjectService projectService; private ProjectService projectService;
@Autowired
private DoneController doneController;
@Autowired @Autowired
private OAuth2Provider provider; private OAuth2Provider provider;
@ -38,7 +32,7 @@ public class ProjectController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/done/edit/project/{id}") @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(); String username = provider.getName();
TProjectRecord project = projectService.get(id); TProjectRecord project = projectService.get(id);
model.addAttribute("projectBean", project); model.addAttribute("projectBean", project);
@ -47,21 +41,21 @@ public class ProjectController extends CommonController {
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/upsert/project", method = RequestMethod.POST) @PostMapping("/done/upsert/project")
public String doUpsert(Model model, @ModelAttribute TProjectRecord bean) { public String doUpsert(Model model, @ModelAttribute("bean") TProjectRecord bean) {
Integer amount = projectService.doUpsert(bean); Integer amount = projectService.doUpsert(bean);
return amount.equals(1) ? "redirect:/done/list" : toProject(bean.getPk(), model); return amount.equals(1) ? "redirect:/done/list" : toProject(bean.getPk(), model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/done/add/project", method = RequestMethod.GET) @GetMapping("/done/add/project")
public String toAddProject(Model model) { public String toAddProject(Model model) {
return toProject(null, model); return toProject(null, model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/done/delete/project/{id}") @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); Integer amount = projectService.doDelete(id);
return amount.equals(1) ? "redirect:/done/list" : toProject(id, model); return amount.equals(1) ? "redirect:/done/list" : toProject(id, model);
} }

View File

@ -44,7 +44,7 @@ public class NoteController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/note/edit/{id}") @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); NoteBean bean = noteService.getBean(id);
if (bean == null) { if (bean == null) {
bean = new NoteBean(); // the add case bean = new NoteBean(); // the add case
@ -57,14 +57,14 @@ public class NoteController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@PostMapping("/note/upsert") @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); Integer amount = noteService.doUpsert(bean);
return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model); return amount.equals(1) ? getList(model) : toItem(bean.getPk(), model);
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping(value = "/note/delete/{id}") @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); Integer amount = noteService.doDelete(id);
return amount.equals(1) ? getList(model) : toItem(id, model); return amount.equals(1) ? getList(model) : toItem(id, model);
} }

View File

@ -22,7 +22,7 @@ public class ProfileController extends CommonController {
private ProfileService service; private ProfileService service;
@PostMapping("/profile/{theme}") @PostMapping("/profile/{theme}")
public String setTheme(@PathVariable String theme) { public String setTheme(@PathVariable("theme") String theme) {
String username = provider.getName(); String username = provider.getName();
service.setTheme(username, theme); service.setTheme(username, theme);
return "redirect:/"; return "redirect:/";

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -44,8 +44,8 @@ body {
@media(min-width:1600px) { @media(min-width:1600px) {
.tabdivblurred { .tabdivblurred {
width: 50%;
margin: auto; margin: auto;
width: 1111px;
} }
} }
@ -210,6 +210,15 @@ body {
color: black !important; 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 { .left {
text-align: left; text-align: left;
} }
@ -275,48 +284,62 @@ body {
} }
.emphgreen { .emphgreen {
font-weight: bolder;
color: #136600; color: #136600;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white); background-image: linear-gradient(to left, #e6e6e6, white);
padding: 4px;
} }
.emphblue { .emphblue {
font-weight: bolder;
color: #1a5fb4; color: #1a5fb4;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white); background-image: linear-gradient(to left, #e6e6e6, white);
padding: 4px;
} }
.emphorange { .emphorange {
font-weight: bolder;
color: #c64600; color: #c64600;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white); background-image: linear-gradient(to left, #e6e6e6, white);
padding: 4px;
} }
.emphred { .emphred {
font-weight: bolder;
color: #a51d2d; color: #a51d2d;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white); background-image: linear-gradient(to left, #e6e6e6, white);
padding: 4px;
} }
.emphpink { .emphpink {
font-weight: bolder;
color: #613583; color: #613583;
border: 1px solid gray;
border-radius: 8px;
background-image: linear-gradient(to left, #e6e6e6, white); background-image: linear-gradient(to left, #e6e6e6, white);
}
.emphgray {
color: #5e5c64;
background-image: linear-gradient(to left, #959595, #e6e6e6);
}
.unround-border {
padding: 4px; 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 { .tab-pane-table {

View File

@ -53,11 +53,15 @@ class Schedule {
} }
time2pixel = function (time, hourHeight) { time2pixel = function (time, hourHeight) {
var timeArray = time.split(":"); if (time == null) {
var hours = parseInt(timeArray[0]); return 0;
var minutes = parseInt(timeArray[1]); } else {
var pixels = parseInt((hours + (minutes / 60)) * hourHeight); var timeArray = time.split(":");
return pixels; 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) { drawSlot = function(ctx, slotNr, from, until, color, fillColor) {

View File

@ -1,6 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" <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}">
xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}">
<head> <head>
<title>Arbeitszeit</title> <title>Arbeitszeit</title>
</head> </head>
@ -8,8 +7,10 @@
<font layout:fragment="title">Arbeitszeit</font> <font layout:fragment="title">Arbeitszeit</font>
<ul layout:fragment="menuitem"> <ul layout:fragment="menuitem">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"> <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;">
<form th:action="@{/done/list}" th:object="${doneModel}" method="post" style="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="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"> <div class="input-group input-group-sm mb-3" style="margin-bottom: 0px !important">
<input type="date" class="form-control" th:value="*{day}" th:field="*{day}" /> <input type="date" class="form-control" th:value="*{day}" th:field="*{day}" />
@ -17,46 +18,74 @@
</div> </div>
</div> </div>
</form> </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> </li>
</ul> </ul>
<ul layout:fragment="menu"> <ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"> <li class="nav-item" sec:authorize="hasRole('timetrack_user')">
<table> <table>
<tr> <tr>
<td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer <td><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/{day}(day=${doneModel.day})}">Neuer Eintrag</a></td>
Eintrag</a></td>
<td> <td>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-white-text dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-white-text dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Letzte Einträge</button>
Letzte Einträge
</button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li th:each="recent : ${recentList}"> <li th:each="recent : ${recentList}">
<a class="dropdown-item" th:href="@{/done/addrecent/{id}(id=${recent.pk})}" <a class="dropdown-item" th:href="@{/done/addrecent/{id}(id=${recent.pk})}"
th:text="${(recent.getJobName()!=null?recent.getJobName():'') + '@' + (recent.getProject()!=null?recent.getProject().getName():'') + (recent.getModule()!=null? ', ' + recent.getModule().getName() : '')}"></a> th:text="${(recent.getJobName()!=null?recent.getJobName():'') + '@' + (recent.getProject()!=null?recent.getProject().getName():'') + (recent.getModule()!=null? ', ' + recent.getModule().getName() : '')}"></a>
</li> </li>
</ul> </ul>
</div> </div>
</td> </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> </tr>
</table> </table>
</li> </li>
</ul> </ul>
<main layout:fragment="content"> <main layout:fragment="content">
<ul class="nav nav-tabs navback" role="tablist"> <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">
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_schedule">Kalender</a></li> <a class="nav-link navlinkstyle active" data-bs-toggle="tab" href="#div_list">Liste</a>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_project">Projekt</a></li> </li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_module">Modul</a></li> <li class="nav-item">
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_job">Aufgabe</a></li> <a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_schedule">Kalender</a>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_billing">Abrechnung</a></li> </li>
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_overtime">Überstunden</a></li> <li class="nav-item">
<li class="nav-item"><a class="nav-link navlinkstyle" data-bs-toggle="tab" href="#div_slot">Slots</a></li> <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> </ul>
<div class="tabdivblurred tab-content"> <div class="tabdivblurred tab-content">
<div id="div_list" class="tab-pane active tab-pane-table"> <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"> <table class="table table-striped table-condensed">
<thead> <thead>
<tr> <tr>
@ -71,7 +100,9 @@
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Favoriten</button> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Favoriten</button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li th:each="f : ${favorites}"> <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> </li>
</ul> </ul>
</div> </div>
@ -80,45 +111,53 @@
</thead> </thead>
<tbody> <tbody>
<tr th:each="done : ${doneList}"> <tr th:each="done : ${doneList}">
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span th:text="${done.timeNote}"></span></a> <td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}">
<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> <span th:text="${done.timeNote}"></span>
</td> </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">
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span th:text="${done.timeDiff}"></span></a></td> <i class="fa fa-clock"></i>
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"><span class="boldtext" </a></td>
th:text="${done.project?.name}"></span></a></td> <td><span th:text="${done.timeDiff}"></span></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> <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">
<a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"><i class="fa fa-edit"></i></a> <option value="">---</option>
<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> <option th:each="p : ${projectList}" th:value="${p.pk}" th:text="${p.name}" th:selected="${done.project?.name == p.name ? 'selected' : 'false'}"></option>
<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> </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>
<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>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr th:if="${daysum}"> <tr th:if="${daysum}">
<td>Start: <span class="emphgreen" th:text="${#temporals.format(daysum.daytimeFrom, 'HH:mm')}" th:if="${daysum.daytimeFrom}"></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>
<td>Ende: <span class="emphgreen" th:text="${#temporals.format(daysum.daytimeUntil, 'HH:mm')}" th:if="${daysum.daytimeUntil}"></span></td> <span class="sumfield">Ende: <span class="emphgreen round-border-right" th:text="${#temporals.format(daysum.daytimeUntil, 'HH:mm')}" th:if="${daysum.daytimeUntil}"></span></span>
<td>Arbeitszeit total: <span class="emphblue" th:text="${#temporals.format(daysum.dayworktime, 'HH:mm')}" th:if="${daysum.dayworktime}"></span></td> <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>
<td>Pausezeit total: <span class="emphorange" th:text="${#temporals.format(daysum.breaks, 'HH:mm')}" th:if="${daysum.breaks}"></span></td> <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>
<td>Überstunden heute: <span class="emphred" th:text="${daysum.printDayOvertime()}"></span></td> <span class="sumfield">Überstunden heute: <span class="emphred round-border-right" th:text="${daysum.printDayOvertime()}"></span></span>
<td colspan="2">Überstunden total: <span class="emphpink" th:text="${daysum.printTotalOvertime()}"></span></td> <span class="sumfield">Überstunden total: <span class="emphpink round-border-right" th:text="${daysum.printTotalOvertime()}"></span></span>
</td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td><span th:if="${sum.getBillingTime('WP2') != '0,0 h'}"><span class="billing WP2">WP2</span><span <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>
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('WP4') != '0,0 h'}"><span class="billing WP4">WP4</span><span <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>
th:text="${sum.getBillingTime('WP4')}" 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('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> <td colspan="2"><span class="billing">X</span><span th:text="${sum.getBillingTime(null)}" class="distfat"></span></td>
</tr> </tr>
</tfoot> </tfoot>
@ -140,13 +179,14 @@
<tr th:each="project : ${projectList}"> <tr th:each="project : ${projectList}">
<td><span th:text="${project.name}"></span></td> <td><span th:text="${project.name}"></span></td>
<td><span th:text="${project.percentUsage}"></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> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
<td colspan="3"><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/project}">neues Projekt</a> <td colspan="3"><a class="nav-link btn btn-success btn-white-text" th:href="@{/done/add/project}">neues Projekt</a></td>
</td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
@ -164,7 +204,9 @@
<tr th:each="module : ${moduleList}"> <tr th:each="module : ${moduleList}">
<td><span th:text="${module.name}"></span></td> <td><span th:text="${module.name}"></span></td>
<td><span th:text="${module.percentUsage}"></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> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
@ -187,7 +229,9 @@
<tr th:each="job : ${jobList}"> <tr th:each="job : ${jobList}">
<td><span th:text="${job.name}"></span></td> <td><span th:text="${job.name}"></span></td>
<td><span th:text="${job.percentUsage}"></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> </tr>
</tbody> </tbody>
<tfoot> <tfoot>
@ -242,9 +286,8 @@
</div> </div>
<div id="div_slot" class="tab-pane fade tab-pane-table"> <div id="div_slot" class="tab-pane fade tab-pane-table">
<div class="alert alert-info"> <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. 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
Die Überstundenberechnung hängt von der Vollständigkeit der vorhandenen Slots ab; fehlen Slots, wird die Arbeitszeit jener Tage nicht eingerechnet.<br /> 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.
Hier werden nur die Slots für diesen Monat angezeigt.
</div> </div>
<div class="container"> <div class="container">
<div class="row row-weekday"> <div class="row row-weekday">
@ -259,21 +302,22 @@
<div class="row row-weekday"> <div class="row row-weekday">
<div class="col slot_badge" th:each="o : ${slotOffset}"></div> <div class="col slot_badge" th:each="o : ${slotOffset}"></div>
<div class="col slot_badge" th:each="s : ${slots}"> <div class="col slot_badge" th:each="s : ${slots}">
<span class="slot_badge_left" th:text="${#temporals.format(s.day, 'dd.MM.')}"> <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><a th:href="@{/done/slot/{id}(id=${s.id})}" class="slot_badge_middle" th:if="${s.id}">
<i class="fas fa-pencil"></i> <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}"> </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> <i class="fas fa-plus"></i>
</a> </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 <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'}"
class="slot_badge_right" th:unless="${s.id}">&nbsp;--:--&nbsp;</span> th:text="${s.printTime()}" th:if="${s.id}"></span><span class="slot_badge_right" th:unless="${s.id}">&nbsp;--:--&nbsp;</span>
</div> </div>
</div> </div>
<br /> <br />
<div class="row"> <div class="row">
<div class="col"> <div class="col-2"><a th:href="@{/done/slot/back}" class="btn btn-outline-primary">&lt;- 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> <a th:href="@{/done/slot/range}" class="btn btn-outline-primary">mehrere Slots auf einmal anlegen</a>
</div> </div>
<div class="col-2"><a th:href="@{/done/slot/forward}" class="btn btn-outline-primary">weiter -&gt;</a></div>
</div> </div>
<br /> <br />
<div class="row alert alert-info"> <div class="row alert alert-info">
@ -281,69 +325,83 @@
<span style="text-decoration: underline">Legende</span> <span style="text-decoration: underline">Legende</span>
</div> </div>
<div class="col"> <div class="col">
Üb: Überstunden, Mehrarbeit<br /> Üb: Überstunden, Mehrarbeit<br /> Ur: Urlaub, Sonderurlaub, Kur<br /> gF: gesetzlicher Feiertag<br />
Ur: Urlaub, Sonderurlaub, Kur<br />
gF: gesetzlicher Feiertag<br />
</div> </div>
<div class="col"> <div class="col">
Kr: Arbeits- und Dienstunfähigkeit<br /> Kr: Arbeits- und Dienstunfähigkeit<br /> Gl: Freistellung aus Gleitzeitguthaben<br /> Ar: Arbeits- und Dienstbefreiung<br />
Gl: Freistellung aus Gleitzeitguthaben<br />
Ar: Arbeits- und Dienstbefreiung<br />
</div> </div>
<div class="col"> <div class="col">
mK: "mit Kind krank"<br /> mK: "mit Kind krank"<br /> Di: Dienstreise, Dienstgänge<br />
Di: Dienstreise, Dienstgänge<br />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document)
let width = parseInt($("#schedule").css("min-width")); .ready(
let height = parseInt($("#schedule").css("min-height")); function() {
var schedule = new Schedule("#schedule", width, height); // the tab deeplink functionality
var ctx = $("#scheduleCanvas")[0].getContext("2d"); let url = location.href.replace(/\/$/, "");
var currentDayRecords = JSON.parse('[(${schedule})]'); if (location.hash) {
var scheduleRecords = currentDayRecords.schedule; const hash = url.split("#");
for (var i = 0; i < scheduleRecords.length; i++) { $('#worktimetabs a[href="#' + hash[1] + '"]').tab("show");
var r = scheduleRecords[i]; url = location.href.replace(/\/#/, "#");
var cssClass = r.billing; history.replaceState(null, null, url);
var color = "#ffffff"; }
if (cssClass == "WP5") {
color = "#aa0000"; // the schedule
} else if (cssClass == "WP4") { let width = parseInt($("#schedule").css(
color = "#0000aa"; "min-width"));
} else if (cssClass == "WP2") { let height = parseInt($("#schedule").css(
color = "#aaaa00"; "min-height"));
} else if (cssClass == "TA3") { var schedule = new Schedule("#schedule", width,
color = "#00aa00"; height);
} var ctx = $("#scheduleCanvas")[0]
/* daySlot 7 = sunday, but this should be slot 0 */ .getContext("2d");
schedule.drawSlot(ctx, r.daySlot > 6 ? 0 : r.daySlot, r.from, r.until, "black", color); var currentDayRecords = JSON
} .parse('[(${schedule})]');
var localeUrl = '[[@{/js/dataTables/de.json}]]'; var scheduleRecords = currentDayRecords.schedule;
$("#project_table").DataTable({ for (var i = 0; i < scheduleRecords.length; i++) {
"language" : { var r = scheduleRecords[i];
"url" : localeUrl var cssClass = r.billing;
} var color = "#ffffff";
}); if (cssClass == "WP5") {
$("#module_table").DataTable({ color = "#aa0000";
"language" : { } else if (cssClass == "WP4") {
"url" : localeUrl color = "#0000aa";
} } else if (cssClass == "WP2") {
}); color = "#aaaa00";
$("#job_table").DataTable({ } else if (cssClass == "TA3") {
"language" : { color = "#00aa00";
"url" : localeUrl }
} /* daySlot 7 = sunday, but this should be slot 0 */
}); schedule.drawSlot(ctx, r.daySlot > 6 ? 0
$("#billing_table").DataTable({ : r.daySlot, r.from, r.until,
"language" : { "black", color);
"url" : localeUrl }
} var localeUrl = '[[@{/js/dataTables/de.json}]]';
}); $("#project_table").DataTable({
}); "language" : {
"url" : localeUrl
}
});
$("#module_table").DataTable({
"language" : {
"url" : localeUrl
}
});
$("#job_table").DataTable({
"language" : {
"url" : localeUrl
}
});
$("#billing_table").DataTable({
"language" : {
"url" : localeUrl
}
});
});
</script> </script>
</main> </main>
</body> </body>

View File

@ -6,7 +6,7 @@
<body> <body>
<ul layout:fragment="menu"> <ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"> <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> </li>
</ul> </ul>
<main layout:fragment="content"> <main layout:fragment="content">

View File

@ -6,7 +6,7 @@
<body> <body>
<ul layout:fragment="menu"> <ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')"> <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> </li>
</ul> </ul>
<main layout:fragment="content"> <main layout:fragment="content">

View File

@ -5,19 +5,19 @@
<title>Timetrack</title> <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/bootstrap/5.3.3/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/datatables/2.1.8/css/dataTables.dataTables.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/font-awesome/6.7.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/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="@{/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="32x32" th:href="@{/png/favicon/favicon-32x32.png}" />
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.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/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/bootstrap/5.3.3/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/2.1.8/js/dataTables.dataTables.min.js}"></script>
<script th:src="@{/webjars/datatables/1.13.5/js/dataTables.bootstrap5.min.js}"></script> <script th:src="@{/webjars/fullcalendar/6.1.9/main.js}"></script>
<script th:src="@{/webjars/fullcalendar/5.11.3/main.js}"></script>
<script th:src="@{/js/helper.js}"></script> <script th:src="@{/js/helper.js}"></script>
<script th:src="@{/js/clock.js}"></script> <script th:src="@{/js/clock.js}"></script>
<script th:src="@{/js/schedule.js}"></script> <script th:src="@{/js/schedule.js}"></script>

View File

@ -17,15 +17,15 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-8"><span class="spanlabel">Start:</span></div> <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-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-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-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-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> </div>
</div> </div>