prepared favorites

This commit is contained in:
Jörg Henke
2023-11-02 18:07:34 +01:00
parent f11723505e
commit d702d6816b
15 changed files with 262 additions and 16 deletions

View File

@ -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'

View File

@ -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;
/**
*

View File

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

View File

@ -37,9 +37,11 @@ public class DoneBean implements Serializable, Comparable<DoneBean> {
private Integer fkModule;
private Integer fkJob;
private Integer fkBilling;
private Boolean isFavorite;
public DoneBean() {
this.day = null;
isFavorite = false;
}
public DoneBean(TDoneRecord r, Map<Integer, VProjectRecord> projectMap, Map<Integer, VModuleRecord> moduleMap,
@ -56,6 +58,7 @@ public class DoneBean implements Serializable, Comparable<DoneBean> {
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<DoneBean> {
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<DoneBean> {
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;
}
}

View File

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

View File

@ -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<DoneBean> getAllOfInterval(LocalDateTime start, LocalDateTime end, Integer userId)
throws DataAccessException, ClassNotFoundException, SQLException {
SelectConditionStep<Record7<Integer, LocalDateTime, LocalDateTime, Integer, Integer, Integer, Integer>> sql = getJooq()
SelectConditionStep<Record8<Integer, LocalDateTime, LocalDateTime, Integer, Integer, Integer, Integer, Integer>> 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<Integer, VModuleRecord> moduleMap = getModuleMap();
Map<Integer, VJobRecord> jobMap = getJobMap();
Map<Integer, VBillingRecord> billingMap = getBillingMap();
for (Record7<Integer, LocalDateTime, LocalDateTime, Integer, Integer, Integer, Integer> r : sql.fetch()) {
for (Record8<Integer, LocalDateTime, LocalDateTime, Integer, Integer, Integer, Integer, Integer> 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<TFavoriteRecord> 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<TFavoriteRecord> 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<FavoriteBean> getFavorites(Integer login) {
SelectConditionStep<Record5<Integer, String, String, String, String>> 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<FavoriteBean> list = new ArrayList<>();
for (Record5<Integer, String, String, String, String> 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;
}
}

View File

@ -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<FavoriteBean> getFavorites(String username) {
try {
DoneGateway gw = new DoneGateway(dsl);
Integer login = gw.getUserId(username);
return gw.getFavorites(login);
} catch (Exception e) {
return new ArrayList<>();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -391,3 +391,6 @@ body {
background-image: linear-gradient(to right bottom, #99c1f1, #1a5f64);
}
.golden {
color: darkgoldenrod;
}

View File

@ -64,7 +64,16 @@
<th>Modul</th>
<th>Aufgabe</th>
<th>Abrechnung</th>
<th></th>
<th>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">Favoriten</button>
<ul class="dropdown-menu">
<li th:each="f : ${favorites}">
<a class="dropdown-item" href="#"><span th:text="${f.project} + ' ' + ${f.module} + ' ' + ${f.job}"></span></a>
</li>
</ul>
</div>
</th>
</tr>
</thead>
<tbody>
@ -83,7 +92,10 @@
th:if="${done.billing != null}"></span></td>
<td>
<a class="btn-list" th:href="@{/done/copy/{id}(id=${done.pk})}" title="Aufgabe neu beginnen"><i class="fa fa-copy"></i></a>
<a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"><i class="fa fa-edit"></i></a></td>
<a class="btn-list" th:href="@{/done/edit/{id}(id=${done.pk})}" title="Eintrag bearbeiten"><i class="fa fa-edit"></i></a>
<a class="btn-list" th:href="@{/done/favorize/{id}(id=${done.pk})}" title="als Favorit speichern" th:if="${!done.isFavorite}"><i class="far fa-star golden"></i></a>
<a class="btn-list" th:href="@{/done/unfavorize/{id}(id=${done.pk})}" title="Favoritenstatus entfernen" th:if="${done.isFavorite}"><i class="fas fa-star golden"></i></a>
</td>
</tr>
</tbody>
<tfoot>