mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:09:35 +01:00
Einheitliches Paginierungs-Pattern mit page, size und Multi-Field sort
für alle 14 List-Endpoints. Response-Format ändert sich von [...] zu
{ content: [...], page: { number, size, totalElements, totalPages } }.
Backend:
- Shared Kernel: Page<T>, PageRequest, SortField, SortDirection
- PaginationHelper (SQL ORDER BY mit Whitelist), PageResponse DTO
- Paginated Methoden in allen 14 Domain-Repos + JDBC-Implementierungen
- Safety-Limit (500) für findAllBelowMinimumLevel/ExpiryRelevantBatches
- Alle List-Use-Cases akzeptieren PageRequest, liefern Page<T>
- Alle Controller mit page/size/sort Query-Params + PageResponse
Frontend:
- PagedResponse<T> Type auf nested page-Format aktualisiert
- Alle 14 API-Client-Resourcen liefern PagedResponse mit PaginationParams
- Alle Hooks mit Pagination-State (currentPage, totalPages, pageSize)
- Alle List-Screens mit Seiten-Navigation (Pfeiltasten) und Footer
Loadtest:
- Podman-Support im justfile (DOCKER_HOST auto-detect)
- Verschärfte Performance-Schwellwerte basierend auf Ist-Werten
176 lines
4.6 KiB
TypeScript
176 lines
4.6 KiB
TypeScript
import { useState, useCallback } from 'react';
|
|
import type { UserDTO, CreateUserRequest } from '@effigenix/api-client';
|
|
import type { PaginationParams } from '@effigenix/types';
|
|
import { client } from '../utils/api-client.js';
|
|
|
|
type RoleName = CreateUserRequest['roleNames'][number];
|
|
|
|
interface UsersState {
|
|
users: UserDTO[];
|
|
loading: boolean;
|
|
error: string | null;
|
|
currentPage: number;
|
|
totalElements: number;
|
|
totalPages: number;
|
|
pageSize: number;
|
|
}
|
|
|
|
function errorMessage(err: unknown): string {
|
|
return err instanceof Error ? err.message : 'Unbekannter Fehler';
|
|
}
|
|
|
|
export function useUsers() {
|
|
const [state, setState] = useState<UsersState>({
|
|
users: [],
|
|
loading: false,
|
|
error: null,
|
|
currentPage: 0,
|
|
totalElements: 0,
|
|
totalPages: 0,
|
|
pageSize: 20,
|
|
});
|
|
|
|
const fetchUsers = useCallback(async (pagination?: PaginationParams) => {
|
|
setState((s) => ({ ...s, loading: true, error: null }));
|
|
try {
|
|
const res = await client.users.list(pagination);
|
|
setState((s) => ({
|
|
...s,
|
|
users: res.content,
|
|
currentPage: res.page.number,
|
|
totalElements: res.page.totalElements,
|
|
totalPages: res.page.totalPages,
|
|
pageSize: res.page.size,
|
|
loading: false,
|
|
error: null,
|
|
}));
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
|
|
}
|
|
}, []);
|
|
|
|
const createUser = useCallback(
|
|
async (username: string, email: string, password: string, roleName?: string) => {
|
|
setState((s) => ({ ...s, loading: true, error: null }));
|
|
try {
|
|
const user = await client.users.create({
|
|
username,
|
|
email,
|
|
password,
|
|
roleNames: roleName ? [roleName as RoleName] : [],
|
|
});
|
|
setState((s) => ({ ...s, users: [...s.users, user], loading: false, error: null }));
|
|
return user;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, loading: false, error: errorMessage(err) }));
|
|
return null;
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
const lockUser = useCallback(async (id: string) => {
|
|
try {
|
|
const user = await client.users.lock(id);
|
|
setState((s) => ({
|
|
...s,
|
|
users: s.users.map((u) => (u.id === id ? user : u)),
|
|
}));
|
|
return user;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, error: errorMessage(err) }));
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const unlockUser = useCallback(async (id: string) => {
|
|
try {
|
|
const user = await client.users.unlock(id);
|
|
setState((s) => ({
|
|
...s,
|
|
users: s.users.map((u) => (u.id === id ? user : u)),
|
|
}));
|
|
return user;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, error: errorMessage(err) }));
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const assignRole = useCallback(async (id: string, roleName: string) => {
|
|
try {
|
|
const user = await client.users.assignRole(id, { roleName: roleName as RoleName });
|
|
setState((s) => ({
|
|
...s,
|
|
users: s.users.map((u) => (u.id === id ? user : u)),
|
|
}));
|
|
return user;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, error: errorMessage(err) }));
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const removeRole = useCallback(async (id: string, roleName: string) => {
|
|
try {
|
|
await client.users.removeRole(id, roleName);
|
|
const updated = await client.users.getById(id);
|
|
setState((s) => ({
|
|
...s,
|
|
users: s.users.map((u) => (u.id === id ? updated : u)),
|
|
}));
|
|
return true;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, error: errorMessage(err) }));
|
|
return false;
|
|
}
|
|
}, []);
|
|
|
|
const changePassword = useCallback(
|
|
async (id: string, currentPassword: string, newPassword: string) => {
|
|
try {
|
|
await client.users.changePassword(id, { currentPassword, newPassword });
|
|
return true;
|
|
} catch (err) {
|
|
setState((s) => ({ ...s, error: errorMessage(err) }));
|
|
return false;
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
const nextPage = useCallback(() => {
|
|
if (state.currentPage < state.totalPages - 1) {
|
|
// Caller should re-fetch with new page
|
|
}
|
|
}, [state.currentPage, state.totalPages]);
|
|
|
|
const prevPage = useCallback(() => {
|
|
if (state.currentPage > 0) {
|
|
// Caller should re-fetch with new page
|
|
}
|
|
}, [state.currentPage]);
|
|
|
|
const goToPage = useCallback((_page: number) => {
|
|
// Caller should re-fetch with new page
|
|
}, []);
|
|
|
|
const clearError = useCallback(() => {
|
|
setState((s) => ({ ...s, error: null }));
|
|
}, []);
|
|
|
|
return {
|
|
...state,
|
|
fetchUsers,
|
|
createUser,
|
|
lockUser,
|
|
unlockUser,
|
|
assignRole,
|
|
removeRole,
|
|
changePassword,
|
|
nextPage,
|
|
prevPage,
|
|
goToPage,
|
|
clearError,
|
|
};
|
|
}
|