adds versions overview

Thu, 08 Oct 2020 20:16:47 +0200

author
Mike Becker <universe@uap-core.de>
date
Thu, 08 Oct 2020 20:16:47 +0200
changeset 109
2e0669e814ff
parent 108
6657dad897ea
child 110
9d0be0b1580f

adds versions overview

includes major refactoring of side menu generation

src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/Constants.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/MenuEntry.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java file | annotate | diff | comparison | revisions
src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java file | annotate | diff | comparison | revisions
src/main/resources/localization/projects.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/projects_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-details.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-navmenu.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/projects.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/version-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/versions.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/menu-entry.jsp file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
--- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java	Thu Oct 08 20:16:47 2020 +0200
@@ -230,12 +230,12 @@
     /**
      * Sets the navigation menu.
      *
-     * @param req             the servlet request object
-     * @param navigationItems the menu entries for the navigation menu
+     * @param req     the servlet request object
+     * @param jspName the name of the menu's jsp file
      * @see Constants#REQ_ATTR_NAVIGATION
      */
-    protected void setNavigationMenu(HttpServletRequest req, List<MenuEntry> navigationItems) {
-        req.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationItems);
+    protected void setNavigationMenu(HttpServletRequest req, String jspName) {
+        req.setAttribute(Constants.REQ_ATTR_NAVIGATION, Functions.jspPath(jspName));
     }
 
     /**
--- a/src/main/java/de/uapcore/lightpit/Constants.java	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/Constants.java	Thu Oct 08 20:16:47 2020 +0200
@@ -72,7 +72,7 @@
     public static final String REQ_ATTR_MENU = fqn(AbstractLightPITServlet.class, "mainMenu");
 
     /**
-     * Key for the request attribute containing the navigation menu.
+     * Key for the request attribute containing the optional navigation menu jsp.
      */
     public static final String REQ_ATTR_NAVIGATION = fqn(AbstractLightPITServlet.class, "navMenu");
 
--- a/src/main/java/de/uapcore/lightpit/MenuEntry.java	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/MenuEntry.java	Thu Oct 08 20:16:47 2020 +0200
@@ -50,33 +50,12 @@
      */
     private boolean active = false;
 
-    /**
-     * The menu level.
-     */
-    private int level;
-
     public MenuEntry(ResourceKey resourceKey, String pathName) {
-        this(0, resourceKey, pathName);
-    }
-
-    public MenuEntry(String text, String pathName) {
-        this(0, text, pathName);
-    }
-
-    public MenuEntry(int level, ResourceKey resourceKey, String pathName) {
-        this.level = level;
         this.text = null;
         this.resourceKey = resourceKey;
         this.pathName = pathName;
     }
 
-    public MenuEntry(int level, String text, String pathName) {
-        this.level = level;
-        this.text = text;
-        this.resourceKey = null;
-        this.pathName = pathName;
-    }
-
     public ResourceKey getResourceKey() {
         return resourceKey;
     }
@@ -94,14 +73,6 @@
     }
 
     public void setActive(boolean active) {
-        this.active = true;
-    }
-
-    public int getLevel() {
-        return level;
-    }
-
-    public void setLevel(int level) {
-        this.level = level;
+        this.active = active;
     }
 }
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java	Thu Oct 08 20:16:47 2020 +0200
@@ -42,7 +42,6 @@
 import java.io.IOException;
 import java.sql.Date;
 import java.sql.SQLException;
-import java.util.ArrayList;
 import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -68,73 +67,6 @@
         return "localization.projects";
     }
 
-    private String queryParams(Project p, Version v) {
-        return String.format("pid=%d&vid=%d",
-                p == null ? -1 : p.getId(),
-                v == null ? -1 : v.getId()
-        );
-    }
-
-    /**
-     * Creates the navigation menu.
-     *
-     * @param req the servlet request
-     * @param viewModel the current view model
-     */
-    private void setNavigationMenu(HttpServletRequest req, ProjectView viewModel) {
-        final Project selectedProject = Optional.ofNullable(viewModel.getProjectInfo()).map(ProjectInfo::getProject).orElse(null);
-
-        final var navigation = new ArrayList<MenuEntry>();
-
-        for (ProjectInfo plistInfo : viewModel.getProjectList()) {
-            final var proj = plistInfo.getProject();
-            final var projEntry = new MenuEntry(
-                    proj.getName(),
-                    "projects/view?" + queryParams(proj, null)
-            );
-            navigation.add(projEntry);
-            if (proj.equals(selectedProject)) {
-                final var projInfo = viewModel.getProjectInfo();
-                projEntry.setActive(true);
-
-                // ****************
-                // Versions Section
-                // ****************
-                {
-                    final var entry = new MenuEntry(1,
-                            new ResourceKey(getResourceBundleName(), "menu.versions"),
-                            "projects/view?" + queryParams(proj, null)
-                    );
-                    navigation.add(entry);
-                }
-
-                final var level2 = new ArrayList<MenuEntry>();
-                {
-                    final var entry = new MenuEntry(
-                            new ResourceKey(getResourceBundleName(), "filter.none"),
-                            "projects/view?" + queryParams(proj, null)
-                    );
-                    if (viewModel.getVersionFilter() == null) entry.setActive(true);
-                    level2.add(entry);
-                }
-
-                for (Version version : projInfo.getVersions()) {
-                    final var entry = new MenuEntry(
-                            version.getName(),
-                            "projects/view?" + queryParams(proj, version)
-                    );
-                    if (version.equals(viewModel.getVersionFilter())) entry.setActive(true);
-                    level2.add(entry);
-                }
-
-                level2.forEach(e -> e.setLevel(2));
-                navigation.addAll(level2);
-            }
-        }
-
-        setNavigationMenu(req, navigation);
-    }
-
     private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
         final var session = req.getSession();
         final var idParam = getParameter(req, Integer.class, param);
@@ -179,7 +111,7 @@
         setViewModel(req, viewModel);
         setContentPage(req, name);
         setStylesheet(req, "projects");
-        setNavigationMenu(req, viewModel);
+        setNavigationMenu(req, "project-navmenu");
         return ResponseType.HTML;
     }
 
@@ -232,7 +164,7 @@
 
             dao.getProjectDao().saveOrUpdate(project);
 
-            setRedirectLocation(req, "./projects/view?pid="+project.getId());
+            setRedirectLocation(req, "./projects/versions?pid="+project.getId());
             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
             LOG.debug("Successfully updated project {}", project.getName());
 
@@ -270,6 +202,26 @@
         return forwardView(req, viewModel, "project-details");
     }
 
+    @RequestMapping(requestPath = "versions", method = HttpMethod.GET)
+    public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException {
+        final var viewModel = new VersionsView();
+        populate(viewModel, req, dao);
+        viewModel.setVersionFilter(null);
+
+        final var projectInfo = viewModel.getProjectInfo();
+        if (projectInfo == null) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
+            return ResponseType.NONE;
+        }
+
+        final var issueDao = dao.getIssueDao();
+        final var issues = issueDao.list(projectInfo.getProject());
+        for (var issue : issues) issueDao.joinVersionInformation(issue);
+        viewModel.update(projectInfo.getVersions(), issues);
+
+        return forwardView(req, viewModel, "versions");
+    }
+
     @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
     public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
         final var viewModel = new VersionEditView();
@@ -297,7 +249,7 @@
             dao.getVersionDao().saveOrUpdate(version);
 
             // specifying the pid parameter will purposely reset the session selected version!
-            setRedirectLocation(req, "./projects/view?pid=" + version.getProject().getId());
+            setRedirectLocation(req, "./projects/versions?pid=" + version.getProject().getId());
             setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
         } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
             LOG.warn("Form validation failure: {}", ex.getMessage());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java	Thu Oct 08 20:16:47 2020 +0200
@@ -0,0 +1,25 @@
+package de.uapcore.lightpit.viewmodel;
+
+import de.uapcore.lightpit.entities.Issue;
+import de.uapcore.lightpit.entities.Version;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class VersionsView extends ProjectView {
+
+    private List<VersionInfo> versionInfo = new ArrayList<>();
+
+    public void update(List<Version> versions, List<Issue> issues) {
+        versionInfo.clear();
+        for (var version : versions) {
+            final var info = new VersionInfo(version);
+            info.collectIssues(issues);
+            versionInfo.add(info);
+        }
+    }
+
+    public List<VersionInfo> getVersionInfo() {
+        return versionInfo;
+    }
+}
--- a/src/main/resources/localization/projects.properties	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/resources/localization/projects.properties	Thu Oct 08 20:16:47 2020 +0200
@@ -31,10 +31,8 @@
 
 no-projects=Welcome to LightPIT. Start off by creating a new project!
 
-filter.all=all
-filter.none=none
-
 menu.versions=Versions
+menu.versions.unassigned=unassigned
 menu.issues=Issues
 
 name=Name
@@ -49,7 +47,6 @@
 issues.open=Open
 issues.active=In Progress
 issues.done=Done
-issues.total=Total
 issues.reported=Reported Issues
 issues.resolved=Assigned Issues
 
@@ -62,8 +59,6 @@
 placeholder.null-owner=Unassigned
 placeholder.null-assignee=Unassigned
 
-version.open=open
-version.label=Version
 version.status.Future=Future
 version.status.Unreleased=Unreleased
 version.status.Released=Released
--- a/src/main/resources/localization/projects_de.properties	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/resources/localization/projects_de.properties	Thu Oct 08 20:16:47 2020 +0200
@@ -31,10 +31,8 @@
 
 no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes!
 
-filter.all=alle
-filter.none=keine
-
 menu.versions=Versionen
+menu.versions.unassigned=Nicht Zugewiesen
 menu.issues=Vorg\u00e4nge
 
 name=Name
@@ -51,7 +49,6 @@
 issues.done=Erledigt
 issues.reported=Er\u00f6ffnete Vorg\u00e4nge
 issues.resolved=Enthaltene Vorg\u00e4nge
-issues.total=Summe
 
 version.project=Projekt
 version.name=Version
@@ -62,8 +59,6 @@
 placeholder.null-owner=Nicht Zugewiesen
 placeholder.null-assignee=Niemandem
 
-version.open=ansehen
-version.label=Version
 version.status.Future=Geplant
 version.status.Unreleased=Unver\u00f6ffentlicht
 version.status.Released=Ver\u00f6ffentlicht
--- a/src/main/webapp/WEB-INF/jsp/issues.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/issues.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -35,7 +35,7 @@
 
 <c:if test="${not empty version}">
     <h2>
-        <fmt:message key="version.label" /> <c:out value="${version.name}" /> - <fmt:message key="version.status.${version.status}"/>
+        <fmt:message key="version.name" /> <c:out value="${version.name}" /> - <fmt:message key="version.status.${version.status}"/>
         <a href="./projects/versions/edit?vid=${version.id}">&#x270e;</a>
     </h2>
 </c:if>
--- a/src/main/webapp/WEB-INF/jsp/project-details.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -49,7 +49,7 @@
 <c:if test="${not empty viewmodel.versionFilter}">
     <c:set var="versionInfo" value="${viewmodel.projectDetails.versionInfo}"/>
     <h2>
-        <fmt:message key="version.label" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/>
+        <fmt:message key="version.name" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/>
     </h2>
 
     <h3><fmt:message key="issues.resolved"/> </h3>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -0,0 +1,60 @@
+<%--
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+Copyright 2020 Mike Becker. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--%>
+<%@page pageEncoding="UTF-8" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.ProjectView" scope="request"/>
+
+<c:forEach var="projectInfo" items="${viewmodel.projectList}">
+    <c:set var="isActive" value="${viewmodel.projectInfo.project eq projectInfo.project}" />
+    <div class="menuEntry level-0" <c:if test="${isActive}">data-active</c:if> >
+        <a href="projects/versions?pid=${projectInfo.project.id}">
+            <c:out value="${projectInfo.project.name}"/>
+        </a>
+    </div>
+    <c:if test="${isActive}">
+        <div class="menuEntry level-1">
+            <a href="projects/versions?pid=${projectInfo.project.id}">
+                <fmt:message key="menu.versions"/>
+            </a>
+        </div>
+        <div class="menuEntry level-2">
+            <a href="projects/view?pid=${projectInfo.project.id}&vid=-1">
+                <fmt:message key="menu.versions.unassigned" />
+            </a>
+        </div>
+        <c:forEach var="version" items="${viewmodel.projectInfo.versions}">
+            <c:set var="isVersionActive" value="${viewmodel.versionFilter eq version}" />
+            <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> >
+                <a href="projects/view?pid=${projectInfo.project.id}&vid=${version.id}">
+                    <c:out value="${version.name}"/>
+                </a>
+            </div>
+        </c:forEach>
+    </c:if>
+</c:forEach>
--- a/src/main/webapp/WEB-INF/jsp/projects.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/projects.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -69,7 +69,7 @@
             <c:set var="project" scope="page" value="${projectInfo.project}"/>
             <tr class="nowrap">
                 <td style="width: 2em;"><a href="./projects/edit?pid=${project.id}">&#x270e;</a></td>
-                <td><a href="./projects/view?pid=${project.id}"><c:out value="${project.name}"/></a>
+                <td><a href="./projects/versions?pid=${project.id}"><c:out value="${project.name}"/></a>
                 </td>
                 <td>
                     <c:if test="${not empty project.repoUrl}">
--- a/src/main/webapp/WEB-INF/jsp/site.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -39,7 +39,7 @@
 <%-- Define an alias for the main menu --%>
 <c:set scope="page" var="mainMenu" value="${requestScope[Constants.REQ_ATTR_MENU]}"/>
 
-<%-- Define an alias for the main menu --%>
+<%-- Define an alias for the navigation menu --%>
 <c:set scope="page" var="navMenu" value="${requestScope[Constants.REQ_ATTR_NAVIGATION]}"/>
 
 <%-- Define an alias for the content page name --%>
@@ -86,9 +86,7 @@
 <div>
     <c:if test="${not empty navMenu}">
         <div id="sideMenu">
-            <c:forEach var="menu" items="${navMenu}">
-                <%@include file="../jspf/menu-entry.jsp" %>
-            </c:forEach>
+            <c:import url="${navMenu}"/>
         </div>
     </c:if>
     <div id="content-area" <c:if test="${not empty navMenu}">class="sidebar-spacing"</c:if>>
--- a/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -72,7 +72,7 @@
         <tr>
             <td colspan="2">
                 <input type="hidden" name="id" value="${version.id}"/>
-                <a href="./projects/view?pid=${version.project.id}" class="button">
+                <a href="./projects/versions?pid=${version.project.id}" class="button">
                     <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/>
                 </a>
                 <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/WEB-INF/jsp/versions.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -0,0 +1,101 @@
+<%--
+DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+
+Copyright 2020 Mike Becker. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--%>
+<%@page pageEncoding="UTF-8" %>
+<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+
+<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.VersionsView" scope="request" />
+
+<c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/>
+<%@include file="../jspf/project-header.jsp"%>
+
+<div id="tool-area">
+    <a href="./projects/versions/edit" class="button"><fmt:message key="button.version.create"/></a>
+    <a href="./projects/issues/edit?pid=${project.id}" class="button"><fmt:message key="button.issue.create"/></a>
+</div>
+
+<h2><fmt:message key="progress" /></h2>
+
+<c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" />
+<%@include file="../jspf/issue-summary.jsp"%>
+
+<table id="version-list" class="datatable medskip fullwidth">
+    <colgroup>
+        <col>
+        <col width="20%">
+        <col width="20%">
+        <col width="10%">
+        <col width="10%">
+        <col width="10%">
+        <col width="10%">
+        <col width="10%">
+        <col width="10%">
+    </colgroup>
+    <thead>
+    <tr>
+        <th colspan="3"></th>
+        <th colspan="3" class="hcenter">
+            <fmt:message key="issues.resolved"/>
+        </th>
+        <th colspan="3" class="hcenter">
+            <fmt:message key="issues.reported"/>
+        </th>
+    </tr>
+    <tr>
+        <th></th>
+        <th><fmt:message key="version.name"/></th>
+        <th><fmt:message key="version.status" /></th>
+        <th class="hcenter"><fmt:message key="issues.open" /></th>
+        <th class="hcenter"><fmt:message key="issues.active" /></th>
+        <th class="hcenter"><fmt:message key="issues.done" /></th>
+        <th class="hcenter"><fmt:message key="issues.open" /></th>
+        <th class="hcenter"><fmt:message key="issues.active" /></th>
+        <th class="hcenter"><fmt:message key="issues.done" /></th>
+    </tr>
+    </thead>
+    <tbody>
+        <c:forEach var="versionInfo" items="${viewmodel.versionInfo}" >
+        <tr>
+            <td style="width: 2em;"><a href="./projects/versions/edit?vid=${versionInfo.version.id}">&#x270e;</a></td>
+            <td>
+                <a href="projects/view?pid=${viewmodel.projectInfo.project.id}&vid=${versionInfo.version.id}">
+                    <c:out value="${versionInfo.version.name}"/>
+                </a>
+            </td>
+            <td>
+                <fmt:message key="version.status.${versionInfo.version.status}"/>
+            </td>
+            <td class="hright">${versionInfo.resolvedTotal.open}</td>
+            <td class="hright">${versionInfo.resolvedTotal.active}</td>
+            <td class="hright">${versionInfo.resolvedTotal.done}</td>
+            <td class="hright">${versionInfo.reportedTotal.open}</td>
+            <td class="hright">${versionInfo.reportedTotal.active}</td>
+            <td class="hright">${versionInfo.reportedTotal.done}</td>
+        </tr>
+        </c:forEach>
+    </tbody>
+</table>
\ No newline at end of file
--- a/src/main/webapp/WEB-INF/jspf/menu-entry.jsp	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/WEB-INF/jspf/menu-entry.jsp	Thu Oct 08 20:16:47 2020 +0200
@@ -1,4 +1,4 @@
-<div class="menuEntry level-${menu.level}"
+<div class="menuEntry"
      <c:if test="${menu.active}">data-active</c:if> >
     <a href="${menu.pathName}">
         <c:if test="${empty menu.resourceKey}">
--- a/src/main/webapp/lightpit.css	Thu Oct 08 18:28:16 2020 +0200
+++ b/src/main/webapp/lightpit.css	Thu Oct 08 20:16:47 2020 +0200
@@ -67,6 +67,7 @@
     height: 100%;
     width: 30ch;
     padding-top: 2.25rem;
+    color: #3060f8;
     border-image-source: linear-gradient(to bottom, #606060, rgba(60, 60, 60, .25));
     border-image-slice: 1;
     border-right-style: solid;

mercurial