Compare commits
4 Commits
springboot
...
c5604d3ce8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5604d3ce8 | ||
|
|
aaee7c9dff | ||
|
|
f8290cdbb6 | ||
|
|
cb8de9b119 |
@@ -7,7 +7,7 @@ plugins {
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'de.jottyfan'
|
||||
version = '1.6.0'
|
||||
version = '26.1'
|
||||
|
||||
description = """timetrack"""
|
||||
|
||||
@@ -24,7 +24,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'de.jottyfan:timetrackjooq:20240109'
|
||||
implementation 'de.jottyfan:timetrackjooq:20260114'
|
||||
|
||||
implementation 'org.webjars:bootstrap:5.3.8'
|
||||
implementation 'org.webjars:font-awesome:7.1.0'
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
package de.jottyfan.timetrack;
|
||||
|
||||
import static de.jottyfan.timetrack.db.public_.Tables.V_VERSION;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.exception.DataAccessException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableTransactionManagement
|
||||
public class Main extends SpringBootServletInitializer {
|
||||
|
||||
private static final Integer requiredDbVersion = 20260114;
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(Main.class);
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(
|
||||
SpringApplicationBuilder application) {
|
||||
return application.sources(Main.class);
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
|
||||
ConfigurableApplicationContext context = builder.run();
|
||||
DSLContext jooq = context.getBean(DSLContext.class);
|
||||
Integer foundDbVersion = null;
|
||||
try {
|
||||
foundDbVersion = jooq.selectFrom(V_VERSION).fetchOne(V_VERSION.VERSION);
|
||||
} catch (DataAccessException e) {
|
||||
String msg = String.format("Wrong database version found; %d is required, but found %d", requiredDbVersion,
|
||||
foundDbVersion);
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
return builder.sources(Main.class);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class SecurityConfiguration {
|
||||
// @formatter:off
|
||||
.oauth2Login(o -> o.defaultSuccessUrl("/"))
|
||||
.logout(o -> o.logoutSuccessHandler(new OidcClientInitiatedLogoutSuccessHandler(crr)))
|
||||
.authorizeHttpRequests(o -> o.requestMatchers("/public/**", "/theme/**").permitAll().anyRequest().authenticated())
|
||||
.authorizeHttpRequests(o -> o.requestMatchers("/public/**", "/theme/**", "/webjars/**").permitAll().anyRequest().authenticated())
|
||||
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
|
||||
.sessionManagement(o -> o.init(sec));
|
||||
// @formatter:on
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.jottyfan.timetrack.help;
|
||||
|
||||
import static de.jottyfan.timetrack.db.public_.Tables.V_VERSION;
|
||||
|
||||
import org.jooq.DSLContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class MainfestRepository {
|
||||
@Autowired
|
||||
private DSLContext jooq;
|
||||
|
||||
/**
|
||||
* get the db version with title
|
||||
*
|
||||
* @return the DB version, prefixed by DBVersion
|
||||
*/
|
||||
public String getDbVersion() {
|
||||
return String.format("DBVersion %s", jooq.selectFrom(V_VERSION).fetchOne(V_VERSION.VERSION));
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,13 @@ public class ManifestBean {
|
||||
@Autowired(required = false)
|
||||
private BuildProperties buildProperties;
|
||||
|
||||
@Autowired
|
||||
private MainfestRepository repository;
|
||||
|
||||
public String getDbVersion() {
|
||||
return repository.getDbVersion();
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("Version ");
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class AppController extends CommonController {
|
||||
|
||||
@Autowired
|
||||
private AppService service;
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/apps")
|
||||
public String getAllApps(final Model model) {
|
||||
model.addAttribute("apps", service.getAppsOf(null));
|
||||
return "/projectmanagement/app/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/workpackage/{pkWorkpackage}/apps")
|
||||
public String getAppsOfWorkpackage(@PathVariable("pkWorkpackage") Integer pkWorkpackage, final Model model) {
|
||||
model.addAttribute("apps", service.getAppsOf(pkWorkpackage));
|
||||
return "/projectmanagement/app/list";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/app/{pkApp}/assign")
|
||||
public String loadAssignmentToolForApp(@PathVariable("pkApp") Integer pkApp, final Model model) {
|
||||
model.addAttribute("app", service.getApp(pkApp));
|
||||
model.addAttribute("workpackages", service.getWorkpackages());
|
||||
return "/projectmanagement/app/assign";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_APP;
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_WORKPACKAGE_APP;
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_WORKPACKAGE;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.Record7;
|
||||
import org.jooq.SelectConditionStep;
|
||||
import org.jooq.impl.DSL;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*/
|
||||
@Repository
|
||||
public class AppRepository {
|
||||
|
||||
@Autowired
|
||||
private DSLContext jooq;
|
||||
|
||||
/**
|
||||
* get all app beans for that workpackage
|
||||
*
|
||||
* @param fkWorkpackage the workpackage ID; if null, return all app beans
|
||||
* @return the list of app beans; an empty list at least
|
||||
*/
|
||||
public List<AppBean> getAllAppBeans(Integer fkWorkpackage) {
|
||||
SelectConditionStep<Record7<Integer, Integer, String, String, String, Integer, String>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_APP.PK_APP,
|
||||
T_APP.FK_BUNDLE,
|
||||
T_APP.BASIC_FUNCTIONALITY,
|
||||
T_APP.NAME,
|
||||
T_APP.DESCRIPTION,
|
||||
T_APP.FK_REPLACED_BY_APP,
|
||||
T_APP.REPOSITORY_URL)
|
||||
.from(T_APP)
|
||||
.leftJoin(T_WORKPACKAGE_APP).on(T_WORKPACKAGE_APP.FK_APP.eq(T_APP.PK_APP))
|
||||
.where(fkWorkpackage == null ? DSL.trueCondition() : T_WORKPACKAGE_APP.FK_WORKPACKAGE.eq(fkWorkpackage));
|
||||
// @formatter:on
|
||||
return sql.fetchInto(AppBean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the app
|
||||
*
|
||||
* @param pkApp the ID of the app
|
||||
* @return the app bean or null
|
||||
*/
|
||||
public AppBean getApp(Integer pkApp) {
|
||||
SelectConditionStep<Record7<Integer, Integer, String, String, String, Integer, String>> sql = jooq
|
||||
// @formatter:off
|
||||
.select(T_APP.PK_APP,
|
||||
T_APP.FK_BUNDLE,
|
||||
T_APP.BASIC_FUNCTIONALITY,
|
||||
T_APP.NAME,
|
||||
T_APP.DESCRIPTION,
|
||||
T_APP.FK_REPLACED_BY_APP,
|
||||
T_APP.REPOSITORY_URL)
|
||||
.from(T_APP)
|
||||
.where(T_APP.PK_APP.eq(pkApp));
|
||||
// @formatter:on
|
||||
return sql.fetchOneInto(AppBean.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get all work packages
|
||||
*
|
||||
* @return the list; an empty list at least
|
||||
*/
|
||||
public List<WorkpackageBean> getWorkpackages() {
|
||||
return jooq.selectFrom(T_WORKPACKAGE).fetchInto(WorkpackageBean.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class AppService {
|
||||
@Autowired
|
||||
private AppRepository repository;
|
||||
|
||||
public List<AppBean> getAppsOf(Integer fkWorkpackage) {
|
||||
return repository.getAllAppBeans(fkWorkpackage);
|
||||
}
|
||||
|
||||
public AppBean getApp(Integer pkApp) {
|
||||
return repository.getApp(pkApp);
|
||||
}
|
||||
|
||||
public List<WorkpackageBean> getWorkpackages() {
|
||||
return repository.getWorkpackages();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import de.jottyfan.timetrack.modules.CommonController;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.ProjectBean;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class ProjectmanagementController extends CommonController {
|
||||
|
||||
@Autowired
|
||||
private ProjectmanagementService service;
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement")
|
||||
public String getDashboard(final Model model) {
|
||||
model.addAttribute("projects", service.getProjects(true));
|
||||
return "/projectmanagement/dashboard";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/project/add")
|
||||
public String getProjectAddMask(final Model model) {
|
||||
model.addAttribute("bean", new ProjectBean());
|
||||
model.addAttribute("funders", service.getFunders());
|
||||
return "/projectmanagement/project/item";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/project/{pkProject}/edit")
|
||||
public String getProjectAddMask(@PathVariable("pkProject") Integer pkProject, final Model model) {
|
||||
model.addAttribute("bean", service.getProject(pkProject));
|
||||
model.addAttribute("funders", service.getFunders());
|
||||
return "/projectmanagement/project/item";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/workpackage/{pkWorkpackage}")
|
||||
public String getWorkpackage(@PathVariable("pkWorkpackage") Integer pkWorkpackage, final Model model) {
|
||||
model.addAttribute("bean", service.getWorkpackage(pkWorkpackage));
|
||||
return "/projectmanagement/workpackage/item";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/workpackage/{pkWorkpackage}/delete")
|
||||
public String deleteWorkpackage(@PathVariable("pkWorkpackage") Integer pkWorkpackage) {
|
||||
service.deleteWorkpackage(pkWorkpackage);
|
||||
return "redirect:/projectmanagement";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/{pkProject}/delete")
|
||||
public String deleteProject(@PathVariable("pkProject") Integer pkProject) {
|
||||
service.deleteProject(pkProject);
|
||||
return "redirect:/projectmanagement";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@GetMapping("/projectmanagement/project/{fkProject}/addWorkpackage")
|
||||
public String getWorkpackageAddMask(@PathVariable("fkProject") Integer fkProject, final Model model) {
|
||||
model.addAttribute("bean", WorkpackageBean.of(fkProject));
|
||||
return "/projectmanagement/workpackage/item";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/projectmanagement/upsert")
|
||||
public String upsertWorkpackage(@ModelAttribute("bean") ProjectBean bean, BindingResult bindingResult, final Model model) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("error", bindingResult.getAllErrors().toString());
|
||||
return "/projectmanagement";
|
||||
}
|
||||
service.upsertProject(bean);
|
||||
return "redirect:/projectmanagement";
|
||||
}
|
||||
|
||||
@RolesAllowed("timetrack_user")
|
||||
@PostMapping("/projectmanagement/project/{fkProject}/upsert")
|
||||
public String updateWorkpackage(@PathVariable("fkProject") Integer fkProject,
|
||||
@ModelAttribute("bean") WorkpackageBean bean, BindingResult bindingResult, final Model model) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("error", bindingResult.getAllErrors().toString());
|
||||
return "/projectmanagement/workpackage/item";
|
||||
}
|
||||
bean.setFkProject(fkProject);
|
||||
service.upsertWorkpackage(bean);
|
||||
return "redirect:/projectmanagement";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_FUNDER;
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_PROJECT;
|
||||
import static de.jottyfan.timetrack.db.project.Tables.T_WORKPACKAGE;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.InsertValuesStep3;
|
||||
import org.jooq.InsertValuesStep6;
|
||||
import org.jooq.UpdateConditionStep;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TFunderRecord;
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TProjectRecord;
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TWorkpackageRecord;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.ProjectBean;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Repository
|
||||
public class ProjectmanagementRepository {
|
||||
|
||||
@Autowired
|
||||
private DSLContext jooq;
|
||||
|
||||
public List<TFunderRecord> getFunders() {
|
||||
return jooq.selectFrom(T_FUNDER).fetchInto(TFunderRecord.class);
|
||||
}
|
||||
|
||||
public List<ProjectBean> getProjects() {
|
||||
List<TProjectRecord> projects = jooq.selectFrom(T_PROJECT).fetchInto(TProjectRecord.class);
|
||||
List<TWorkpackageRecord> workpackages = jooq.selectFrom(T_WORKPACKAGE).fetchInto(TWorkpackageRecord.class);
|
||||
Map<Integer, List<WorkpackageBean>> wpMap = new HashMap<>();
|
||||
workpackages.forEach(w -> {
|
||||
List<WorkpackageBean> found = wpMap.get(w.getFkProject());
|
||||
if (found == null) {
|
||||
found = new ArrayList<>();
|
||||
wpMap.put(w.getFkProject(), found);
|
||||
}
|
||||
found.add(WorkpackageBean.of(w));
|
||||
});
|
||||
List<ProjectBean> list = new ArrayList<>();
|
||||
for (TProjectRecord r : projects) {
|
||||
list.add(ProjectBean.of(r, wpMap.get(r.getPkProject())));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public WorkpackageBean getWorkpackage(Integer pkWorkpackage) {
|
||||
return jooq.selectFrom(T_WORKPACKAGE).where(T_WORKPACKAGE.PK_WORKPACKAGE.eq(pkWorkpackage))
|
||||
.fetchOneInto(WorkpackageBean.class);
|
||||
}
|
||||
|
||||
public void updateWorkpackage(WorkpackageBean bean) {
|
||||
UpdateConditionStep<TWorkpackageRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.update(T_WORKPACKAGE)
|
||||
.set(T_WORKPACKAGE.FK_PROJECT, bean.getFkProject())
|
||||
.set(T_WORKPACKAGE.NAME, bean.getName())
|
||||
.set(T_WORKPACKAGE.DESCRIPTION, bean.getDescription())
|
||||
.set(T_WORKPACKAGE.MILESTONE_URL, bean.getMilestoneUrl())
|
||||
.set(T_WORKPACKAGE.CONTRACT_URL, bean.getContractUrl())
|
||||
.set(T_WORKPACKAGE.PLANNED_DUEDATE, bean.getPlannedDuedate())
|
||||
.where(T_WORKPACKAGE.PK_WORKPACKAGE.eq(bean.getPkWorkpackage()));
|
||||
// @formatter:on
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public void insertWorkpackage(WorkpackageBean bean) {
|
||||
InsertValuesStep6<TWorkpackageRecord, Integer, String, String, String, String, LocalDate> sql = jooq
|
||||
// @formatter:off
|
||||
.insertInto(T_WORKPACKAGE,
|
||||
T_WORKPACKAGE.FK_PROJECT,
|
||||
T_WORKPACKAGE.NAME,
|
||||
T_WORKPACKAGE.DESCRIPTION,
|
||||
T_WORKPACKAGE.MILESTONE_URL,
|
||||
T_WORKPACKAGE.CONTRACT_URL,
|
||||
T_WORKPACKAGE.PLANNED_DUEDATE)
|
||||
.values(bean.getFkProject(), bean.getName(), bean.getDescription(), bean.getMilestoneUrl(), bean.getContractUrl(), bean.getPlannedDuedate());
|
||||
// @formatter:on
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public void deleteWorkpackage(Integer pkWorkpackage) {
|
||||
jooq.deleteFrom(T_WORKPACKAGE).where(T_WORKPACKAGE.PK_WORKPACKAGE.eq(pkWorkpackage)).execute();
|
||||
}
|
||||
|
||||
public void updateProject(ProjectBean bean) {
|
||||
UpdateConditionStep<TProjectRecord> sql = jooq
|
||||
// @formatter:off
|
||||
.update(T_PROJECT)
|
||||
.set(T_PROJECT.FK_FUNDER, bean.getFkFunder())
|
||||
.set(T_PROJECT.NAME, bean.getName())
|
||||
.set(T_PROJECT.DESCRIPTION, bean.getDescription())
|
||||
.where(T_PROJECT.PK_PROJECT.eq(bean.getPkProject()));
|
||||
// @formatter:on
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public void insertProject(ProjectBean bean) {
|
||||
InsertValuesStep3<TProjectRecord, String, String, Integer> sql = jooq
|
||||
// @formatter:off
|
||||
.insertInto(T_PROJECT,
|
||||
T_PROJECT.NAME,
|
||||
T_PROJECT.DESCRIPTION,
|
||||
T_PROJECT.FK_FUNDER)
|
||||
.values(bean.getName(), bean.getDescription(), bean.getFkFunder());
|
||||
// @formatter:on
|
||||
sql.execute();
|
||||
}
|
||||
|
||||
public void deleteProject(Integer pkProject) {
|
||||
jooq.deleteFrom(T_PROJECT).where(T_PROJECT.PK_PROJECT.eq(pkProject)).execute();
|
||||
}
|
||||
|
||||
public ProjectBean getProject(Integer pkProject) {
|
||||
return jooq.selectFrom(T_PROJECT).where(T_PROJECT.PK_PROJECT.eq(pkProject)).fetchOneInto(ProjectBean.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TFunderRecord;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.ProjectBean;
|
||||
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
@Service
|
||||
public class ProjectmanagementService {
|
||||
|
||||
@Autowired
|
||||
private ProjectmanagementRepository repository;
|
||||
|
||||
/**
|
||||
* get all projects
|
||||
*
|
||||
* @return all projects
|
||||
*/
|
||||
public List<ProjectBean> getProjects(boolean includeFunderName) {
|
||||
List<ProjectBean> list = repository.getProjects();
|
||||
if (includeFunderName) {
|
||||
List<TFunderRecord> funders = repository.getFunders();
|
||||
Map<Integer, String> funderMap = new HashMap<>();
|
||||
funders.forEach(f -> funderMap.put(f.getPkFunder(), f.getName()));
|
||||
for (ProjectBean bean : list) {
|
||||
bean.setFunderName(funderMap.get(bean.getFkFunder()));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the workpackage
|
||||
*
|
||||
* @param pkWorkpackage the ID of the workpackage
|
||||
* @return the bean or null if not found
|
||||
*/
|
||||
public WorkpackageBean getWorkpackage(Integer pkWorkpackage) {
|
||||
return repository.getWorkpackage(pkWorkpackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* update the workpackage
|
||||
*
|
||||
* @param bean the bean
|
||||
*/
|
||||
public void upsertWorkpackage(WorkpackageBean bean) {
|
||||
if (bean.getPkWorkpackage() == null) {
|
||||
repository.insertWorkpackage(bean);
|
||||
} else {
|
||||
repository.updateWorkpackage(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the work package
|
||||
*
|
||||
* @param pkWorkpackage the ID of the work package
|
||||
*/
|
||||
public void deleteWorkpackage(Integer pkWorkpackage) {
|
||||
repository.deleteWorkpackage(pkWorkpackage);
|
||||
}
|
||||
|
||||
public void upsertProject(ProjectBean bean) {
|
||||
if (bean.getPkProject() == null) {
|
||||
repository.insertProject(bean);
|
||||
} else {
|
||||
repository.updateProject(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteProject(Integer pkProject) {
|
||||
repository.deleteProject(pkProject);
|
||||
}
|
||||
|
||||
public List<TFunderRecord> getFunders() {
|
||||
return repository.getFunders();
|
||||
}
|
||||
|
||||
public ProjectBean getProject(Integer pkProject) {
|
||||
return repository.getProject(pkProject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class AppBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer pkApp;
|
||||
private Integer fkBundle;
|
||||
private String basicFunctionality;
|
||||
private String name;
|
||||
private String description;
|
||||
private Integer fkReplacedByApp;
|
||||
private String repositoryUrl;
|
||||
|
||||
/**
|
||||
* @return the pkApp
|
||||
*/
|
||||
public Integer getPkApp() {
|
||||
return pkApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pkApp the pkApp to set
|
||||
*/
|
||||
public void setPkApp(Integer pkApp) {
|
||||
this.pkApp = pkApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fkBundle
|
||||
*/
|
||||
public Integer getFkBundle() {
|
||||
return fkBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fkBundle the fkBundle to set
|
||||
*/
|
||||
public void setFkBundle(Integer fkBundle) {
|
||||
this.fkBundle = fkBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the basicFunctionality
|
||||
*/
|
||||
public String getBasicFunctionality() {
|
||||
return basicFunctionality;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param basicFunctionality the basicFunctionality to set
|
||||
*/
|
||||
public void setBasicFunctionality(String basicFunctionality) {
|
||||
this.basicFunctionality = basicFunctionality;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fkReplacedByApp
|
||||
*/
|
||||
public Integer getFkReplacedByApp() {
|
||||
return fkReplacedByApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fkReplacedByApp the fkReplacedByApp to set
|
||||
*/
|
||||
public void setFkReplacedByApp(Integer fkReplacedByApp) {
|
||||
this.fkReplacedByApp = fkReplacedByApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the repositoryUrl
|
||||
*/
|
||||
public String getRepositoryUrl() {
|
||||
return repositoryUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repositoryUrl the repositoryUrl to set
|
||||
*/
|
||||
public void setRepositoryUrl(String repositoryUrl) {
|
||||
this.repositoryUrl = repositoryUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TProjectRecord;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class ProjectBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer pkProject;
|
||||
private String name;
|
||||
private String description;
|
||||
private Integer fkFunder;
|
||||
private String funderName;
|
||||
private final List<WorkpackageBean> workpackages;
|
||||
|
||||
public ProjectBean() {
|
||||
workpackages = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static final ProjectBean of(TProjectRecord r, List<WorkpackageBean> workpackages) {
|
||||
ProjectBean bean = new ProjectBean();
|
||||
bean.setPkProject(r.getPkProject());
|
||||
bean.setName(r.getName());
|
||||
bean.setDescription(r.getDescription());
|
||||
bean.setFkFunder(r.getFkFunder());
|
||||
if (workpackages != null) {
|
||||
bean.getWorkpackages().addAll(workpackages);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the workpackages
|
||||
*/
|
||||
public List<WorkpackageBean> getWorkpackages() {
|
||||
return workpackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pkProject
|
||||
*/
|
||||
public Integer getPkProject() {
|
||||
return pkProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pkProject the pkProject to set
|
||||
*/
|
||||
public void setPkProject(Integer pkProject) {
|
||||
this.pkProject = pkProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the funderName
|
||||
*/
|
||||
public String getFunderName() {
|
||||
return funderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param funderName the funderName to set
|
||||
*/
|
||||
public void setFunderName(String funderName) {
|
||||
this.funderName = funderName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fkFunder
|
||||
*/
|
||||
public Integer getFkFunder() {
|
||||
return fkFunder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fkFunder the fkFunder to set
|
||||
*/
|
||||
public void setFkFunder(Integer fkFunder) {
|
||||
this.fkFunder = fkFunder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package de.jottyfan.timetrack.modules.projectmanagement.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import de.jottyfan.timetrack.db.project.tables.records.TWorkpackageRecord;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jotty
|
||||
*
|
||||
*/
|
||||
public class WorkpackageBean implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Integer pkWorkpackage;
|
||||
private Integer fkProject;
|
||||
private String name;
|
||||
private String description;
|
||||
private String milestoneUrl;
|
||||
private String contractUrl;
|
||||
private LocalDate plannedDuedate;
|
||||
|
||||
public static final WorkpackageBean of(TWorkpackageRecord r) {
|
||||
WorkpackageBean bean = new WorkpackageBean();
|
||||
bean.setPkWorkpackage(r.getPkWorkpackage());
|
||||
bean.setFkProject(r.getFkProject());
|
||||
bean.setName(r.getName());
|
||||
bean.setDescription(r.getDescription());
|
||||
bean.setMilestoneUrl(r.getMilestoneUrl());
|
||||
bean.setContractUrl(r.getContractUrl());
|
||||
bean.setPlannedDuedate(r.getPlannedDuedate());
|
||||
return bean;
|
||||
}
|
||||
|
||||
public static final WorkpackageBean of(Integer fkProject) {
|
||||
WorkpackageBean bean = new WorkpackageBean();
|
||||
bean.setFkProject(fkProject);
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pkWorkpackage
|
||||
*/
|
||||
public Integer getPkWorkpackage() {
|
||||
return pkWorkpackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pkWorkpackage the pkWorkpackage to set
|
||||
*/
|
||||
public void setPkWorkpackage(Integer pkWorkpackage) {
|
||||
this.pkWorkpackage = pkWorkpackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the fkProject
|
||||
*/
|
||||
public Integer getFkProject() {
|
||||
return fkProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fkProject the fkProject to set
|
||||
*/
|
||||
public void setFkProject(Integer fkProject) {
|
||||
this.fkProject = fkProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the milestoneUrl
|
||||
*/
|
||||
public String getMilestoneUrl() {
|
||||
return milestoneUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param milestoneUrl the milestoneUrl to set
|
||||
*/
|
||||
public void setMilestoneUrl(String milestoneUrl) {
|
||||
this.milestoneUrl = milestoneUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the contractUrl
|
||||
*/
|
||||
public String getContractUrl() {
|
||||
return contractUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param contractUrl the contractUrl to set
|
||||
*/
|
||||
public void setContractUrl(String contractUrl) {
|
||||
this.contractUrl = contractUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the plannedDuedate
|
||||
*/
|
||||
public LocalDate getPlannedDuedate() {
|
||||
return plannedDuedate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param plannedDuedate the plannedDuedate to set
|
||||
*/
|
||||
public void setPlannedDuedate(LocalDate plannedDuedate) {
|
||||
this.plannedDuedate = plannedDuedate;
|
||||
}
|
||||
}
|
||||
@@ -22,3 +22,6 @@ spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_
|
||||
# application
|
||||
server.port = ${server.port}
|
||||
server.servlet.context-path = /timetrack
|
||||
|
||||
spring.mvc.contentnegotiation.favor-parameter=false
|
||||
spring.mvc.contentnegotiation.media-types.css=text/css
|
||||
|
||||
@@ -242,15 +242,20 @@ body {
|
||||
|
||||
.version {
|
||||
font-size: small;
|
||||
color: black;
|
||||
color: gray;
|
||||
position: absolute;
|
||||
padding-top: 36px;
|
||||
padding-left: 22px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .version {
|
||||
color: white;
|
||||
.dbversion {
|
||||
font-size: small;
|
||||
color: gray;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
padding-left: 22px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.fc-content {
|
||||
@@ -494,3 +499,9 @@ body {
|
||||
.boldy {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.dashboardcard {
|
||||
width: 312px;
|
||||
margin: 8px;
|
||||
padding: 0px;
|
||||
}
|
||||
@@ -8,24 +8,24 @@
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.3.8/css/bootstrap.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/2.3.5/css/dataTables.dataTables.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/7.1.0/css/all.min.css}" />
|
||||
<link rel="stylesheet" type="text/css" th:href="@{/webjars/fullcalendar/6.1.10/main.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="16x16" th:href="@{/png/favicon/favicon-16x16.png}" />
|
||||
|
||||
<script th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script>
|
||||
<script th:src="@{/webjars/bootstrap/5.3.8/js/bootstrap.bundle.min.js}"></script>
|
||||
<script th:src="@{/webjars/datatables/2.3.5/js/dataTables.dataTables.min.js}"></script>
|
||||
<script th:src="@{/webjars/fullcalendar/6.1.10/main.js}"></script>
|
||||
<script th:src="@{/js/helper.js}"></script>
|
||||
<script th:src="@{/js/clock.js}"></script>
|
||||
<script th:src="@{/js/schedule.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/webjars/jquery/3.7.1/jquery.min.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/webjars/bootstrap/5.3.8/js/bootstrap.bundle.min.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/webjars/datatables/2.3.5/js/dataTables.dataTables.min.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/webjars/fullcalendar/6.1.19/index.global.min.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/helper.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/clock.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/schedule.js}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg static-top">
|
||||
<div class="container-fluid" style="width: 98%">
|
||||
<div class="dbversion" th:text="${@manifestBean.getDbVersion()}"></div>
|
||||
<i class="fa fa-calendar-alt"></i> <a class="navbar-brand" style="margin-left: 8px; z-index: 1" th:href="@{/}">Timetrack</a><br />
|
||||
<div class="version" th:text="${@manifestBean.getVersion()}"></div>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
|
||||
@@ -41,6 +41,7 @@
|
||||
<li><a class="dropdown-item" th:href="@{/contact/list}">Kontakte</a></li>
|
||||
<li><a class="dropdown-item" th:href="@{/note/list}">Notizen</a></li>
|
||||
<li><a class="dropdown-item" th:href="@{/calendar}">Kalender</a></li>
|
||||
<li><a class="dropdown-item" th:href="@{/projectmanagement}">Projekte</a></li>
|
||||
<li><hr /></li>
|
||||
<li><a class="dropdown-item" th:href="@{/logout}">[[${currentUser}]] abmelden</a></li>
|
||||
</ul></li>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<head>
|
||||
<title>Projektmanagement</title>
|
||||
</head>
|
||||
<body>
|
||||
<font layout:fragment="title">Projekt</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-secondary btn-white-text" th:href="@{/projectmanagement}">zur Projektübersicht</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div th:text="${app.name}"></div>
|
||||
<div th:each="p : ${workpackages}" th:text="${p.name}"></div>
|
||||
TODO: assign app to workpackage and store it
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
46
src/main/resources/templates/projectmanagement/app/list.html
Normal file
46
src/main/resources/templates/projectmanagement/app/list.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<head>
|
||||
<title>Projektmanagement</title>
|
||||
</head>
|
||||
<body>
|
||||
<font layout:fragment="title">Projekt</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-secondary btn-white-text" th:href="@{/projectmanagement}">zur Projektübersicht</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="p-2">
|
||||
<table id="table" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Beschreibung</th>
|
||||
<th>URL</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="b : ${apps}">
|
||||
<td th:text="${b.name}"></td>
|
||||
<td th:text="${b.description}"></td>
|
||||
<td><a th:href="${b.repositoryUrl}" target="_blank">gitlab</a></td>
|
||||
<td><a th:href="@{/projectmanagement/app/{id}/assign(id=${b.pkApp})}">zuordnen</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
var localeUrl = '[[@{/js/dataTables/de.json}]]';
|
||||
$("#table").DataTable({
|
||||
"language" : {
|
||||
"url" : localeUrl
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<head>
|
||||
<title>Projektmanagement</title>
|
||||
</head>
|
||||
<body>
|
||||
<font layout:fragment="title">Projekte</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-success btn-white-text" th:href="@{/projectmanagement/project/add}">Neues Projekt anlegen</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-3 col-lg-2 dashboardcard" th:each="p : ${projects}">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span th:text="${p.funderName}"></span>: <span class="fw-bold ms-auto" th:text="${p.name}"></span>
|
||||
<a th:href="@{/projectmanagement/project/{p}/edit(p=${p.pkProject})}" class="btn btn-link ms-auto" title="bearbeiten">edit</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center" th:each="w : ${p.workpackages}">
|
||||
<a th:href="@{/projectmanagement/workpackage/{id}(id=${w.pkWorkpackage})}" th:text="${w.name}"></a>
|
||||
<a th:href="@{/projectmanagement/workpackage/{id}/apps(id=${w.pkWorkpackage})}" class="ms-auto">apps</a>
|
||||
</div>
|
||||
<a th:href="@{/projectmanagement/project/{id}/addWorkpackage(id=${p.pkProject})}" class="btn btn-outline-secondary">Workpackage anlegen</a>
|
||||
</div>
|
||||
<div class="card-footer" th:text="${p.description}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-3 col-lg-2 dashboardcard">
|
||||
<div class="card">
|
||||
<div class="card-header">Alles</div>
|
||||
<div class="card-body">
|
||||
<a th:href="@{/projectmanagement/apps}">alle apps</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<head>
|
||||
<title>Projektmanagement</title>
|
||||
</head>
|
||||
<body>
|
||||
<font layout:fragment="title">Projekt</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-secondary btn-white-text" th:href="@{/projectmanagement}">abbrechen</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="container">
|
||||
<div class="alert alert-danger" th:if="${error}" th:text="${error}"></div>
|
||||
<form th:action="@{/projectmanagement/upsert}" method="post" th:object="${bean}">
|
||||
<div class="container m-2">
|
||||
<div class="row g-2">
|
||||
<div class="col-2" th:if="${bean.pkProject}">ID</div>
|
||||
<div class="col-10" th:if="${bean.pkProject}">
|
||||
<input type="text" class="form-control" th:field="*{pkProject}" readonly="readonly" />
|
||||
</div>
|
||||
<div class="col-2">Name</div>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control" th:field="*{name}" />
|
||||
</div>
|
||||
<div class="col-2">Beschreibung</div>
|
||||
<div class="col-10">
|
||||
<textarea class="form-control" th:field="*{description}"></textarea>
|
||||
</div>
|
||||
<div class="col-2">Geldgeber</div>
|
||||
<div class="col-10">
|
||||
<select th:field="*{fkFunder}" class="select-field">
|
||||
<option value="" label="--- bitte wählen ---" />
|
||||
<option th:each="f : ${funders}" th:label="${f.name}" th:value="${f.pkFunder}" />
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-2"></div>
|
||||
<div class="col-5">
|
||||
<input type="submit" class="btn btn-primary" value="Speichern">
|
||||
</div>
|
||||
<div class="col-5" th:if="${bean.pkProject}">
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Bestätigung für Löschvorgang</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">Bist du sicher, dass du dieses Element löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<a th:href="@{/projectmanagement/{p}/delete(p=${bean.pkProject})}" class="btn btn-danger">Löschen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<head>
|
||||
<title>Projektmanagement</title>
|
||||
</head>
|
||||
<body>
|
||||
<font layout:fragment="title">Workpackage</font>
|
||||
<ul layout:fragment="menu">
|
||||
<li class="nav-item" sec:authorize="hasRole('timetrack_user')">
|
||||
<a class="nav-link btn btn-secondary btn-white-text" th:href="@{/projectmanagement}">abbrechen</a>
|
||||
</li>
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="container">
|
||||
<div class="alert alert-danger" th:if="${error}" th:text="${error}"></div>
|
||||
<form th:action="@{/projectmanagement/project/{p}/upsert(p=${bean.fkProject})}" method="post" th:object="${bean}">
|
||||
<div class="container m-2">
|
||||
<div class="row g-2">
|
||||
<div class="col-2" th:if="${bean.pkWorkpackage}">ID</div>
|
||||
<div class="col-10" th:if="${bean.pkWorkpackage}">
|
||||
<input type="text" class="form-control" th:field="*{pkWorkpackage}" readonly="readonly" />
|
||||
</div>
|
||||
<div class="col-2">Name</div>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control" th:field="*{name}" />
|
||||
</div>
|
||||
<div class="col-2">Beschreibung</div>
|
||||
<div class="col-10">
|
||||
<textarea class="form-control" th:field="*{description}"></textarea>
|
||||
</div>
|
||||
<div class="col-2">Milestone-URL</div>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control" th:field="*{milestoneUrl}" />
|
||||
</div>
|
||||
<div class="col-2">Auftragsdokument</div>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control" th:field="*{contractUrl}" />
|
||||
</div>
|
||||
<div class="col-2">geplante Fertigstellung</div>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control" th:field="*{plannedDuedate}" />
|
||||
</div>
|
||||
<div class="col-2"></div>
|
||||
<div class="col-5">
|
||||
<input type="submit" class="btn btn-primary" value="Speichern">
|
||||
</div>
|
||||
<div class="col-5" th:if="${bean.pkWorkpackage}">
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Bestätigung für Löschvorgang</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||
</div>
|
||||
<div class="modal-body">Bist du sicher, dass du dieses Element löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<a th:href="@{/projectmanagement/workpackage/{p}/delete(p=${bean.pkWorkpackage})}" class="btn btn-danger">Löschen</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,46 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<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}">
|
||||
<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}">
|
||||
|
||||
<head>
|
||||
<title>Timetrack</title>
|
||||
<title>Timetrack</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul layout:fragment="menu">
|
||||
</ul>
|
||||
<main layout:fragment="content">
|
||||
<div class="card" style="width: 312px; margin: 24px">
|
||||
<div class="card-header"><a class="btn btn-seondary btn-bordered btn-secondaryhover" style="width: 100%"
|
||||
th:href="@{/done/list}">heutige Arbeitszeiten</a></div>
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
<div class="card dashboardcard">
|
||||
<div class="card-header">
|
||||
<a class="btn btn-seondary btn-bordered btn-secondaryhover" style="width: 100%" th:href="@{/done/list}">heutige Arbeitszeiten</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-8"><span class="spanlabel">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-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-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-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-4"><span class="emphred round-border border-frame" th:text="${sum.overdue}"></span></div>
|
||||
<div class="col-8">
|
||||
<span class="spanlabel">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-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-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-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-4">
|
||||
<span class="emphred round-border border-frame" th:text="${sum.overdue}"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span th:if="${sum.getBillingTime('WP2') != '0,0 h'}"><span class="billing WP2">WP2</span><span
|
||||
th:text="${sum.getBillingTime('WP2')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('WP4') != '0,0 h'}"><span class="billing WP4">WP4</span><span
|
||||
th:text="${sum.getBillingTime('WP4')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('WP5') != '0,0 h'}"><span class="billing WP5">WP5</span><span
|
||||
th:text="${sum.getBillingTime('WP5')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('TA3') != '0,0 h'}"><span class="billing TA3">TA3</span><span
|
||||
th:text="${sum.getBillingTime('TA3')}" class="distfat"></span></span> <span class="billing">X</span><span
|
||||
<span th:if="${sum.getBillingTime('WP2') != '0,0 h'}"><span class="billing WP2">WP2</span><span th:text="${sum.getBillingTime('WP2')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('WP4') != '0,0 h'}"><span class="billing WP4">WP4</span><span th:text="${sum.getBillingTime('WP4')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('WP5') != '0,0 h'}"><span class="billing WP5">WP5</span><span th:text="${sum.getBillingTime('WP5')}" class="distfat"></span></span> <span
|
||||
th:if="${sum.getBillingTime('TA3') != '0,0 h'}"><span class="billing TA3">TA3</span><span th:text="${sum.getBillingTime('TA3')}" class="distfat"></span></span> <span class="billing">X</span><span
|
||||
th:text="${sum.getBillingTime(null)}" class="distfat"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user