Compare commits

...

3 Commits

Author SHA1 Message Date
689a601c8c optimized reason for slot difference 2024-01-05 15:12:32 +01:00
5117fd0e71 preparation for slot dimediff reason 2024-01-05 14:58:40 +01:00
c1b8283dd0 code cleanup 2024-01-05 11:09:53 +01:00
14 changed files with 243 additions and 85 deletions

View File

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

View File

@ -1,7 +1,11 @@
package de.jottyfan.timetrack.modules; package de.jottyfan.timetrack.modules;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.modules.profile.ProfileService;
/** /**
* *
* @author jotty * @author jotty
@ -9,6 +13,12 @@ import org.springframework.web.bind.annotation.ModelAttribute;
*/ */
public abstract class CommonController { public abstract class CommonController {
@Autowired
private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@Value("${server.servlet.context-path}") @Value("${server.servlet.context-path}")
private String contextPath; private String contextPath;
@ -16,4 +26,10 @@ public abstract class CommonController {
public String getBaseUrl() { public String getBaseUrl() {
return contextPath; return contextPath;
} }
@ModelAttribute("theme")
public String getTheme() {
String username = provider.getName();
return profileService.getTheme(username);
}
} }

View File

@ -12,14 +12,12 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; 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.RequestMapping;
import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.modules.done.DoneService; import de.jottyfan.timetrack.modules.done.DoneService;
import de.jottyfan.timetrack.modules.done.model.DoneBean; import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.DoneModel; import de.jottyfan.timetrack.modules.done.model.DoneModel;
import de.jottyfan.timetrack.modules.done.model.SummaryBean; import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -39,9 +37,6 @@ public class IndexController extends CommonController {
@Autowired @Autowired
private OAuth2Provider provider; private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@GetMapping("/logout") @GetMapping("/logout")
public String getLogout(HttpServletRequest request) throws ServletException { public String getLogout(HttpServletRequest request) throws ServletException {
request.logout(); request.logout();
@ -49,14 +44,13 @@ public class IndexController extends CommonController {
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping("/") @GetMapping("/")
public String getIndex(@ModelAttribute DoneModel doneModel, Model model, OAuth2AuthenticationToken token) { public String getIndex(@ModelAttribute 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();
List<DoneBean> list = doneService.getList(day, username); List<DoneBean> list = doneService.getList(day, username);
model.addAttribute("sum", new SummaryBean(list, day, maxWorkTime)); model.addAttribute("sum", new SummaryBean(list, day, maxWorkTime));
model.addAttribute("theme", profileService.getTheme(username));
LOGGER.debug("sum = {}", model.getAttribute("sum")); LOGGER.debug("sum = {}", model.getAttribute("sum"));
return "public/index"; return "public/index";
} }

View File

@ -5,9 +5,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.CommonController;
import de.jottyfan.timetrack.modules.profile.ProfileService;
/** /**
* *
@ -17,19 +15,12 @@ import de.jottyfan.timetrack.modules.profile.ProfileService;
@Controller @Controller
public class CalendarController extends CommonController { public class CalendarController extends CommonController {
@Autowired
private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@Autowired @Autowired
private CalendarService service; private CalendarService service;
@GetMapping("/calendar") @GetMapping("/calendar")
public String getCalendar(Model model) { public String getCalendar(Model model) {
model.addAttribute("events", service.getJsonEvents()); model.addAttribute("events", service.getJsonEvents());
model.addAttribute("theme", profileService.getTheme(provider.getName()));
return "/calendar/calendar"; return "/calendar/calendar";
} }
} }

View File

@ -10,14 +10,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 org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import de.jottyfan.timetrack.component.OAuth2Provider;
import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; import de.jottyfan.timetrack.db.contact.enums.EnumContacttype;
import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.CommonController;
import de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
/** /**
@ -28,12 +25,6 @@ import jakarta.annotation.security.RolesAllowed;
@Controller @Controller
public class ContactController extends CommonController { public class ContactController extends CommonController {
@Autowired
private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@Autowired @Autowired
private ContactService contactService; private ContactService contactService;
@ -44,16 +35,15 @@ public class ContactController extends CommonController {
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/list") @GetMapping("/contact/list")
public String getList(Model model) { public String getList(Model model) {
List<ContactBean> list = contactService.getList(); List<ContactBean> list = contactService.getList();
model.addAttribute("contactList", list); model.addAttribute("contactList", list);
model.addAttribute("theme", profileService.getTheme(provider.getName()));
return "contact/list"; return "contact/list";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/add", method = RequestMethod.GET) @GetMapping("/contact/add")
public String toAdd(Model model) { public String toAdd(Model model) {
return toItem(null, model); return toItem(null, model);
} }
@ -67,12 +57,11 @@ public class ContactController extends CommonController {
} }
model.addAttribute("contactBean", bean); model.addAttribute("contactBean", bean);
model.addAttribute("types", Arrays.asList(EnumContacttype.values())); model.addAttribute("types", Arrays.asList(EnumContacttype.values()));
model.addAttribute("theme", profileService.getTheme(provider.getName()));
return "contact/item"; return "contact/item";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/contact/upsert", method = RequestMethod.POST) @PostMapping("/contact/upsert")
public String doUpsert(Model model, @ModelAttribute ContactBean bean) { public String doUpsert(Model model, @ModelAttribute 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);

View File

@ -12,6 +12,7 @@ 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.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.SessionAttributes;
import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.component.OAuth2Provider;
@ -19,8 +20,8 @@ import de.jottyfan.timetrack.modules.CommonController;
import de.jottyfan.timetrack.modules.done.model.DoneBean; import de.jottyfan.timetrack.modules.done.model.DoneBean;
import de.jottyfan.timetrack.modules.done.model.DoneModel; import de.jottyfan.timetrack.modules.done.model.DoneModel;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean; import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SlotBean;
import de.jottyfan.timetrack.modules.done.model.SummaryBean; import de.jottyfan.timetrack.modules.done.model.SummaryBean;
import de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
/** /**
@ -35,9 +36,6 @@ public class DoneController extends CommonController {
@Autowired @Autowired
private OAuth2Provider provider; private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@Autowired @Autowired
private DoneService doneService; private DoneService doneService;
@ -68,7 +66,6 @@ public class DoneController extends CommonController {
model.addAttribute("moduleList", doneService.getModules(false)); model.addAttribute("moduleList", doneService.getModules(false));
model.addAttribute("jobList", doneService.getJobs(false)); model.addAttribute("jobList", doneService.getJobs(false));
model.addAttribute("billingList", doneService.getBillings(false)); model.addAttribute("billingList", doneService.getBillings(false));
model.addAttribute("theme", profileService.getTheme(username));
model.addAttribute("favorites", doneService.getFavorites(username)); model.addAttribute("favorites", doneService.getFavorites(username));
return "done/list"; return "done/list";
} }
@ -96,13 +93,11 @@ public class DoneController extends CommonController {
} }
private String toItem(DoneBean bean, Model model) { private String toItem(DoneBean bean, Model model) {
String username = provider.getName();
model.addAttribute("doneBean", bean); model.addAttribute("doneBean", bean);
model.addAttribute("projectList", doneService.getProjects(true)); model.addAttribute("projectList", doneService.getProjects(true));
model.addAttribute("moduleList", doneService.getModules(true)); model.addAttribute("moduleList", doneService.getModules(true));
model.addAttribute("jobList", doneService.getJobs(true)); model.addAttribute("jobList", doneService.getJobs(true));
model.addAttribute("billingList", doneService.getBillings(true)); model.addAttribute("billingList", doneService.getBillings(true));
model.addAttribute("theme", profileService.getTheme(username));
return "done/item"; return "done/item";
} }
@ -210,4 +205,25 @@ public class DoneController extends CommonController {
model.addAttribute("bean", doneService.getSlot(id, username)); model.addAttribute("bean", doneService.getSlot(id, username));
return "/done/slot/item"; return "/done/slot/item";
} }
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/add")
public String addSlot(@RequestParam("day") LocalDate day, Model model) {
model.addAttribute("bean", SlotBean.of(day));
return "/done/slot/item";
}
@RolesAllowed("timetrack_user")
@PostMapping("/done/slot/upsert")
public String upsertSlot(@ModelAttribute("bean") SlotBean bean, Model model) {
doneService.upsert(bean, provider.getName());
return "redirect:/done/list";
}
@RolesAllowed("timetrack_user")
@GetMapping("/done/slot/{id}/delete")
public String deleteSlot(@PathVariable("id") Integer slotId) {
doneService.delete(slotId, provider.getName());
return "redirect:/done/list";
}
} }

View File

@ -17,10 +17,13 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext; import org.jooq.DSLContext;
import org.jooq.DatePart; import org.jooq.DatePart;
import org.jooq.DeleteConditionStep;
import org.jooq.Field; import org.jooq.Field;
import org.jooq.InsertOnDuplicateSetMoreStep;
import org.jooq.InsertOnDuplicateStep; import org.jooq.InsertOnDuplicateStep;
import org.jooq.Record1; import org.jooq.Record1;
import org.jooq.Record3; import org.jooq.Record3;
import org.jooq.Record4;
import org.jooq.Record5; import org.jooq.Record5;
import org.jooq.SelectConditionStep; import org.jooq.SelectConditionStep;
import org.jooq.SelectHavingStep; import org.jooq.SelectHavingStep;
@ -32,6 +35,7 @@ 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.TOvertimeRecord; import de.jottyfan.timetrack.db.done.tables.records.TOvertimeRecord;
import de.jottyfan.timetrack.db.done.tables.records.TRequiredWorktimeRecord;
import de.jottyfan.timetrack.modules.done.model.DaysumBean; import de.jottyfan.timetrack.modules.done.model.DaysumBean;
import de.jottyfan.timetrack.modules.done.model.OvertimeBean; import de.jottyfan.timetrack.modules.done.model.OvertimeBean;
import de.jottyfan.timetrack.modules.done.model.SlotBean; import de.jottyfan.timetrack.modules.done.model.SlotBean;
@ -157,10 +161,11 @@ public class DoneRepository {
} }
public Map<LocalDate, SlotBean> getSlots(LocalDate from, LocalDate until, String login) { public Map<LocalDate, SlotBean> getSlots(LocalDate from, LocalDate until, String login) {
SelectSeekStep1<Record3<Integer, LocalDate, Integer>, LocalDate> sql = jooq SelectSeekStep1<Record4<Integer, LocalDate, String, Integer>, LocalDate> sql = jooq
// @formatter:off // @formatter:off
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME, .select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY, T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REASON,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES) T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
.from(T_REQUIRED_WORKTIME) .from(T_REQUIRED_WORKTIME)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN)) .innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
@ -170,14 +175,15 @@ public class DoneRepository {
.orderBy(T_REQUIRED_WORKTIME.DAY); .orderBy(T_REQUIRED_WORKTIME.DAY);
// @formatter:on // @formatter:on
LOGGER.trace(sql); LOGGER.trace(sql);
Iterator<Record3<Integer, LocalDate, Integer>> i = sql.fetch().iterator(); Iterator<Record4<Integer, LocalDate, String, Integer>> i = sql.fetch().iterator();
Map<LocalDate, SlotBean> map = new HashMap<>(); Map<LocalDate, SlotBean> map = new HashMap<>();
while (i.hasNext()) { while (i.hasNext()) {
Record3<Integer, LocalDate, Integer> n = i.next(); Record4<Integer, LocalDate, String, Integer> n = i.next();
LocalDate day = n.get(T_REQUIRED_WORKTIME.DAY); LocalDate day = n.get(T_REQUIRED_WORKTIME.DAY);
Integer pk = n.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME); Integer pk = n.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME);
Integer minutes = n.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES); Integer minutes = n.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES);
map.put(day, SlotBean.of(pk, day, minutes)); String reason = n.get(T_REQUIRED_WORKTIME.REASON);
map.put(day, SlotBean.of(pk, day, minutes, reason));
} }
return map; return map;
} }
@ -190,10 +196,11 @@ public class DoneRepository {
* @return the slot or null * @return the slot or null
*/ */
public SlotBean getSlot(Integer id, String login) { public SlotBean getSlot(Integer id, String login) {
SelectConditionStep<Record3<Integer, LocalDate, Integer>> sql = jooq SelectConditionStep<Record4<Integer, LocalDate, String, Integer>> sql = jooq
// @formatter:off // @formatter:off
.select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME, .select(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY, T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REASON,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES) T_REQUIRED_WORKTIME.REQUIRED_MINUTES)
.from(T_REQUIRED_WORKTIME) .from(T_REQUIRED_WORKTIME)
.innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN)) .innerJoin(T_LOGIN).on(T_LOGIN.PK.eq(T_REQUIRED_WORKTIME.FK_LOGIN))
@ -201,9 +208,60 @@ public class DoneRepository {
.and(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(id)); .and(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(id));
// @formatter:on // @formatter:on
LOGGER.trace(sql); LOGGER.trace(sql);
Record3<Integer, LocalDate, Integer> r = sql.fetchOne(); Record4<Integer, LocalDate, String, Integer> r = sql.fetchOne();
return r == null ? null return r == null ? null
: SlotBean.of(r.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), r.get(T_REQUIRED_WORKTIME.DAY), : SlotBean.of(r.get(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME), r.get(T_REQUIRED_WORKTIME.DAY),
r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES)); r.get(T_REQUIRED_WORKTIME.REQUIRED_MINUTES), r.get(T_REQUIRED_WORKTIME.REASON));
}
public void addSlot(SlotBean bean, String login) {
InsertOnDuplicateSetMoreStep<TRequiredWorktimeRecord> sql = jooq
// @formatter:off
.insertInto(T_REQUIRED_WORKTIME,
T_REQUIRED_WORKTIME.DAY,
T_REQUIRED_WORKTIME.REQUIRED_MINUTES,
T_REQUIRED_WORKTIME.REASON,
T_REQUIRED_WORKTIME.FK_LOGIN)
.select(jooq
.select(DSL.val(bean.getDay()), DSL.val(bean.getMinutes()), DSL.val(bean.getReason()), T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login)))
.onConflict(T_REQUIRED_WORKTIME.FK_LOGIN, T_REQUIRED_WORKTIME.DAY)
.doUpdate()
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason());
// @formatter:off
LOGGER.trace(sql);
sql.execute();
}
public void updateSlot(SlotBean bean, String login) {
UpdateConditionStep<TRequiredWorktimeRecord> sql = jooq
// @formatter:off
.update(T_REQUIRED_WORKTIME)
.set(T_REQUIRED_WORKTIME.REQUIRED_MINUTES, bean.getMinutes())
.set(T_REQUIRED_WORKTIME.REASON, bean.getReason())
.where(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(bean.getId()))
.and(T_REQUIRED_WORKTIME.FK_LOGIN.in(jooq
.select(T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login))));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
}
public void deleteSlot(Integer id, String login) {
DeleteConditionStep<TRequiredWorktimeRecord> sql = jooq
// @formatter:off
.deleteFrom(T_REQUIRED_WORKTIME)
.where(T_REQUIRED_WORKTIME.PK_REQUIRED_WORKTIME.eq(id))
.and(T_REQUIRED_WORKTIME.FK_LOGIN.in(jooq
.select(T_LOGIN.PK)
.from(T_LOGIN)
.where(T_LOGIN.LOGIN.eq(login))));
// @formatter:on
LOGGER.trace(sql);
sql.execute();
} }
} }

View File

@ -287,4 +287,22 @@ public class DoneService {
} }
return list; return list;
} }
/**
* upsert the bean
*
* @param bean the bean
* @param username the username
*/
public void upsert(SlotBean bean, String username) {
if (bean.getId() == null) {
repository.addSlot(bean, username);
} else {
repository.updateSlot(bean, username);
}
}
public void delete(Integer slotId, String username) {
repository.deleteSlot(slotId, username);
}
} }

View File

@ -3,6 +3,8 @@ package de.jottyfan.timetrack.modules.done.model;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDate; import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
/** /**
* *
* @author jotty * @author jotty
@ -12,14 +14,17 @@ public class SlotBean implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Integer id; private Integer id;
@DateTimeFormat(pattern="yyyy-MM-dd")
private LocalDate day; private LocalDate day;
private Integer minutes; private Integer minutes;
private String reason;
public static final SlotBean of(Integer id, LocalDate day, Integer minutes) { public static final SlotBean of(Integer id, LocalDate day, Integer minutes, String reason) {
SlotBean bean = new SlotBean(); SlotBean bean = new SlotBean();
bean.setId(id); bean.setId(id);
bean.setDay(day); bean.setDay(day);
bean.setMinutes(minutes); bean.setMinutes(minutes);
bean.setReason(reason);
return bean; return bean;
} }
@ -80,4 +85,18 @@ public class SlotBean implements Serializable {
public void setId(Integer id) { public void setId(Integer id) {
this.id = id; this.id = id;
} }
/**
* @return the reason
*/
public String getReason() {
return reason;
}
/**
* @param reason the reason to set
*/
public void setReason(String reason) {
this.reason = reason;
}
} }

View File

@ -10,14 +10,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.db.note.enums.EnumCategory; import de.jottyfan.timetrack.db.note.enums.EnumCategory;
import de.jottyfan.timetrack.db.note.enums.EnumNotetype; import de.jottyfan.timetrack.db.note.enums.EnumNotetype;
import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.CommonController;
import de.jottyfan.timetrack.modules.profile.ProfileService;
import jakarta.annotation.security.RolesAllowed; import jakarta.annotation.security.RolesAllowed;
/** /**
@ -28,27 +25,19 @@ import jakarta.annotation.security.RolesAllowed;
@Controller @Controller
public class NoteController extends CommonController { public class NoteController extends CommonController {
@Autowired
private OAuth2Provider provider;
@Autowired
private ProfileService profileService;
@Autowired @Autowired
private NoteService noteService; private NoteService noteService;
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/note/list") @GetMapping("/note/list")
public String getList(Model model) { public String getList(Model model) {
String username = provider.getName();
List<NoteBean> list = noteService.getList(); List<NoteBean> list = noteService.getList();
model.addAttribute("noteList", list); model.addAttribute("noteList", list);
model.addAttribute("theme", profileService.getTheme(username));
return "note/list"; return "note/list";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/note/add", method = RequestMethod.GET) @GetMapping("/note/add")
public String toAdd(Model model, OAuth2AuthenticationToken token) { public String toAdd(Model model, OAuth2AuthenticationToken token) {
return toItem(null, model); return toItem(null, model);
} }
@ -56,7 +45,6 @@ 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 Integer id, Model model) {
String username = provider.getName();
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
@ -64,12 +52,11 @@ public class NoteController extends CommonController {
model.addAttribute("noteBean", bean); model.addAttribute("noteBean", bean);
model.addAttribute("types", Arrays.asList(EnumNotetype.values())); model.addAttribute("types", Arrays.asList(EnumNotetype.values()));
model.addAttribute("categories", Arrays.asList(EnumCategory.values())); model.addAttribute("categories", Arrays.asList(EnumCategory.values()));
model.addAttribute("theme", profileService.getTheme(username));
return "note/item"; return "note/item";
} }
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@RequestMapping(value = "/note/upsert", method = RequestMethod.POST) @PostMapping("/note/upsert")
public String doUpsert(Model model, @ModelAttribute NoteBean bean, OAuth2AuthenticationToken token) { public String doUpsert(Model model, @ModelAttribute 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);

View File

@ -3,9 +3,7 @@ package de.jottyfan.timetrack.modules.profile;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
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.modules.CommonController; import de.jottyfan.timetrack.modules.CommonController;
@ -23,7 +21,7 @@ public class ProfileController extends CommonController {
@Autowired @Autowired
private ProfileService service; private ProfileService service;
@RequestMapping(value = "/profile/{theme}", method = RequestMethod.POST) @PostMapping("/profile/{theme}")
public String setTheme(@PathVariable String theme) { public String setTheme(@PathVariable String theme) {
String username = provider.getName(); String username = provider.getName();
service.setTheme(username, theme); service.setTheme(username, theme);

View File

@ -449,6 +449,14 @@ body {
color: white; color: white;
} }
.slot_reason {
color: #26a269;
}
[data-bs-theme=dark] .slot_reason {
color: lime;
}
.flex-row-weekday { .flex-row-weekday {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;

View File

@ -249,11 +249,11 @@
<div class="container"> <div class="container">
<div class="row row-weekday"> <div class="row row-weekday">
<div class="col">Sonntag</div> <div class="col">Sonntag</div>
<div class="col">Montag</div> <div class="col boldy">Montag</div>
<div class="col">Dienstag</div> <div class="col boldy">Dienstag</div>
<div class="col">Mittwoch</div> <div class="col boldy">Mittwoch</div>
<div class="col">Donnerstag</div> <div class="col boldy">Donnerstag</div>
<div class="col">Freitag</div> <div class="col boldy">Freitag</div>
<div class="col">Samstag</div> <div class="col">Samstag</div>
</div> </div>
<div class="row row-weekday"> <div class="row row-weekday">
@ -262,11 +262,30 @@
<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_right boldy" th:text="${s.printTime()}" th:if="${s.id}"></span> <span class="slot_badge_middle slot_reason" th:if="${s.reason}" th:text="${s.reason}"></span><span th:class="${s.reason != null ? 'slot_badge_right' : 'slot_badge_right boldy'}" th:text="${s.printTime()}" th:if="${s.id}"></span><span
<span class="slot_badge_right" th:unless="${s.id}">&nbsp;--:--&nbsp;</span> class="slot_badge_right" th:unless="${s.id}">&nbsp;--:--&nbsp;</span>
</div>
</div>
<div class="row alert alert-info">
<div class="col-sm-12">
<span style="text-decoration: underline">Legende</span>
</div>
<div class="col">
Üb: Überstunden, Mehrarbeit<br />
Ur: Urlaub, Sonderurlaub, Kur<br />
gF: gesetzlicher Feiertag<br />
</div>
<div class="col">
Kr: Arbeits- und Dienstunfähigkeit<br />
Gl: Freistellung aus Gleitzeitguthaben<br />
Ar: Arbeits- und Dienstbefreiung<br />
</div>
<div class="col">
mK: "mit Kind krank"<br />
Di: Dienstreise, Dienstgänge<br />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,21 +1,66 @@
<!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>Slot aktualisieren</title> <title>Slot aktualisieren</title>
</head> </head>
<body> <body>
<ul layout:fragment="menu"> <ul layout:fragment="menu">
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
<a class="nav-link btn btn-outline-primary btn-white-text" th:href="@{/done/list}">zur Arbeitszeit</a>
</li>
</ul> </ul>
<main layout:fragment="content"> <main layout:fragment="content">
<div class="container formpane"> <div class="container formpane">
<div class="row" th:if="${bean}"> <form th:action="@{/done/slot/upsert}" method="post" th:object="${bean}">
<div class="col-sm-3">ID</div> <input type="hidden" th:field="*{id}" />
<div class="col-sm-9" th:text="${bean.id}"></div> <div class="row g-2" th:if="${bean}">
<div class="col-sm-3">Tag</div> <div class="col-sm-3">Tag</div>
<div class="col-sm-9" th:text="${#temporals.format(bean.day, 'EEEE, dd.MM.yyyy')}"></div> <div class="col-sm-9">
<div class="col-sm-3">vereinbarte Arbeitszeit</div> <input type="date" th:field="*{day}" class="form-control" />
<div class="col-sm-9" th:text="${bean.printTime()}"></div> </div>
<div class="col-sm-3">vereinbarte Arbeitszeit in Minuten</div>
<div class="col-sm-9">
<input type="number" th:field="*{minutes}" class="form-control">
</div>
<div class="col-sm-3">Abweichungsgrund</div>
<div class="col-sm-9">
<select th:field="*{reason}" class="form-select">
<option value="">-</option>
<option value="Ar">Arbeits- und Dienstbefreiung</option>
<option value="Di">Dienstreise, Dienstgänge</option>
<option value="gF">gesetzlicher Feiertag</option>
<option value="Gl">Freistellung aus Gleitzeitguthaben</option>
<option value="Kr">Arbeits- und Dienstunfähigkeit</option>
<option value="mK">"mit Kind krank"</option>
<option value="Ur">Urlaub, Sonderurlaub, Kur</option>
<option value="Üb">Überstunden, Mehrarbeit</option>
</select>
</div>
<div class="col-sm-3"></div>
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-primary">Übernehmen</button>
</div>
</div>
</form>
</div>
<div class="container formpane" th:if="${bean.id}">
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">Slot löschen</button>
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="deleteModalLabel">Slot löschen</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body text-danger">
Wollen Sie die angegebene Arbeitszeit von <span th:text="${bean.printTime()}"></span> vom Tag <span th:text="${#temporals.format(bean.day, 'dd.MM.yyyy')}"></span> wirklich löschen?
</div>
<div class="modal-footer">
<a th:href="@{/done/slot/{id}/delete(id=${bean.id})}" class="btn btn-outline-danger">Ja</a>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Nein</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
</main> </main>