diff --git a/build.gradle b/build.gradle index 7165806..bc126e7 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { apply plugin: 'io.spring.dependency-management' group = 'de.jottyfan' -version = '1.3.9' +version = '1.4.0' description = """timetrack""" @@ -48,7 +48,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6' - implementation 'de.jottyfan:timetrackjooq:0.1.2' + implementation 'de.jottyfan:timetrackjooq:0.1.3' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.2.1' diff --git a/src/main/java/de/jottyfan/timetrack/config/InitialConfiguration.java b/src/main/java/de/jottyfan/timetrack/config/InitialConfiguration.java index 8c865f9..c27b03d 100644 --- a/src/main/java/de/jottyfan/timetrack/config/InitialConfiguration.java +++ b/src/main/java/de/jottyfan/timetrack/config/InitialConfiguration.java @@ -11,7 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; -import de.jottyfan.timetrack.modules.done.DoneModel; +import de.jottyfan.timetrack.modules.done.model.DoneModel; /** * diff --git a/src/main/java/de/jottyfan/timetrack/modules/IndexController.java b/src/main/java/de/jottyfan/timetrack/modules/IndexController.java index 0979591..c7c4f1a 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/IndexController.java +++ b/src/main/java/de/jottyfan/timetrack/modules/IndexController.java @@ -16,9 +16,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.modules.done.DoneBean; -import de.jottyfan.timetrack.modules.done.DoneModel; import de.jottyfan.timetrack.modules.done.DoneService; -import de.jottyfan.timetrack.modules.done.SummaryBean; +import de.jottyfan.timetrack.modules.done.model.DoneModel; +import de.jottyfan.timetrack.modules.done.model.SummaryBean; import de.jottyfan.timetrack.modules.profile.ProfileService; import jakarta.annotation.security.RolesAllowed; import jakarta.servlet.ServletException; diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/DoneBean.java b/src/main/java/de/jottyfan/timetrack/modules/done/DoneBean.java index 03c5263..491fe6c 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/DoneBean.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/DoneBean.java @@ -37,9 +37,11 @@ public class DoneBean implements Serializable, Comparable { private Integer fkModule; private Integer fkJob; private Integer fkBilling; + private Boolean isFavorite; public DoneBean() { this.day = null; + isFavorite = false; } public DoneBean(TDoneRecord r, Map projectMap, Map moduleMap, @@ -56,6 +58,7 @@ public class DoneBean implements Serializable, Comparable { this.fkModule = module.getPk(); this.fkJob = activity.getPk(); this.fkBilling = billing.getPk(); + isFavorite = false; } private final String nullable(Object o, String format) { @@ -94,6 +97,7 @@ public class DoneBean implements Serializable, Comparable { buf.append(",module=").append(module == null ? "" : module.getName()); buf.append(",activity=").append(activity == null ? "" : activity.getName()); buf.append(",billing=").append(billing == null ? "" : billing.getName()); + buf.append(",isFavorite=").append(isFavorite); buf.append("}"); return buf.toString(); } @@ -383,4 +387,18 @@ public class DoneBean implements Serializable, Comparable { public void setFkBilling(Integer fkBilling) { this.fkBilling = fkBilling; } + + /** + * @return the isFavorite + */ + public Boolean getIsFavorite() { + return isFavorite; + } + + /** + * @param isFavorite the isFavorite to set + */ + public void setIsFavorite(Boolean isFavorite) { + this.isFavorite = isFavorite; + } } diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/DoneController.java b/src/main/java/de/jottyfan/timetrack/modules/done/DoneController.java index 7740b16..e8447d1 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/DoneController.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/DoneController.java @@ -17,6 +17,8 @@ import org.springframework.web.bind.annotation.SessionAttributes; import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.modules.CommonController; +import de.jottyfan.timetrack.modules.done.model.DoneModel; +import de.jottyfan.timetrack.modules.done.model.SummaryBean; import de.jottyfan.timetrack.modules.profile.ProfileService; import jakarta.annotation.security.RolesAllowed; @@ -62,6 +64,7 @@ public class DoneController extends CommonController { model.addAttribute("jobList", doneService.getJobs(false)); model.addAttribute("billingList", doneService.getBillings(false)); model.addAttribute("theme", profileService.getTheme(username)); + model.addAttribute("favorites", doneService.getFavorites(username)); return "done/list"; } @@ -157,4 +160,18 @@ public class DoneController extends CommonController { Integer amount = doneService.doDelete(id); return amount.equals(1) ? "redirect:/done/list" : "redirect:/" + toItem(id, model); } + + @RolesAllowed("timetrack_user") + @GetMapping(value = "/done/favorize/{id}") + public String favorize(@PathVariable Integer id) { + doneService.favorize(id); + return "redirect:/done/list"; + } + + @RolesAllowed("timetrack_user") + @GetMapping(value = "/done/unfavorize/{id}") + public String unfavorize(@PathVariable Integer id) { + doneService.unfavorize(id); + return "redirect:/done/list"; + } } diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/DoneGateway.java b/src/main/java/de/jottyfan/timetrack/modules/done/DoneGateway.java index e896b5b..0bdc3db 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/DoneGateway.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/DoneGateway.java @@ -1,6 +1,7 @@ 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_FAVORITE; import static de.jottyfan.timetrack.db.done.Tables.V_BILLING; import static de.jottyfan.timetrack.db.done.Tables.V_JOB; import static de.jottyfan.timetrack.db.done.Tables.V_MODULE; @@ -20,8 +21,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jooq.DSLContext; import org.jooq.DeleteConditionStep; +import org.jooq.InsertReturningStep; import org.jooq.InsertValuesStep7; +import org.jooq.Record5; import org.jooq.Record7; +import org.jooq.Record8; import org.jooq.Result; import org.jooq.SelectConditionStep; import org.jooq.SelectLimitPercentStep; @@ -32,12 +36,14 @@ import org.springframework.stereotype.Repository; import de.jottyfan.timetrack.db.done.tables.TDone; import de.jottyfan.timetrack.db.done.tables.records.TDoneRecord; +import de.jottyfan.timetrack.db.done.tables.records.TFavoriteRecord; import de.jottyfan.timetrack.db.done.tables.records.VBillingRecord; import de.jottyfan.timetrack.db.done.tables.records.VJobRecord; import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord; import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord; import de.jottyfan.timetrack.db.profile.tables.records.TLoginRecord; import de.jottyfan.timetrack.help.LocalDateHelper; +import de.jottyfan.timetrack.modules.done.model.FavoriteBean; /** * @@ -232,7 +238,7 @@ public class DoneGateway { */ private List getAllOfInterval(LocalDateTime start, LocalDateTime end, Integer userId) throws DataAccessException, ClassNotFoundException, SQLException { - SelectConditionStep> sql = getJooq() + SelectConditionStep> sql = getJooq() // @formatter:off .select(T_DONE.PK, T_DONE.TIME_FROM, @@ -240,8 +246,14 @@ public class DoneGateway { T_DONE.FK_PROJECT, T_DONE.FK_MODULE, T_DONE.FK_JOB, - T_DONE.FK_BILLING) + T_DONE.FK_BILLING, + T_FAVORITE.PK_FAVORITE) .from(T_DONE) + .leftJoin(T_FAVORITE).on(T_FAVORITE.FK_LOGIN.eq(T_DONE.FK_LOGIN)) + .and(T_FAVORITE.FK_PROJECT.eq(T_DONE.FK_PROJECT).or(T_FAVORITE.FK_PROJECT.isNull().and(T_DONE.FK_PROJECT.isNull()))) + .and(T_FAVORITE.FK_MODULE.eq(T_DONE.FK_MODULE).or(T_FAVORITE.FK_MODULE.isNull().and(T_DONE.FK_MODULE.isNull()))) + .and(T_FAVORITE.FK_JOB.eq(T_DONE.FK_JOB).or(T_FAVORITE.FK_JOB.isNull().and(T_DONE.FK_JOB.isNull()))) + .and(T_FAVORITE.FK_BILLING.eq(T_DONE.FK_BILLING).or(T_FAVORITE.FK_BILLING.isNull().and(T_DONE.FK_BILLING.isNull()))) .where(T_DONE.TIME_FROM.between(start, end).or(T_DONE.TIME_FROM.isNull())) .and(T_DONE.TIME_UNTIL.between(start, end).or(T_DONE.TIME_UNTIL.isNull())) .and(T_DONE.FK_LOGIN.eq(userId == null ? -999999 : userId)); @@ -252,7 +264,7 @@ public class DoneGateway { Map moduleMap = getModuleMap(); Map jobMap = getJobMap(); Map billingMap = getBillingMap(); - for (Record7 r : sql.fetch()) { + for (Record8 r : sql.fetch()) { DoneBean bean = new DoneBean(); bean.setPk(r.get(T_DONE.PK)); bean.setTimeFrom(r.get(T_DONE.TIME_FROM)); @@ -262,6 +274,7 @@ public class DoneGateway { bean.setModule(moduleMap.get(r.get(T_DONE.FK_MODULE))); bean.setActivity(jobMap.get(r.get(T_DONE.FK_JOB))); bean.setBilling(billingMap.get(r.get(T_DONE.FK_BILLING))); + bean.setIsFavorite(r.get(T_FAVORITE.PK_FAVORITE) != null); list.add(bean); } list.sort((o1, o2) -> o1 == null ? 0 : o1.compareTo(o2)); @@ -436,4 +449,72 @@ public class DoneGateway { LOGGER.debug(sql.toString()); return sql.execute(); } + + public void favorize(Integer id) { + InsertReturningStep sql = getJooq() + // @formatter:off + .insertInto(T_FAVORITE, + T_FAVORITE.FK_LOGIN, + T_FAVORITE.FK_PROJECT, + T_FAVORITE.FK_MODULE, + T_FAVORITE.FK_JOB, + T_FAVORITE.FK_BILLING) + .select(getJooq() + .select(T_DONE.FK_LOGIN, T_DONE.FK_PROJECT, T_DONE.FK_MODULE, T_DONE.FK_JOB, T_DONE.FK_BILLING) + .from(T_DONE) + .where(T_DONE.PK.eq(id))) + // TODO: create unique constraint + /* + .onConflict(T_FAVORITE.FK_LOGIN, T_FAVORITE.FK_PROJECT, T_FAVORITE.FK_MODULE, T_FAVORITE.FK_JOB, T_FAVORITE.FK_BILLING) + .doNothing()*/ + ; + // @formatter:on + LOGGER.trace(sql); + sql.execute(); + } + + public void unfavorize(Integer id) { + DeleteConditionStep sql = getJooq() + // @formatter:off + .deleteFrom(T_FAVORITE) + .using(T_DONE) + .where(T_FAVORITE.FK_LOGIN.eq(T_DONE.FK_LOGIN)) + .and(T_FAVORITE.FK_PROJECT.eq(T_DONE.FK_PROJECT).or(T_FAVORITE.FK_PROJECT.isNull().and(T_DONE.FK_PROJECT.isNull()))) + .and(T_FAVORITE.FK_MODULE.eq(T_DONE.FK_MODULE).or(T_FAVORITE.FK_MODULE.isNull().and(T_DONE.FK_MODULE.isNull()))) + .and(T_FAVORITE.FK_JOB.eq(T_DONE.FK_JOB).or(T_FAVORITE.FK_JOB.isNull().and(T_DONE.FK_JOB.isNull()))) + .and(T_FAVORITE.FK_BILLING.eq(T_DONE.FK_BILLING).or(T_FAVORITE.FK_BILLING.isNull().and(T_DONE.FK_BILLING.isNull()))) + .and(T_DONE.PK.eq(id)); + // @formatter:on + LOGGER.trace(sql); + sql.execute(); + } + + public List getFavorites(Integer login) { + SelectConditionStep> sql = getJooq() + // @formatter:off + .select(T_FAVORITE.PK_FAVORITE, + V_PROJECT.NAME, + V_MODULE.NAME, + V_JOB.NAME, + V_BILLING.NAME) + .from(T_FAVORITE) + .leftJoin(V_PROJECT).on(V_PROJECT.PK.eq(T_FAVORITE.FK_PROJECT)) + .leftJoin(V_MODULE).on(V_MODULE.PK.eq(T_FAVORITE.FK_MODULE)) + .leftJoin(V_JOB).on(V_JOB.PK.eq(T_FAVORITE.FK_JOB)) + .leftJoin(V_BILLING).on(V_BILLING.PK.eq(T_FAVORITE.FK_BILLING)) + .where(T_FAVORITE.FK_LOGIN.eq(login)); + // @formatter:on + LOGGER.trace(sql); + List list = new ArrayList<>(); + for (Record5 r : sql.fetch()) { + FavoriteBean bean = new FavoriteBean(); + bean.setFkFavorite(r.get(T_FAVORITE.PK_FAVORITE)); + bean.setProject(r.get(V_PROJECT.NAME)); + bean.setModule(r.get(V_MODULE.NAME)); + bean.setJob(r.get(V_JOB.NAME)); + bean.setBilling(r.get(V_BILLING.NAME)); + list.add(bean); + } + return list; + } } diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/DoneService.java b/src/main/java/de/jottyfan/timetrack/modules/done/DoneService.java index dc76034..bd78cd7 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/DoneService.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/DoneService.java @@ -19,6 +19,7 @@ import de.jottyfan.timetrack.db.done.tables.records.VBillingRecord; import de.jottyfan.timetrack.db.done.tables.records.VJobRecord; import de.jottyfan.timetrack.db.done.tables.records.VModuleRecord; import de.jottyfan.timetrack.db.done.tables.records.VProjectRecord; +import de.jottyfan.timetrack.modules.done.model.FavoriteBean; import de.jottyfan.timetrack.modules.note.NoteService; /** @@ -181,7 +182,7 @@ public class DoneService { if (userId == null) { LOGGER.warn("userId of user {} is null", username); } - return gw.getRecent( userId, recentCount); + return gw.getRecent(userId, recentCount); } catch (Exception e) { LOGGER.error(e); return new ArrayList<>(); @@ -194,4 +195,28 @@ public class DoneService { bean.setTimeUntil(null); return this.doUpsert(bean, username); } + + public void favorize(Integer id) { + try { + new DoneGateway(dsl).favorize(id); + } catch (Exception e) { + } + } + + public void unfavorize(Integer id) { + try { + new DoneGateway(dsl).unfavorize(id); + } catch (Exception e) { + } + } + + public List getFavorites(String username) { + try { + DoneGateway gw = new DoneGateway(dsl); + Integer login = gw.getUserId(username); + return gw.getFavorites(login); + } catch (Exception e) { + return new ArrayList<>(); + } + } } diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/job/JobController.java b/src/main/java/de/jottyfan/timetrack/modules/done/job/JobController.java index 7b5bfd7..12bbc43 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/job/JobController.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/job/JobController.java @@ -13,7 +13,7 @@ import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.db.done.tables.records.TJobRecord; import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.done.DoneController; -import de.jottyfan.timetrack.modules.done.DoneModel; +import de.jottyfan.timetrack.modules.done.model.DoneModel; import de.jottyfan.timetrack.modules.profile.ProfileService; import jakarta.annotation.security.RolesAllowed; diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/DoneModel.java b/src/main/java/de/jottyfan/timetrack/modules/done/model/DoneModel.java similarity index 94% rename from src/main/java/de/jottyfan/timetrack/modules/done/DoneModel.java rename to src/main/java/de/jottyfan/timetrack/modules/done/model/DoneModel.java index a3ee4c2..c5e9331 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/DoneModel.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/model/DoneModel.java @@ -1,4 +1,4 @@ -package de.jottyfan.timetrack.modules.done; +package de.jottyfan.timetrack.modules.done.model; import java.io.Serializable; import java.time.LocalDate; diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/model/FavoriteBean.java b/src/main/java/de/jottyfan/timetrack/modules/done/model/FavoriteBean.java new file mode 100644 index 0000000..7dcd78c --- /dev/null +++ b/src/main/java/de/jottyfan/timetrack/modules/done/model/FavoriteBean.java @@ -0,0 +1,88 @@ +package de.jottyfan.timetrack.modules.done.model; + +import java.io.Serializable; + +/** + * + * @author jotty + * + */ +public class FavoriteBean implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer fkFavorite; + private String project; + private String module; + private String job; + private String billing; + + /** + * @return the project + */ + public String getProject() { + return project; + } + + /** + * @param project the project to set + */ + public void setProject(String project) { + this.project = project; + } + + /** + * @return the module + */ + public String getModule() { + return module; + } + + /** + * @param module the module to set + */ + public void setModule(String module) { + this.module = module; + } + + /** + * @return the job + */ + public String getJob() { + return job; + } + + /** + * @param job the job to set + */ + public void setJob(String job) { + this.job = job; + } + + /** + * @return the billing + */ + public String getBilling() { + return billing; + } + + /** + * @param billing the billing to set + */ + public void setBilling(String billing) { + this.billing = billing; + } + + /** + * @return the fkFavorite + */ + public Integer getFkFavorite() { + return fkFavorite; + } + + /** + * @param fkFavorite the fkFavorite to set + */ + public void setFkFavorite(Integer fkFavorite) { + this.fkFavorite = fkFavorite; + } +} diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/SummaryBean.java b/src/main/java/de/jottyfan/timetrack/modules/done/model/SummaryBean.java similarity index 97% rename from src/main/java/de/jottyfan/timetrack/modules/done/SummaryBean.java rename to src/main/java/de/jottyfan/timetrack/modules/done/model/SummaryBean.java index df3cebf..2ad80d2 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/SummaryBean.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/model/SummaryBean.java @@ -1,4 +1,4 @@ -package de.jottyfan.timetrack.modules.done; +package de.jottyfan.timetrack.modules.done.model; import java.io.Serializable; import java.time.Duration; @@ -6,6 +6,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; +import de.jottyfan.timetrack.modules.done.DoneBean; + /** * * @author henkej diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/module/ModuleController.java b/src/main/java/de/jottyfan/timetrack/modules/done/module/ModuleController.java index b462d45..366d131 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/module/ModuleController.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/module/ModuleController.java @@ -13,7 +13,7 @@ import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.db.done.tables.records.TModuleRecord; import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.done.DoneController; -import de.jottyfan.timetrack.modules.done.DoneModel; +import de.jottyfan.timetrack.modules.done.model.DoneModel; import de.jottyfan.timetrack.modules.profile.ProfileService; import jakarta.annotation.security.RolesAllowed; diff --git a/src/main/java/de/jottyfan/timetrack/modules/done/project/ProjectController.java b/src/main/java/de/jottyfan/timetrack/modules/done/project/ProjectController.java index 48cb6d5..dedb256 100644 --- a/src/main/java/de/jottyfan/timetrack/modules/done/project/ProjectController.java +++ b/src/main/java/de/jottyfan/timetrack/modules/done/project/ProjectController.java @@ -13,7 +13,7 @@ import de.jottyfan.timetrack.component.OAuth2Provider; import de.jottyfan.timetrack.db.done.tables.records.TProjectRecord; import de.jottyfan.timetrack.modules.CommonController; import de.jottyfan.timetrack.modules.done.DoneController; -import de.jottyfan.timetrack.modules.done.DoneModel; +import de.jottyfan.timetrack.modules.done.model.DoneModel; import de.jottyfan.timetrack.modules.profile.ProfileService; import jakarta.annotation.security.RolesAllowed; diff --git a/src/main/resources/static/css/style.css b/src/main/resources/static/css/style.css index 16cddd6..9568478 100644 --- a/src/main/resources/static/css/style.css +++ b/src/main/resources/static/css/style.css @@ -391,3 +391,6 @@ body { background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64); } +.golden { + color: darkgoldenrod; +} \ No newline at end of file diff --git a/src/main/resources/templates/done/list.html b/src/main/resources/templates/done/list.html index 91d25a5..bf49642 100644 --- a/src/main/resources/templates/done/list.html +++ b/src/main/resources/templates/done/list.html @@ -64,7 +64,16 @@ Modul Aufgabe Abrechnung - + + + @@ -83,7 +92,10 @@ th:if="${done.billing != null}"> - + + + +