Skip to content

Commit eb43157

Browse files
committed
User list
1 parent 10839c9 commit eb43157

File tree

11 files changed

+264
-10
lines changed

11 files changed

+264
-10
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.github.throyer.common.springboot.controllers.app;
2+
3+
import com.github.throyer.common.springboot.domain.models.pagination.Page;
4+
import com.github.throyer.common.springboot.domain.models.pagination.Pagination;
5+
import com.github.throyer.common.springboot.domain.repositories.UserRepository;
6+
import com.github.throyer.common.springboot.domain.services.user.FindUserService;
7+
import com.github.throyer.common.springboot.domain.services.user.dto.SearchUser;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.data.domain.Sort;
10+
import org.springframework.security.access.prepost.PreAuthorize;
11+
import org.springframework.stereotype.Controller;
12+
import org.springframework.ui.Model;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
16+
@Controller
17+
@PreAuthorize("hasAnyAuthority('ADM')")
18+
@RequestMapping("/app/users")
19+
public class UserController {
20+
21+
@Autowired
22+
private UserRepository repository;
23+
24+
@GetMapping
25+
public String idnex(Model model, Pagination pagination, Sort sort, SearchUser search) {
26+
27+
var page = Page.of(repository.findSimplifiedUsers(pagination.build()));
28+
29+
model.addAttribute("page", page);
30+
31+
return "/app/users/index";
32+
}
33+
}

src/main/java/com/github/throyer/common/springboot/domain/models/pagination/Pagination.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import com.github.throyer.common.springboot.domain.validation.InvalidSortException;
1717
import com.github.throyer.common.springboot.domain.validation.SimpleError;
18+
import java.util.Objects;
1819

1920
import org.springframework.data.domain.Pageable;
2021
import org.springframework.data.domain.Sort;
@@ -57,6 +58,26 @@ public void setSize(int size) {
5758
}
5859
}
5960

61+
public void setSize(String string) {
62+
63+
if (Objects.isNull(string)) {
64+
this.size = DEFAULT_SIZE;
65+
return;
66+
}
67+
68+
try {
69+
var size = Integer.parseInt(string);
70+
if (size >= MIN_SIZE && size <= MAX_SIZE) {
71+
this.size = size;
72+
} else {
73+
this.size = DEFAULT_SIZE;
74+
}
75+
}
76+
catch (NumberFormatException exception) {
77+
this.size = DEFAULT_SIZE;
78+
}
79+
}
80+
6081
public Pageable build() {
6182
return of(page, size);
6283
}
@@ -68,8 +89,8 @@ public Pageable build(Sort sort) {
6889
public <T> Pageable build(Sort query, Class<T> entity) {
6990
if (!entity.isAnnotationPresent(Entity.class)) {
7091
throw new ResponseStatusException(
71-
HttpStatus.INTERNAL_SERVER_ERROR,
72-
"Esta Classe não é uma entidade."
92+
HttpStatus.INTERNAL_SERVER_ERROR,
93+
"Esta Classe não é uma entidade."
7394
);
7495
}
7596

@@ -78,9 +99,9 @@ public <T> Pageable build(Sort query, Class<T> entity) {
7899

79100
for (Order order : query) {
80101
Optional<Field> optional = fields(entity)
81-
.filter(field -> belongs(field, order))
102+
.filter(field -> belongs(field, order))
82103
.findAny();
83-
104+
84105
if (optional.isPresent()) {
85106
orders.add(getOrder(order, optional.get()));
86107
} else {
@@ -97,7 +118,7 @@ public <T> Pageable build(Sort query, Class<T> entity) {
97118

98119
private static Boolean belongs(Field field, Order order) {
99120
var sortable = field.getAnnotation(SortableProperty.class);
100-
121+
101122
var fieldName = field.getName().equals(order.getProperty());
102123
var name = sortable.name().equals(order.getProperty());
103124

@@ -106,8 +127,8 @@ private static Boolean belongs(Field field, Order order) {
106127

107128
private static <T> Stream<Field> fields(Class<T> entity) {
108129
return Arrays
109-
.asList(entity.getDeclaredFields())
110-
.stream()
130+
.asList(entity.getDeclaredFields())
131+
.stream()
111132
.filter(field -> field.isAnnotationPresent(SortableProperty.class));
112133
}
113134

@@ -125,4 +146,4 @@ private static Order getOrder(Order order, Field field) {
125146

126147
return new Order(order.getDirection(), fieldName);
127148
}
128-
}
149+
}

src/main/java/com/github/throyer/common/springboot/domain/repositories/UserRepository.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Optional;
44

55
import com.github.throyer.common.springboot.domain.models.entity.User;
6+
import com.github.throyer.common.springboot.domain.services.user.dto.UserDetails;
67

78
import org.springframework.data.domain.Page;
89
import org.springframework.data.domain.Pageable;
@@ -74,4 +75,15 @@ default void deleteAll(Iterable<? extends User> entities) {
7475
public Optional<User> findOptionalByEmailFetchRoles(String email);
7576

7677
public Optional<User> findOptionalByEmail(String email);
78+
79+
@Query("""
80+
SELECT
81+
new com.github.throyer.common.springboot.domain.services.user.dto.UserDetails(
82+
user.id,
83+
user.name,
84+
user.email
85+
)
86+
FROM #{#entityName} user
87+
""")
88+
public Page<UserDetails> findSimplifiedUsers(Pageable pageable);
7789
}

src/main/java/com/github/throyer/common/springboot/domain/services/user/dto/UserDetails.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.throyer.common.springboot.domain.services.user.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonInclude;
34
import java.util.List;
45

56
import com.github.throyer.common.springboot.domain.models.entity.User;
@@ -9,6 +10,8 @@ public class UserDetails implements Entity {
910
private final Long id;
1011
private final String name;
1112
private final String email;
13+
14+
@JsonInclude(JsonInclude.Include.NON_NULL)
1215
private final List<String> roles;
1316

1417
public UserDetails(User user) {
@@ -22,6 +25,13 @@ public UserDetails(User user) {
2225
.toList();
2326
}
2427

28+
public UserDetails(Long id, String name, String email) {
29+
this.id = id;
30+
this.name = name;
31+
this.email = email;
32+
this.roles = null;
33+
}
34+
2535
public Long getId() {
2636
return id;
2737
}

src/main/resources/static/css/styles.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,26 @@
4444
.slideIn {
4545
-webkit-animation-name: slideIn;
4646
animation-name : slideIn;
47+
}
48+
49+
.pagination-container {
50+
display: flex;
51+
flex-direction: row;
52+
justify-content: center;
53+
align-items: center;
54+
gap: 2rem;
55+
}
56+
57+
#sizes_input {
58+
cursor: pointer;
59+
}
60+
61+
.dropdown-menu {
62+
gap: .25rem;
63+
padding: .5rem;
64+
border-radius: .5rem;
65+
}
66+
67+
.dropdown-menu .dropdown-item {
68+
border-radius: .25rem;
4769
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const form = document.querySelector("#sizes_form");
2+
3+
form.addEventListener("change", () => {
4+
form.submit();
5+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("gg")

src/main/resources/templates/app/fragments/navbar.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,22 @@
6767
</a>
6868
<ul class="dropdown-menu border-0 shadow bg-light animate slideIn" aria-labelledby="show-more">
6969
<li>
70-
<a data-bs-toggle="modal" data-bs-target="#me" class="dropdown-item" href="#">
70+
<a href="#" data-bs-toggle="modal" data-bs-target="#me" class="dropdown-item">
7171
<i class="fas fa-user-circle"></i>
7272
Me
7373
</a>
7474
</li>
75+
<li sec:authorize="hasAnyAuthority('ADM')">
76+
<a class="dropdown-item" th:href="@{/app/users}">
77+
<i class="fas fa-users"></i>
78+
Users
79+
</a>
80+
</li>
7581
<li>
7682
<hr class="dropdown-divider">
7783
</li>
7884
<li>
79-
<a th:href="@{/app/logout}" class="dropdown-item" href="#">
85+
<a th:href="@{/app/logout}" class="dropdown-item">
8086
<i class="fas fa-door-open"></i>
8187
Sign out
8288
</a>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<div th:fragment="delete-confirm" class="modal fade" tabindex="-1" role="dialog" id="delete-confirm">
2+
<div class="modal-dialog" role="document">
3+
<div class="modal-content rounded-4 shadow">
4+
<div class="modal-body p-4 text-center">
5+
<h5 class="mb-0">Enable this setting?</h5>
6+
<p class="mb-0">You can always change your mind in your account settings.</p>
7+
</div>
8+
<div class="modal-footer flex-nowrap p-0">
9+
<button type="button"
10+
class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0 border-right"><strong>Yes,
11+
enable</strong></button>
12+
<button type="button" class="btn btn-lg btn-link fs-6 text-decoration-none col-6 m-0 rounded-0"
13+
data-bs-dismiss="modal">No thanks</button>
14+
</div>
15+
</div>
16+
</div>
17+
</div>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<div th:fragment="table (page)">
2+
<table class="table table-bordered mt-4 table-striped table-hover ">
3+
<thead>
4+
<tr>
5+
<th scope="col">Name</th>
6+
<th scope="col">Email</th>
7+
<th class="text-center">Actions</th>
8+
</tr>
9+
</thead>
10+
<tbody>
11+
<tr th:each="user : ${page.content}" th:object="${user}">
12+
<td th:text="*{name}" class="align-middle">name</td>
13+
<td th:text="*{email}" class="align-middle">email</td>
14+
<td width="130" class="text-center">
15+
<div class="btn-group">
16+
<button type="button" class="btn-sm btn btn-outline-dark">
17+
<i class="far fa-trash-alt"></i>
18+
</button>
19+
</div>
20+
</td>
21+
</tr>
22+
</tbody>
23+
</table>
24+
<div class="pagination-container">
25+
<nav class="">
26+
<ul class="pagination pagination-sm">
27+
28+
<!-- first page -->
29+
<li
30+
th:classappend="${page.page == 0} ? 'disabled'"
31+
class="page-item">
32+
<a class="page-link" th:href="@{/app/users(page=0, size=${(param.size ?: 10)})}">
33+
<span class="d-lg-none">
34+
<i class="fas fa-angle-double-left"></i>
35+
</span>
36+
<span class="d-none d-lg-block">
37+
Primeira
38+
</span>
39+
</a>
40+
</li>
41+
42+
<!-- previous page -->
43+
<li
44+
th:classappend="${page.page == 0} ? 'disabled'"
45+
class="page-item"
46+
th:if="${page.page > 0}">
47+
<a class="page-link" th:href="@{/app/users(page=${page.page -1}, size=${(param.size ?: 10)})}">
48+
<span class="d-lg-none">
49+
<i class="fas fa-angle-left"></i>
50+
</span>
51+
<span class="d-none d-lg-block">
52+
Anterior
53+
</span>
54+
</a>
55+
</li>
56+
57+
<!-- interval buttons -->
58+
<li
59+
class="page-item"
60+
th:each="interval : ${#numbers.sequence(page.page - 3, page.page + 3)}"
61+
th:if="${interval > -1 && interval < page.totalPages}"
62+
th:classappend="${page.page == interval} ? 'disabled'">
63+
<a
64+
class="page-link"
65+
th:href="@{/app/users(page=${interval}, size=${(param.size ?: 10)})}"
66+
th:text="${interval + 1}">
67+
</a>
68+
</li>
69+
70+
<!-- next page -->
71+
<li
72+
class="page-item"
73+
th:classappend="${page.page == page.totalPages} ? 'disabled'"
74+
th:if="${page.page + 1 < page.totalPages}">
75+
<a class="page-link" th:href="@{/app/users(page=${page.page +1}, size=${(param.size ?: 10)})}">
76+
<span class="d-none d-lg-block">
77+
Proximo
78+
</span>
79+
<span class="d-lg-none">
80+
<i class="fas fa-angle-right"></i>
81+
</span>
82+
</a>
83+
</li>
84+
85+
<!-- last page -->
86+
<li
87+
th:classappend="${page.page + 1 == page.totalPages} ? 'disabled'"
88+
class="page-item">
89+
<a class="page-link" th:href="@{/app/users(page=${page.totalPages -1}, size=${(param.size ?: 10)})}">
90+
<span class="d-none d-lg-block">
91+
Última
92+
</span>
93+
<span class="d-lg-none">
94+
<i class="fas fa-angle-double-right"></i>
95+
</span>
96+
</a>
97+
</li>
98+
</ul>
99+
</nav>
100+
<form id="sizes_form" class="">
101+
<input hidden type="text" name="page" value="0">
102+
<div class="input-group">
103+
<select id="sizes_input" name="size" class="form-select form-select-sm">
104+
<option value="10">Itens por pagina</option>
105+
<option value="10">10</option>
106+
<option value="20">20</option>
107+
<option value="25">25</option>
108+
<option value="50">50</option>
109+
</select>
110+
</div>
111+
</form>
112+
</div>
113+
</div>

0 commit comments

Comments
 (0)