more details

This commit is contained in:
Jörg Henke
2026-01-19 19:21:00 +01:00
parent 2cf5a44cf5
commit 62060311e9
5 changed files with 156 additions and 86 deletions

View File

@@ -30,6 +30,8 @@ public class AppController extends CommonController {
@RolesAllowed("timetrack_user") @RolesAllowed("timetrack_user")
@GetMapping("/projectmanagement/workpackage/{pkWorkpackage}/apps") @GetMapping("/projectmanagement/workpackage/{pkWorkpackage}/apps")
public String getAppsOfWorkpackage(@PathVariable("pkWorkpackage") Integer pkWorkpackage, final Model model) { public String getAppsOfWorkpackage(@PathVariable("pkWorkpackage") Integer pkWorkpackage, final Model model) {
model.addAttribute("bean", service.getWorkpackage(pkWorkpackage));
model.addAttribute("project", service.getProjectOfWorkpackage(pkWorkpackage));
model.addAttribute("apps", service.getAppsOf(pkWorkpackage)); model.addAttribute("apps", service.getAppsOf(pkWorkpackage));
return "/projectmanagement/app/list"; return "/projectmanagement/app/list";
} }

View File

@@ -1,5 +1,6 @@
package de.jottyfan.timetrack.modules.projectmanagement; package de.jottyfan.timetrack.modules.projectmanagement;
import static de.jottyfan.timetrack.db.project.Tables.T_PROJECT;
import static de.jottyfan.timetrack.db.project.Tables.T_APP; import static de.jottyfan.timetrack.db.project.Tables.T_APP;
import static de.jottyfan.timetrack.db.project.Tables.T_BUNDLE; import static de.jottyfan.timetrack.db.project.Tables.T_BUNDLE;
import static de.jottyfan.timetrack.db.project.Tables.T_WORKPACKAGE; import static de.jottyfan.timetrack.db.project.Tables.T_WORKPACKAGE;
@@ -25,6 +26,7 @@ import org.springframework.stereotype.Repository;
import de.jottyfan.timetrack.db.project.tables.records.TBundleRecord; import de.jottyfan.timetrack.db.project.tables.records.TBundleRecord;
import de.jottyfan.timetrack.db.project.tables.records.TWorkpackageAppRecord; import de.jottyfan.timetrack.db.project.tables.records.TWorkpackageAppRecord;
import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean; import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.ProjectBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageAppBean; import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageAppBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean; import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
@@ -34,7 +36,7 @@ import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
*/ */
@Repository @Repository
public class AppRepository { public class AppRepository {
@Autowired @Autowired
private DSLContext jooq; private DSLContext jooq;
@@ -109,7 +111,8 @@ public class AppRepository {
* @return the list of workpackage app linkages * @return the list of workpackage app linkages
*/ */
public List<WorkpackageAppBean> getWorkpackageApps(Integer pkApp) { public List<WorkpackageAppBean> getWorkpackageApps(Integer pkApp) {
return jooq.selectFrom(T_WORKPACKAGE_APP).where(T_WORKPACKAGE_APP.FK_APP.eq(pkApp)).fetchInto(WorkpackageAppBean.class); return jooq.selectFrom(T_WORKPACKAGE_APP).where(T_WORKPACKAGE_APP.FK_APP.eq(pkApp))
.fetchInto(WorkpackageAppBean.class);
} }
/** /**
@@ -156,4 +159,37 @@ public class AppRepository {
jooq.selectFrom(T_BUNDLE).fetchInto(TBundleRecord.class).forEach(b -> map.put(b.getPkBundle(), b)); jooq.selectFrom(T_BUNDLE).fetchInto(TBundleRecord.class).forEach(b -> map.put(b.getPkBundle(), b));
return map; return map;
} }
/**
* get the workpackage
*
* @param pkWorkpackage the ID of the workpackage
* @return the workpackage if found or null
*/
public WorkpackageBean getWorkpackage(Integer pkWorkpackage) {
return jooq
// @formatter:off
.selectFrom(T_WORKPACKAGE)
.where(T_WORKPACKAGE.PK_WORKPACKAGE.eq(pkWorkpackage))
.fetchOneInto(WorkpackageBean.class);
// @formatter:on
}
/**
* get the project of the workpackage
*
* @param pkWorkpackage the ID of the workpackage
* @return the project if found or null
*/
public ProjectBean getProjectOfWorkpackage(Integer pkWorkpackage) {
return jooq
// @formatter:off
.select(T_PROJECT.NAME,
T_PROJECT.DESCRIPTION)
.from(T_WORKPACKAGE)
.leftJoin(T_PROJECT).on(T_PROJECT.PK_PROJECT.eq(T_WORKPACKAGE.FK_PROJECT))
.where(T_WORKPACKAGE.PK_WORKPACKAGE.eq(pkWorkpackage))
.fetchOneInto(ProjectBean.class);
// @formatter:on
}
} }

View File

@@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
import de.jottyfan.timetrack.db.project.tables.records.TBundleRecord; import de.jottyfan.timetrack.db.project.tables.records.TBundleRecord;
import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean; import de.jottyfan.timetrack.modules.projectmanagement.model.AppBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.ProjectBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageAppBean; import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageAppBean;
import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean; import de.jottyfan.timetrack.modules.projectmanagement.model.WorkpackageBean;
@@ -44,5 +45,12 @@ public class AppService {
public Map<Integer, TBundleRecord> getBundleMap() { public Map<Integer, TBundleRecord> getBundleMap() {
return repository.getBundleMap(); return repository.getBundleMap();
} }
public WorkpackageBean getWorkpackage(Integer pkWorkpackage) {
return repository.getWorkpackage(pkWorkpackage);
}
public ProjectBean getProjectOfWorkpackage(Integer pkWorkpackage) {
return repository.getProjectOfWorkpackage(pkWorkpackage);
}
} }

View File

@@ -4,60 +4,60 @@
<title>Projektmanagement</title> <title>Projektmanagement</title>
</head> </head>
<body> <body>
<font layout:fragment="title">Projekt</font> <font layout:fragment="title">Projekt</font>
<ul layout:fragment="menu"> <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> <li class="nav-item" sec:authorize="hasRole('timetrack_user')">
</ul> <a class="nav-link btn btn-secondary btn-white-text" th:href="@{/projectmanagement}">zur Projektübersicht</a>
<main layout:fragment="content"> </li>
<div class="container"> </ul>
<div class="row"> <main layout:fragment="content">
<div class="col-2">Name</div> <div class="card">
<div class="col-10 fw-bold" th:text="${app.name}"></div> <div class="card-header" th:text="${app.name}"></div>
<div class="col-2">Beschreibung</div> <div class="card-body">
<div class="col-10" th:text="${app.description}"></div> <div class="container">
<div class="col-2">Basisfunktion</div> <div class="row">
<div class="col-10" th:text="${app.basicFunctionality}"></div> <div class="col-2">Beschreibung</div>
<div class="col-2">URL der Entwicklung</div> <div class="col-10" th:text="${app.description}"></div>
<div class="col-10"><a th:href="${app.repositoryUrl}" target="_blank" th:text="${app.repositoryUrl}"></a></div> <div class="col-2">Basisfunktion</div>
<div class="col-2" th:if="${app.fkReplacedByApp}">Ersetzt durch andere App</div> <div class="col-10" th:text="${app.basicFunctionality}"></div>
<div class="col-10" th:if="${app.fkReplacedByApp}"> <div class="col-2">URL der Entwicklung</div>
<a th:href="@{/projectmanagement/app/{id}/assign(id=${app.fkReplacedByApp})}" th:text="${app.fkReplacedByApp}"></a> <div class="col-10">
</div> <a th:href="${app.repositoryUrl}" target="_blank" th:text="${app.repositoryUrl}"></a>
<div class="col-2">Bundle</div> </div>
<div class="col-10"> <div class="col-2" th:if="${app.fkReplacedByApp}">Ersetzt durch andere App</div>
<th:block th:with="b=${bundleMap.get(app.fkBundle)}"> <div class="col-10" th:if="${app.fkReplacedByApp}">
<div th:text="${b.name}"></div> <a th:href="@{/projectmanagement/app/{id}/assign(id=${app.fkReplacedByApp})}" th:text="${app.fkReplacedByApp}"></a>
<div th:text="${b.description}"></div> </div>
</th:block> <div class="col-2">Bundle</div>
</div> <div class="col-10">
</div> <th:block th:with="b=${bundleMap.get(app.fkBundle)}">
<div class="row"> <div th:text="${b.name}"></div>
<div class="col-12"> <div th:text="${b.description}"></div>
<table class="table table-striped"> </th:block>
<thead> </div>
<tr> </div>
<th>Name</th> </div>
<th>Beschreibung</th> </div>
<th>Zuordnung</th> <div class="card-footer">
</tr> <table class="table table-striped">
</thead> <thead>
<tbody> <tr>
<tr th:each="p : ${workpackages}"> <th>Name</th>
<td> <th>Beschreibung</th>
<input type="checkbox" th:checked="${p.isIn(linked)}" /> <a th:href="@{/projectmanagement/workpackage/{w}/apps(w=${p.pkWorkpackage})}" th:text="${p.name}"></a> <th>Zuordnung</th>
</td> </tr>
<td th:text="${p.description}"></td> </thead>
<td> <tbody>
<a th:href="@{/projectmanagement/workpackage/{w}/app/{a}/toggle(w=${p.pkWorkpackage},a=${app.pkApp})}" class="btn btn-outline-secondary"> <tr th:each="p : ${workpackages}">
<span th:if="${p.isIn(linked)}">rausschmeißen</span> <td><input type="checkbox" th:checked="${p.isIn(linked)}" /> <a th:href="@{/projectmanagement/workpackage/{w}/apps(w=${p.pkWorkpackage})}" th:text="${p.name}"></a></td>
<span th:unless="${p.isIn(linked)}">zuweisen</span> <td th:text="${p.description}"></td>
</a> <td><a th:href="@{/projectmanagement/workpackage/{w}/app/{a}/toggle(w=${p.pkWorkpackage},a=${app.pkApp})}" class="btn btn-outline-secondary">
</td> <span th:if="${p.isIn(linked)}">rausschmeißen</span> <span th:unless="${p.isIn(linked)}">zuweisen</span>
</tr> </a></td>
</table> </tr>
</div> </table>
</div> </div>
</div> </div>
</main> </main>
</body> </body>
</html> </html>

View File

@@ -11,35 +11,59 @@
</li> </li>
</ul> </ul>
<main layout:fragment="content"> <main layout:fragment="content">
<div class="p-2"> <div class="card">
<table id="table" class="table table-striped"> <div class="card-header" th:if="${bean}">
<thead> <span th:text="${project.name}" th:if="${project}"></span>:
<tr> <span th:text="${bean.name}"></span>
<th>Name</th> </div>
<th>Beschreibung</th> <div class="card-body" th:if="${bean}">
<th>URL</th> <div class="container">
<th></th> <div class="row">
</tr> <div class="col-sm-12 col-md-2" th:if="${project}">Projektdetails</div>
</thead> <div class="col-sm-12 col-md-10" th:if="${project}" th:text="${project.description}"></div>
<tbody> <div class="col-sm-12 col-md-2">Beschreibung</div>
<tr th:each="b : ${apps}"> <div class="col-sm-12 col-md-10" th:text="${bean.description}"></div>
<td th:text="${b.name}"></td> <div class="col-sm-12 col-md-2">Milestone</div>
<td th:text="${b.description}"></td> <div class="col-sm-12 col-md-10">
<td><a th:href="${b.repositoryUrl}" target="_blank">gitlab</a></td> <a th:href="${bean.milestoneUrl}" target="_blank" th:text="${bean.milestoneUrl}" th:if="${bean.milestoneUrl}"></a>
<td><a th:href="@{/projectmanagement/app/{id}/assign(id=${b.pkApp})}">zuordnen</a><span th:text="' (' + ${b.workpackagesString} + ')'" th:if="${b.workpackagesString}"></span></td> </div>
</tr> <div class="col-sm-12 col-md-2">Vertrag</div>
</tbody> <div class="col-sm-12 col-md-10" th:text="${bean.contractUrl}"></div>
</table> <div class="col-sm-12 col-md-2">Zieldatum</div>
<script type="text/javascript"> <div class="col-sm-12 col-md-10" th:text="${bean.plannedDuedate}"></div>
$(document).ready(function() { </div>
var localeUrl = '[[@{/js/dataTables/de.json}]]'; </div>
$("#table").DataTable({ </div>
"language" : { <div class="card-footer">
"url" : localeUrl <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><span th:text="' (' + ${b.workpackagesString} + ')'" th:if="${b.workpackagesString}"></span></td>
</tr>
</tbody>
</table>
<script type="text/javascript">
$(document).ready(function() {
var localeUrl = '[[@{/js/dataTables/de.json}]]';
$("#table").DataTable({
"language" : {
"url" : localeUrl
}
});
}); });
}); </script>
</script> </div>
</div> </div>
</main> </main>
</body> </body>