Added single user fetch to user api

This commit is contained in:
Jan 2025-11-17 10:16:01 +01:00
parent cd66b5bba5
commit 0a3d759733
12 changed files with 127 additions and 37 deletions

View file

@ -38,6 +38,7 @@
<flag v-if="column.showFlag" :tooltip-text="getCellValue(item, column)" :iso="getCellValue(item, column)"></flag>
<span v-else-if="column.iconResolver == null">{{ getCellValue(item, column) }}</span>
<span v-else-if="column.badgeResolver">{{ getCellValue(item, column) }}</span>
<component v-else
:is="getCellValue(item, column)"
weight="regular"

View file

@ -23,6 +23,9 @@
<div v-if="toast.title" class="toast__title">{{ toast.title }}</div>
<div class="toast__message">{{ toast.message }}</div>
</div>
<div v-if="toast.count > 1" class="toast__badge">
{{ toast.count }}
</div>
</div>
</transition-group>
</teleport>
@ -49,8 +52,40 @@ export default {
* @param {string} options.variant - Toast variant: 'success', 'error', 'warning', 'info'
* @param {number} options.duration - Auto-dismiss duration in ms (0 = no auto-dismiss)
* @param {string} options.icon - Icon name (optional)
* @param {boolean} options.stack - Whether to stack identical toasts (default: true)
*/
addToast(options = {}) {
const shouldStack = options.stack !== undefined ? options.stack : true;
// Check if an identical toast already exists
if (shouldStack) {
const existingToast = this.toasts.find(t =>
t.message === options.message &&
t.title === (options.title || null) &&
t.variant === this.mapVariant(options.variant || 'primary')
);
if (existingToast) {
// Increment count and reset timer
existingToast.count = (existingToast.count || 1) + 1;
// Clear existing timeout
if (existingToast.timeoutId) {
clearTimeout(existingToast.timeoutId);
}
// Set new timeout
const duration = options.duration !== undefined ? options.duration : 5000;
if (duration > 0) {
existingToast.timeoutId = setTimeout(() => {
this.removeToast(existingToast.id);
}, duration);
}
return existingToast.id;
}
}
const toast = {
id: this.nextId++,
message: options.message || 'Notification',
@ -58,13 +93,15 @@ export default {
variant: this.mapVariant(options.variant || 'primary' ),
duration: options.duration !== undefined ? options.duration : 5000,
icon: options.icon ? `Ph${options.icon.charAt(0).toUpperCase() + options.icon.slice(1)}` : null,
count: 1,
timeoutId: null
};
this.toasts.push(toast)
// Auto-dismiss if duration is set
if (toast.duration > 0) {
setTimeout(() => {
toast.timeoutId = setTimeout(() => {
this.removeToast(toast.id)
}, toast.duration)
}
@ -94,6 +131,10 @@ export default {
removeToast(id) {
const index = this.toasts.findIndex(toast => toast.id === id)
if (index > -1) {
// Clear timeout if exists
if (this.toasts[index].timeoutId) {
clearTimeout(this.toasts[index].timeoutId);
}
this.toasts.splice(index, 1)
}
},
@ -102,6 +143,12 @@ export default {
* Remove all toasts
*/
clearToasts() {
// Clear all timeouts
this.toasts.forEach(toast => {
if (toast.timeoutId) {
clearTimeout(toast.timeoutId);
}
});
this.toasts = []
},
@ -150,12 +197,11 @@ export default {
cursor: pointer;
pointer-events: auto;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.toast:hover {
transform: translateY(-2px);
box-shadow: 0 0.5rem 0.9rem rgba(0, 0, 0, 0.1);
}
@ -197,22 +243,50 @@ export default {
word-break: break-word;
}
.toast__badge {
flex-shrink: 0;
width: 2.4rem;
height: 2.4rem;
border-radius: 50%;
background-color: #002F54;
color: #5AF0B4;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
font-weight: 700;
margin-left: auto;
}
.toast-primary {
background-color: #5AF0B4;
color: #002F54;
}
.toast-primary .toast__badge {
background-color: #002F54;
color: #5AF0B4;
}
.toast--secondary {
color: #002F54;
background-color: #c3cfdf;
}
.toast--secondary .toast__badge {
background-color: #002F54;
color: #c3cfdf;
}
.toast--exception{
background-color: #BC2B72;
color: #ffffff;
}
.toast--exception .toast__badge {
background-color: #ffffff;
color: #BC2B72;
}
/* Transition animations */
.toast-enter-active {

View file

@ -13,9 +13,9 @@
{{ premise.hs_code }}
</div>
<div class="edit-calculation-cell-line edit-calculation-cell-subline"
v-if="premise.tariff_rate">
v-if="(premise.tariff_rate ?? null) !== null">
Tariff rate:
{{ toPercent(premise.tariff_rate) }}&nbsp;%
{{ toPercent(premise.tariff_rate) }}&nbsp;
</div>
</div>
</div>

View file

@ -51,7 +51,6 @@ import ModalDialog from "@/components/UI/ModalDialog.vue";
import {mapStores} from "pinia";
import {useValidityPeriodStore} from "@/store/validityPeriod.js";
import AutosuggestSearchbar from "@/components/UI/AutoSuggestSearchBar.vue";
import DataTable from "@/components/UI/DataTable.vue";
import TableView from "@/components/UI/TableView.vue";
import RadioOption from "@/components/UI/RadioOption.vue";
import {useMatrixRateStore} from "@/store/matrixRate.js";
@ -80,7 +79,7 @@ export default {
},
components: {
StagedRates,
RadioOption, TableView, DataTable, AutosuggestSearchbar, ModalDialog, Tooltip, IconButton, Dropdown
RadioOption, TableView, AutosuggestSearchbar, ModalDialog, Tooltip, IconButton, Dropdown
},
computed: {
...mapStores(useValidityPeriodStore, useMatrixRateStore, useContainerRateStore),

View file

@ -279,7 +279,7 @@ public class SecurityConfig {
} else if (identifyBy.equals("workday") && workdayId != null && !workdayId.isEmpty()) {
log.debug("Fetch user by workday id {}", workdayId);
user = userRepository.getByWorkdayId(workdayId);
user = userRepository.getByWorkdayId(workdayId).orElse(null);
}
if (user != null) {

View file

@ -12,6 +12,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
/**
* REST Controller for managing user-related operations.
@ -51,6 +52,22 @@ public class UserController {
.body(users.toList());
}
/**
* Retrieves a single user by its workday id.
*
* @param workdayId The workday id of the user to retrieve.
* @return A ResponseEntity containing the user, along with pagination headers.
*/
@GetMapping({"/{workdayId}/", "/{workdayId}"})
@PreAuthorize("hasRole('RIGHT-MANAGEMENT')")
public ResponseEntity<Optional<UserDTO>> getUser(@PathVariable String workdayId) {
Optional<UserDTO> users = userService.getUser(workdayId);
return ResponseEntity.ok()
.body(users);
}
/**
* Updates the details of an existing user or creates a new one if it does not exist.
* Users are identified by its workday id. If a group from the group membership does not exist

View file

@ -25,9 +25,6 @@ public class UserDTO {
@JsonProperty("workday_id")
private String workdayId;
@JsonProperty("is_active")
private boolean isActive;
@NotNull
@Size(min = 1)
@JsonProperty("groups")
@ -65,14 +62,6 @@ public class UserDTO {
this.workdayId = workdayId;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean active) {
isActive = active;
}
public List<String> getGroups() {
return groups;
}

View file

@ -18,6 +18,7 @@ import java.sql.Statement;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Repository
public class UserRepository {
@ -145,12 +146,12 @@ public class UserRepository {
}
@Transactional
public User getByWorkdayId(String workdayId) {
public Optional<User> getByWorkdayId(String workdayId) {
List<User> results = jdbcTemplate.query("SELECT * FROM sys_user WHERE workday_id = ?",
new UserMapper(),
workdayId);
return results.isEmpty() ? null : results.getFirst();
return results.isEmpty() ? Optional.empty() : Optional.of(results.getFirst());
}
@Transactional

View file

@ -11,7 +11,6 @@ public class UserTransformer {
public UserDTO toUserDTO(User entity) {
UserDTO dto = new UserDTO();
dto.setActive(entity.getActive());
dto.setEmail(entity.getEmail());
dto.setFirstName(entity.getFirstName());
dto.setLastName(entity.getLastName());
@ -25,7 +24,7 @@ public class UserTransformer {
public User fromUserDTO(UserDTO dto) {
User entity = new User();
entity.setActive(dto.isActive());
entity.setActive(true);
entity.setEmail(dto.getEmail());
entity.setFirstName(dto.getFirstName());
entity.setLastName(dto.getLastName());

View file

@ -10,6 +10,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* Service class responsible for handling business logic related to users.
* Provides methods to retrieve and update user information.
@ -38,6 +40,16 @@ public class UserService {
return SearchQueryResult.map(userRepository.listUsers(new SearchQueryPagination(page, limit)), userTransformer::toUserDTO);
}
/**
* Retrieves a single user by its workday id.
*
* @param workdayId The workday id of the user to retrieve.
* @return A ResponseEntity containing the user, along with pagination headers.
*/
public Optional<UserDTO> getUser(String workdayId) {
return userRepository.getByWorkdayId(workdayId).map(userTransformer::toUserDTO);
}
/**
* Updates an existing user's information.
*

View file

@ -25,8 +25,6 @@ spring.sql.init.mode=never
lcc.allowed_cors=
lcc.allowed_oauth_token_cors=*
logging.level.org.springframework.ws=DEBUG
logging.level.org.springframework.oxm=DEBUG
lcc.auth.identify.by=workday
lcc.auth.claim.workday=employeeid

View file

@ -1,20 +1,20 @@
INSERT INTO sys_group(group_name, group_description)
VALUES ('none', 'no rights');
INSERT INTO sys_group(group_name, group_description)
VALUES ('basic', 'Login, generate reports');
VALUES ('basic', 'can generate reports');
INSERT INTO sys_group(group_name, group_description)
VALUES ('calculation', 'Login, generate reports, do calculations');
VALUES ('calculation', 'can generate reports, do calculations');
INSERT INTO sys_group(group_name, group_description)
VALUES ('freight', 'Login, generate reports, edit freight rates');
VALUES ('freight', 'manage freight rates');
INSERT INTO sys_group(group_name, group_description)
VALUES ('packaging', 'Login, generate reports, edit packaging data');
VALUES ('packaging', 'manage packaging data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('material', 'Login, generate reports, edit material data');
VALUES ('material', 'manage material data');
INSERT INTO sys_group(group_name, group_description)
VALUES ('super',
'Login, generate reports, do calculations, edit freight rates, edit packaging data');
'can generate reports, do calculations, manage freight rates, manage packaging data, manage material data, manage general system settings');
INSERT INTO sys_group(group_name, group_description)
VALUES ('service', 'Register API Tokens');
VALUES ('service', 'register external applications');
INSERT INTO sys_group(group_name, group_description)
VALUES ('right-management',
'Add/Remove users, groups, etc.');
'add users, manage user groups');