Compare commits

...

11 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
25 changed files with 330 additions and 164 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.6' 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));
@ -77,15 +83,22 @@ public class DoneController extends CommonController {
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);
@ -102,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
@ -112,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);
@ -121,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);
@ -130,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);
@ -138,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);
@ -163,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";
} }
@ -216,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")
@ -238,7 +251,7 @@ 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") @RolesAllowed("timetrack_user")
@ -246,7 +259,7 @@ public class DoneController extends CommonController {
public String oneMonthBack(Model model, @ModelAttribute("doneModel") DoneModel doneModel) { public String oneMonthBack(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
LocalDate day = doneModel.getDay(); LocalDate day = doneModel.getDay();
doneModel.setDay(day.minusMonths(1)); doneModel.setDay(day.minusMonths(1));
return "redirect:/done/list"; return "redirect:/done/list#div_slot";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@ -254,6 +267,6 @@ public class DoneController extends CommonController {
public String oneMonthForward(Model model, @ModelAttribute("doneModel") DoneModel doneModel) { public String oneMonthForward(Model model, @ModelAttribute("doneModel") DoneModel doneModel) {
LocalDate day = doneModel.getDay(); LocalDate day = doneModel.getDay();
doneModel.setDay(day.plusMonths(1)); doneModel.setDay(day.plusMonths(1));
return "redirect:/done/list"; 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;
@ -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

@ -47,7 +47,7 @@
</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"> <li class="nav-item">
<a class="nav-link navlinkstyle active" data-bs-toggle="tab" href="#div_list">Liste</a> <a class="nav-link navlinkstyle active" data-bs-toggle="tab" href="#div_list">Liste</a>
</li> </li>
@ -75,6 +75,17 @@
</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>
@ -105,38 +116,41 @@
</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"> </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> <i class="fa fa-clock"></i>
</a></td> </a></td>
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"> <td><span th:text="${done.timeDiff}"></span></td>
<span th:text="${done.timeDiff}"></span> <td>
</a></td> <select onchange="submitDropdown(this)" th:data-id="${done.pk}" data-field="project">
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"> <option value="">---</option>
<span class="boldtext" th:text="${done.project?.name}"></span> <option th:each="p : ${projectList}" th:value="${p.pk}" th:text="${p.name}" th:selected="${done.project?.name == p.name ? 'selected' : 'false'}"></option>
</a></td> </select>
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"> </td>
<span class="boldtext" th:text="${done.module?.name}"></span> <td>
</a></td> <select onchange="submitDropdown(this)" th:data-id="${done.pk}" data-field="module">
<td><a class="hoverlink" th:href="@{/done/edit/{id}(id=${done.pk})}"> <option value="">---</option>
<span class="boldtext" th:text="${done.activity?.name}"></span> <option th:each="m : ${moduleList}" th:value="${m.pk}" th:text="${m.name}" th:selected="${done.module?.name == m.name ? 'selected' : 'false'}"></option>
</a></td> </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><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"> <td><a class="btn-list" th:href="@{/done/copy/{id}(id=${done.pk})}" title="Aufgabe neu beginnen"><i class="fa fa-copy"></i></a>
<i class="fa fa-copy"></i> <a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"><i class="fa fa-edit"></i></a>
</a> <a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"> <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>
<i class="fa fa-edit"></i> <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> <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>
@ -288,11 +302,9 @@
<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> <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}">
<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><a th:href="@{/done/slot/add?day={d}(d=${s.day})}" class="slot_badge_middle" th:unless="${s.id}">
<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'}" <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'}"
@ -329,6 +341,16 @@
$(document) $(document)
.ready( .ready(
function() { 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( let width = parseInt($("#schedule").css(
"min-width")); "min-width"));
let height = parseInt($("#schedule").css( let height = parseInt($("#schedule").css(

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>
@ -56,7 +56,7 @@
$(document).ready(function(){ $(document).ready(function(){
var theme = "[[${theme}]]"; var theme = "[[${theme}]]";
$("html").attr("data-bs-theme", theme); $("html").attr("data-bs-theme", theme);
resetClock = function() { resetClock = function() {
$("#clock").empty(); $("#clock").empty();
var theme = $("html").attr("data-bs-theme"); var theme = $("html").attr("data-bs-theme");

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>