feat(ui): integrate project and task pages with shell

This commit is contained in:
sechmachine
2026-08-15 01:35:30 +07:00
parent b1c6b170d0
commit 401f67671a
12 changed files with 297 additions and 136 deletions
File diff suppressed because one or more lines are too long
@@ -23,11 +23,10 @@
<svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#layout-dashboard}"></use></svg><span class="sidebar-label">Overview</span></a></li>
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/admin/accounts/new}" th:attr="aria-current=${activeNav == 'accounts'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#users}"></use></svg><span class="sidebar-label">Accounts</span></a></li>
<li sec:authorize="hasRole('ADMIN')"><a class="nav-link" th:href="@{/attendance/calendar}" th:attr="aria-current=${activeNav == 'calendar'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#calendar-days}"></use></svg><span class="sidebar-label">Global calendar</span></a></li>
<li sec:authorize="hasRole('MENTOR')"><a class="nav-link" th:href="@{/projects}"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">Owned Projects</span></a></li>
<li sec:authorize="hasRole('MENTOR')"><a class="nav-link" th:href="@{/projects}" th:attr="aria-current=${activeNav == 'projects'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">Owned Projects</span></a></li>
<li sec:authorize="hasRole('MENTOR')"><a class="nav-link" th:href="@{/attendance}" th:attr="aria-current=${activeNav == 'attendance'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#clock}"></use></svg><span class="sidebar-label">Intern attendance</span></a></li>
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/attendance/me}" th:attr="aria-current=${activeNav == 'attendance'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#clock}"></use></svg><span class="sidebar-label">My attendance</span></a></li>
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/projects}"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">My Projects</span></a></li>
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/tasks}"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#list-check}"></use></svg><span class="sidebar-label">Assigned Tasks</span></a></li>
<li sec:authorize="hasRole('INTERN')"><a class="nav-link" th:href="@{/projects}" th:attr="aria-current=${activeNav == 'projects'} ? 'page' : null"><svg class="nav-icon" aria-hidden="true"><use th:href="@{/assets/icons.svg#folder-kanban}"></use></svg><span class="sidebar-label">My Projects</span></a></li>
</ul>
</nav>
<div class="sidebar-footer">
@@ -1,12 +1,27 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><title th:text="${project.name}">Project</title></head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle=${project.name},
section='Projects',
activeNav='projects',
primaryAction=~{::#primary-action},
content=~{::main})}">
<body>
<a id="primary-action" class="button button-primary" th:href="@{/projects/{id}/tasks(id=${project.id})}">View Tasks</a>
<main>
<h1 th:text="${project.name}">Project</h1>
<p th:text="${project.description}"></p>
<dl><dt>Status</dt><dd th:text="${project.status}"></dd><dt>Mentor</dt><dd th:text="${project.mentorName}"></dd><dt>Leader</dt><dd th:text="${project.leaderName}"></dd></dl>
<nav aria-label="Project sections"><a th:href="@{/projects/{id}/members(id=${project.id})}">Members</a> <a th:href="@{/projects/{id}/leadership(id=${project.id})}">Leadership</a></nav>
<p class="page-description" th:text="${project.description == null || #strings.isEmpty(project.description) ? 'No Project description.' : project.description}">Project description.</p>
<section class="panel metric-strip" aria-label="Project details">
<div class="metric"><div class="metric-label">Status</div><div class="metric-value"><span class="badge" th:classappend="${project.status == 'ACTIVE' ? ' badge-success' : ''}" th:text="${project.status}">PLANNED</span></div></div>
<div class="metric"><div class="metric-label">Mentor</div><div class="metric-value" th:text="${project.mentorName}">Mentor</div></div>
<div class="metric"><div class="metric-label">Leader</div><div class="metric-value" th:text="${project.leaderName}">Leader</div></div>
<div class="metric"><div class="metric-label">Project window</div><div class="metric-value" th:text="${#temporals.format(project.startDate, 'dd/MM/yyyy') + ' ' + #temporals.format(project.endDate, 'dd/MM/yyyy')}">15/08/2026 30/09/2026</div></div>
</section>
<nav class="tabs" aria-label="Project sections">
<a class="tab" th:href="@{/projects/{id}(id=${project.id})}" aria-current="page">Overview</a>
<a class="tab" th:href="@{/projects/{id}/members(id=${project.id})}">Members</a>
<a class="tab" th:href="@{/projects/{id}/leadership(id=${project.id})}">Leadership</a>
<a class="tab" th:href="@{/projects/{id}/tasks(id=${project.id})}">Tasks</a>
</nav>
</main>
</body>
</html>
+29 -13
View File
@@ -1,19 +1,35 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><title>Create Project</title></head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle='Create Project',
section='Projects',
activeNav='projects',
primaryAction=~{},
content=~{::main})}">
<body>
<main>
<h1>Create Project</h1>
<form method="post" th:action="@{/projects}" th:object="${projectForm}">
<label for="name">Name</label><input id="name" th:field="*{name}" required maxlength="160">
<p th:errors="*{name}"></p>
<label for="description">Description</label><textarea id="description" th:field="*{description}"></textarea>
<label for="startDate">Start date</label><input id="startDate" type="date" th:field="*{startDate}" required>
<label for="endDate">End date</label><input id="endDate" type="date" th:field="*{endDate}" required>
<label for="leader">Initial Leader user ID</label><input id="leader" type="number" min="1" th:field="*{initialLeaderUserId}" required>
<p th:errors="*{initialLeaderUserId}"></p>
<button type="submit">Create Project</button>
</form>
<p class="page-description">Define the initial Project window and Leader. Membership changes remain server-authorized.</p>
<form class="panel form-panel form-grid" method="post" th:action="@{/projects}" th:object="${projectForm}">
<div class="field">
<label class="field-label" for="name">Name</label>
<input class="control" id="name" th:field="*{name}" required maxlength="160" th:attr="aria-invalid=${#fields.hasErrors('name')}">
<p class="field-error" role="alert" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name error</p>
</div>
<div class="field">
<label class="field-label" for="description">Description</label>
<textarea class="control" id="description" rows="5" th:field="*{description}"></textarea>
</div>
<div class="form-grid form-grid-three">
<div class="field"><label class="field-label" for="startDate">Start date</label><input class="control" id="startDate" type="date" th:field="*{startDate}" required></div>
<div class="field"><label class="field-label" for="endDate">End date</label><input class="control" id="endDate" type="date" th:field="*{endDate}" required></div>
<div class="field">
<label class="field-label" for="leader">Initial Leader user ID</label>
<input class="control" id="leader" type="number" min="1" th:field="*{initialLeaderUserId}" required th:attr="aria-invalid=${#fields.hasErrors('initialLeaderUserId')}">
<p class="field-error" role="alert" th:if="${#fields.hasErrors('initialLeaderUserId')}" th:errors="*{initialLeaderUserId}">Leader error</p>
</div>
</div>
<div class="form-actions"><a class="button" th:href="@{/projects}">Cancel</a><button class="button button-primary" type="submit">Create Project</button></div>
</form>
</main>
</body>
</html>
@@ -1,13 +1,29 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><title>Project leadership</title></head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle=${project.name + ' leadership'},
section='Projects',
activeNav='projects',
primaryAction=~{},
content=~{::main})}">
<body>
<main>
<h1><span th:text="${project.name}">Project</span> leadership</h1>
<table><caption>Leadership history</caption><thead><tr><th scope="col">Leader</th><th scope="col">Started</th><th scope="col">Ended</th></tr></thead>
<tbody><tr th:each="term : ${leadership}"><td th:text="${term.leaderName}"></td><td th:text="${term.startedAt}"></td><td th:text="${term.endedAt}"></td></tr></tbody>
</table>
<form th:if="${project.canManage}" method="post" th:action="@{/projects/{id}/leadership(id=${project.id})}"><label for="leader">New Leader user ID</label><input id="leader" name="internUserId" type="number" min="1" required><button type="submit">Change Leader</button></form>
<nav class="tabs" aria-label="Project sections">
<a class="tab" th:href="@{/projects/{id}(id=${project.id})}">Overview</a>
<a class="tab" th:href="@{/projects/{id}/members(id=${project.id})}">Members</a>
<a class="tab" th:href="@{/projects/{id}/leadership(id=${project.id})}" aria-current="page">Leadership</a>
<a class="tab" th:href="@{/projects/{id}/tasks(id=${project.id})}">Tasks</a>
</nav>
<section class="panel form-panel" th:if="${#lists.isEmpty(leadership)}"><th:block th:replace="~{fragments/components :: empty('No leadership history', 'Leadership terms will appear here.')} "></th:block></section>
<section class="panel form-panel" th:unless="${#lists.isEmpty(leadership)}">
<div class="table-scroll"><table class="data-table"><caption class="sr-only">Leadership history</caption><thead><tr><th scope="col">Leader</th><th scope="col">Started</th><th scope="col">Ended</th></tr></thead>
<tbody><tr th:each="term : ${leadership}"><td th:text="${term.leaderName}">Leader</td><td th:text="${#temporals.format(term.startedAt, 'dd/MM/yyyy HH:mm')}">Started</td><td th:text="${term.endedAt == null ? 'Current' : #temporals.format(term.endedAt, 'dd/MM/yyyy HH:mm')}">Current</td></tr></tbody>
</table></div>
</section>
<form class="panel form-panel filter-form" th:if="${project.canManage}" method="post" th:action="@{/projects/{id}/leadership(id=${project.id})}">
<div class="field"><label class="field-label" for="leader">New Leader user ID</label><input class="control" id="leader" name="internUserId" type="number" min="1" required></div>
<span></span><button class="button button-primary" type="submit">Change Leader</button>
</form>
</main>
</body>
</html>
+26 -14
View File
@@ -1,20 +1,32 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><title>Projects</title></head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle='Projects',
section='Projects',
activeNav='projects',
primaryAction=~{::#primary-action},
content=~{::main})}">
<body>
<a id="primary-action" class="button button-primary" th:if="${canCreateProject}" th:href="@{/projects/new}">Create Project</a>
<main>
<h1>Projects</h1>
<a th:if="${canCreateProject}" href="/projects/new">Create Project</a>
<p th:if="${#lists.isEmpty(projects)}">No authorized Projects.</p>
<table th:unless="${#lists.isEmpty(projects)}">
<caption>Authorized Projects</caption>
<thead><tr><th scope="col">Name</th><th scope="col">Status</th><th scope="col">Dates</th></tr></thead>
<tbody><tr th:each="project : ${projects}">
<td><a th:href="@{/projects/{id}(id=${project.id})}" th:text="${project.name}">Project</a></td>
<td th:text="${project.status}">PLANNED</td>
<td><span th:text="${project.startDate}">Start</span> <span th:text="${project.endDate}">End</span></td>
</tr></tbody>
</table>
<p class="page-description">Projects visible to your current role and membership.</p>
<section class="panel" th:if="${#lists.isEmpty(projects)}">
<th:block th:replace="~{fragments/components :: empty('No authorized Projects', 'Projects you can access will appear here.')} "></th:block>
</section>
<section class="panel" th:unless="${#lists.isEmpty(projects)}">
<div class="table-scroll">
<table class="data-table">
<caption class="sr-only">Authorized Projects</caption>
<thead><tr><th scope="col">Name</th><th scope="col">Status</th><th scope="col">Start</th><th scope="col">End</th></tr></thead>
<tbody><tr th:each="project : ${projects}">
<td><a th:href="@{/projects/{id}(id=${project.id})}" th:text="${project.name}">Project</a></td>
<td><span class="badge" th:classappend="${project.status == 'ACTIVE' ? ' badge-success' : ''}" th:text="${project.status}">PLANNED</span></td>
<td th:text="${#temporals.format(project.startDate, 'dd/MM/yyyy')}">15/08/2026</td>
<td th:text="${#temporals.format(project.endDate, 'dd/MM/yyyy')}">30/09/2026</td>
</tr></tbody>
</table>
</div>
</section>
</main>
</body>
</html>
@@ -1,13 +1,29 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="utf-8"><title>Project members</title></head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle=${project.name + ' members'},
section='Projects',
activeNav='projects',
primaryAction=~{},
content=~{::main})}">
<body>
<main>
<h1><span th:text="${project.name}">Project</span> members</h1>
<table><caption>Membership history</caption><thead><tr><th scope="col">Intern</th><th scope="col">Joined</th><th scope="col">Left</th><th scope="col">Role</th></tr></thead>
<tbody><tr th:each="member : ${members}"><td th:text="${member.displayName}"></td><td th:text="${member.joinedAt}"></td><td th:text="${member.leftAt}"></td><td th:text="${member.currentLeader} ? 'Leader' : 'Member'"></td></tr></tbody>
</table>
<form th:if="${project.canManage}" method="post" th:action="@{/projects/{id}/members(id=${project.id})}"><label for="intern">Intern user ID</label><input id="intern" name="internUserId" type="number" min="1" required><button type="submit">Add member</button></form>
<nav class="tabs" aria-label="Project sections">
<a class="tab" th:href="@{/projects/{id}(id=${project.id})}">Overview</a>
<a class="tab" th:href="@{/projects/{id}/members(id=${project.id})}" aria-current="page">Members</a>
<a class="tab" th:href="@{/projects/{id}/leadership(id=${project.id})}">Leadership</a>
<a class="tab" th:href="@{/projects/{id}/tasks(id=${project.id})}">Tasks</a>
</nav>
<section class="panel form-panel" th:if="${#lists.isEmpty(members)}"><th:block th:replace="~{fragments/components :: empty('No membership history', 'Project members will appear here.')} "></th:block></section>
<section class="panel form-panel" th:unless="${#lists.isEmpty(members)}">
<div class="table-scroll"><table class="data-table"><caption class="sr-only">Membership history</caption><thead><tr><th scope="col">Intern</th><th scope="col">Joined</th><th scope="col">Left</th><th scope="col">Role</th></tr></thead>
<tbody><tr th:each="member : ${members}"><td th:text="${member.displayName}">Intern</td><td th:text="${#temporals.format(member.joinedAt, 'dd/MM/yyyy HH:mm')}">Joined</td><td th:text="${member.leftAt == null ? 'Current' : #temporals.format(member.leftAt, 'dd/MM/yyyy HH:mm')}">Current</td><td><span class="badge" th:classappend="${member.currentLeader ? ' badge-success' : ''}" th:text="${member.currentLeader ? 'Leader' : 'Member'}">Member</span></td></tr></tbody>
</table></div>
</section>
<form class="panel form-panel filter-form" th:if="${project.canManage}" method="post" th:action="@{/projects/{id}/members(id=${project.id})}">
<div class="field"><label class="field-label" for="intern">Intern user ID</label><input class="control" id="intern" name="internUserId" type="number" min="1" required></div>
<span></span><button class="button button-primary" type="submit">Add member</button>
</form>
</main>
</body>
</html>
+21 -29
View File
@@ -1,36 +1,28 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${details.task.title}">Task</title>
</head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle=${details.task.title},
section='Projects',
activeNav='projects',
primaryAction=~{},
content=~{::main})}">
<body>
<main>
<h1 th:text="${details.task.title}">Task</h1>
<p th:text="${details.task.description ?: 'No description'}">No description</p>
<p>Assignee: <strong th:text="${details.task.assigneeName}">Assignee</strong></p>
<p>Status: <strong th:text="${details.task.status}">TODO</strong></p>
<p>Due date: <span th:text="${details.task.dueDate ?: '—'}"></span></p>
<form th:if="${details.canChangeStatus}" method="post" th:action="@{/projects/{projectId}/tasks/{taskId}/status(projectId=${projectId},taskId=${details.task.id})}">
<label for="status">New status</label>
<select id="status" name="status" required>
<option th:each="status : ${statuses}" th:value="${status}" th:text="${status}">TODO</option>
</select>
<button type="submit">Change status</button>
<p class="page-description" th:text="${details.task.description == null || #strings.isEmpty(details.task.description) ? 'No Task description.' : details.task.description}">Task description.</p>
<section class="panel metric-strip metric-strip-three" aria-label="Task details">
<div class="metric"><div class="metric-label">Assignee</div><div class="metric-value" th:text="${details.task.assigneeName}">Assignee</div></div>
<div class="metric"><div class="metric-label">Status</div><div class="metric-value"><span class="badge" th:classappend="${details.task.status.name() == 'BLOCKED' ? ' badge-warning' : details.task.status.name() == 'DONE' ? ' badge-success' : ''}" th:text="${details.task.status}">TODO</span></div></div>
<div class="metric"><div class="metric-label">Due date</div><div class="metric-value" th:text="${details.task.dueDate == null ? 'No due date' : #temporals.format(details.task.dueDate, 'dd/MM/yyyy')}">No due date</div></div>
</section>
<form class="panel form-panel filter-form" th:if="${details.canChangeStatus}" method="post" th:action="@{/projects/{projectId}/tasks/{taskId}/status(projectId=${projectId},taskId=${details.task.id})}">
<div class="field"><label class="field-label" for="status">New status</label><select class="control" id="status" name="status" required><option th:each="status : ${statuses}" th:value="${status}" th:text="${status}" th:selected="${status == details.task.status}">TODO</option></select></div>
<span></span><button class="button button-primary" type="submit">Change status</button>
</form>
<section aria-labelledby="comments-heading">
<h2 id="comments-heading">Comments</h2>
<ol>
<li th:each="comment : ${details.comments}" th:text="${comment.body}">Comment</li>
</ol>
<form th:if="${details.canComment}" method="post" th:action="@{/projects/{projectId}/tasks/{taskId}/comments(projectId=${projectId},taskId=${details.task.id})}">
<label for="body">Comment</label>
<textarea id="body" name="body" required></textarea>
<button type="submit">Add comment</button>
</form>
<section class="panel form-panel" aria-labelledby="comments-heading">
<header class="panel-header"><h2 class="panel-title" id="comments-heading">Comments</h2></header>
<div th:if="${#lists.isEmpty(details.comments)}"><th:block th:replace="~{fragments/components :: empty('No comments', 'Task discussion will appear here.')} "></th:block></div>
<div class="table-scroll" th:unless="${#lists.isEmpty(details.comments)}"><table class="data-table"><caption class="sr-only">Task comments</caption><thead><tr><th scope="col">Comment</th><th scope="col">Created</th></tr></thead><tbody><tr th:each="comment : ${details.comments}"><td th:text="${comment.body}">Comment</td><td th:text="${#temporals.format(comment.createdAt, 'dd/MM/yyyy HH:mm')}">Created</td></tr></tbody></table></div>
<form class="form-grid form-panel" th:if="${details.canComment}" method="post" th:action="@{/projects/{projectId}/tasks/{taskId}/comments(projectId=${projectId},taskId=${details.task.id})}"><div class="field"><label class="field-label" for="body">Comment</label><textarea class="control" id="body" name="body" rows="4" required></textarea></div><div class="form-actions"><button class="button button-primary" type="submit">Add comment</button></div></form>
</section>
</main>
</body>
+15 -29
View File
@@ -1,36 +1,22 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Create Task</title>
</head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle='Create Task',
section='Projects',
activeNav='projects',
primaryAction=~{},
content=~{::main})}">
<body>
<main>
<h1>Create Task</h1>
<form method="post" th:action="@{/projects/{projectId}/tasks(projectId=${projectId})}" th:object="${taskForm}">
<div>
<label for="title">Title</label>
<input id="title" type="text" maxlength="200" required th:field="*{title}">
<p role="alert" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title error</p>
<p class="page-description">Assign work to a current eligible Project member.</p>
<form class="panel form-panel form-grid" method="post" th:action="@{/projects/{projectId}/tasks(projectId=${projectId})}" th:object="${taskForm}">
<div class="field"><label class="field-label" for="title">Title</label><input class="control" id="title" type="text" maxlength="200" required th:field="*{title}" th:attr="aria-invalid=${#fields.hasErrors('title')}"><p class="field-error" role="alert" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title error</p></div>
<div class="field"><label class="field-label" for="description">Description</label><textarea class="control" id="description" rows="5" th:field="*{description}"></textarea></div>
<div class="form-grid form-grid-three">
<div class="field"><label class="field-label" for="assigneeMembershipId">Assignee</label><select class="control" id="assigneeMembershipId" required th:field="*{assigneeMembershipId}" th:attr="aria-invalid=${#fields.hasErrors('assigneeMembershipId')}"><option value="">Select an assignee</option><option th:each="assignee : ${assignees}" th:value="${assignee.membershipId}" th:text="${assignee.displayName}">Member</option></select><p class="field-error" role="alert" th:if="${#fields.hasErrors('assigneeMembershipId')}" th:errors="*{assigneeMembershipId}">Assignee error</p></div>
<div class="field"><label class="field-label" for="dueDate">Due date</label><input class="control" id="dueDate" type="date" th:field="*{dueDate}"></div>
</div>
<div>
<label for="description">Description</label>
<textarea id="description" th:field="*{description}"></textarea>
</div>
<div>
<label for="assigneeMembershipId">Assignee</label>
<select id="assigneeMembershipId" required th:field="*{assigneeMembershipId}">
<option value="">Select an assignee</option>
<option th:each="assignee : ${assignees}" th:value="${assignee.membershipId}" th:text="${assignee.displayName}">Member</option>
</select>
<p role="alert" th:if="${#fields.hasErrors('assigneeMembershipId')}" th:errors="*{assigneeMembershipId}">Assignee error</p>
</div>
<div>
<label for="dueDate">Due date</label>
<input id="dueDate" type="date" th:field="*{dueDate}">
</div>
<button type="submit">Create Task</button>
<div class="form-actions"><a class="button" th:href="@{/projects/{projectId}/tasks(projectId=${projectId})}">Cancel</a><button class="button button-primary" type="submit">Create Task</button></div>
</form>
</main>
</body>
+21 -27
View File
@@ -1,33 +1,27 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project tasks</title>
</head>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: shell(
pageTitle='Project Tasks',
section='Projects',
activeNav='projects',
primaryAction=~{::#primary-action},
content=~{::main})}">
<body>
<a id="primary-action" class="button button-primary" th:if="${taskList.canCreate}" th:href="@{/projects/{projectId}/tasks/new(projectId=${projectId})}">Create Task</a>
<main>
<h1>Project tasks</h1>
<p>Progress: <strong th:text="${progressLabel}">N/A</strong></p>
<dl>
<dt>TODO</dt><dd th:text="${taskList.progress.todo}">0</dd>
<dt>IN_PROGRESS</dt><dd th:text="${taskList.progress.inProgress}">0</dd>
<dt>BLOCKED</dt><dd th:text="${taskList.progress.blocked}">0</dd>
<dt>DONE</dt><dd th:text="${taskList.progress.done}">0</dd>
</dl>
<p th:if="${taskList.canCreate}"><a th:href="@{/projects/{projectId}/tasks/new(projectId=${projectId})}">Create Task</a></p>
<table>
<caption>Current non-deleted Tasks</caption>
<thead><tr><th scope="col">Title</th><th scope="col">Assignee</th><th scope="col">Status</th><th scope="col">Due date</th></tr></thead>
<tbody>
<tr th:each="task : ${taskList.tasks}">
<td><a th:href="@{/projects/{projectId}/tasks/{taskId}(projectId=${projectId},taskId=${task.id})}" th:text="${task.title}">Task</a></td>
<td th:text="${task.assigneeName}">Assignee</td>
<td th:text="${task.status}">TODO</td>
<td th:text="${task.dueDate ?: '—'}"></td>
</tr>
</tbody>
</table>
<p class="page-description">Current non-deleted Tasks and Project completion progress.</p>
<section class="panel metric-strip" aria-label="Task progress">
<div class="metric"><div class="metric-label">Progress</div><div class="metric-value" th:text="${progressLabel}">N/A</div></div>
<div class="metric"><div class="metric-label">TODO</div><div class="metric-value" th:text="${taskList.progress.todo}">0</div></div>
<div class="metric"><div class="metric-label">IN PROGRESS</div><div class="metric-value" th:text="${taskList.progress.inProgress}">0</div></div>
<div class="metric"><div class="metric-label">BLOCKED / DONE</div><div class="metric-value" th:text="${taskList.progress.blocked + ' / ' + taskList.progress.done}">0 / 0</div></div>
</section>
<section class="panel" th:if="${#lists.isEmpty(taskList.tasks)}"><th:block th:replace="~{fragments/components :: empty('No current Tasks', 'Tasks for this Project will appear here.')} "></th:block></section>
<section class="panel" th:unless="${#lists.isEmpty(taskList.tasks)}">
<div class="table-scroll"><table class="data-table"><caption class="sr-only">Current non-deleted Tasks</caption><thead><tr><th scope="col">Title</th><th scope="col">Assignee</th><th scope="col">Status</th><th scope="col">Due date</th></tr></thead>
<tbody><tr th:each="task : ${taskList.tasks}"><td><a th:href="@{/projects/{projectId}/tasks/{taskId}(projectId=${projectId},taskId=${task.id})}" th:text="${task.title}">Task</a></td><td th:text="${task.assigneeName}">Assignee</td><td><span class="badge" th:classappend="${task.status.name() == 'BLOCKED' ? ' badge-warning' : task.status.name() == 'DONE' ? ' badge-success' : ''}" th:text="${task.status}">TODO</span></td><td th:text="${task.dueDate == null ? 'No due date' : #temporals.format(task.dueDate, 'dd/MM/yyyy')}">No due date</td></tr></tbody>
</table></div>
</section>
</main>
</body>
</html>