feat: add ordering for member page
This commit is contained in:
parent
be2f01c070
commit
b479b992cf
@ -13,6 +13,7 @@ import fr.titionfire.ffsaf.rest.data.SimpleMembre;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
|
||||
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DInternalError;
|
||||
import fr.titionfire.ffsaf.rest.from.FullMemberForm;
|
||||
import fr.titionfire.ffsaf.utils.*;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
@ -102,8 +103,30 @@ public class MembreService {
|
||||
return baseUni;
|
||||
}
|
||||
|
||||
private Sort getSort(String order) {
|
||||
|
||||
Sort sort;
|
||||
if (order == null || order.isBlank()) {
|
||||
sort = Sort.ascending("fname", "lname");
|
||||
} else {
|
||||
sort = Sort.empty();
|
||||
|
||||
for (String e : order.split(",")) {
|
||||
String[] split = e.split(" ");
|
||||
if (split.length == 2) {
|
||||
sort = sort.and(split[0],
|
||||
split[1].equals("n") ? Sort.Direction.Ascending : Sort.Direction.Descending);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sort;
|
||||
}
|
||||
|
||||
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club,
|
||||
int licenceRequest, int payState) {
|
||||
int licenceRequest, int payState, String order) {
|
||||
if (search == null)
|
||||
search = "";
|
||||
search = "%" + search.replaceAll(" ", "% %") + "%";
|
||||
@ -111,6 +134,10 @@ public class MembreService {
|
||||
String finalSearch = search;
|
||||
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
|
||||
|
||||
Sort sort = getSort(order);
|
||||
if (sort == null)
|
||||
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
|
||||
|
||||
return baseUni
|
||||
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
|
||||
.chain(ids -> {
|
||||
@ -121,17 +148,17 @@ public class MembreService {
|
||||
if (club == null || club.isBlank()) {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ")",
|
||||
Sort.ascending("fname", "lname"), finalSearch, ids)
|
||||
sort, finalSearch, ids)
|
||||
.page(Page.ofSize(limit));
|
||||
} else {
|
||||
if (club.equals("null")) {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ")",
|
||||
Sort.ascending("fname", "lname"), finalSearch, ids).page(Page.ofSize(limit));
|
||||
sort, finalSearch, ids).page(Page.ofSize(limit));
|
||||
} else {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ")",
|
||||
Sort.ascending("fname", "lname"), finalSearch, club + "%", ids)
|
||||
sort, finalSearch, club + "%", ids)
|
||||
.page(Page.ofSize(limit));
|
||||
}
|
||||
}
|
||||
@ -140,7 +167,7 @@ public class MembreService {
|
||||
}
|
||||
|
||||
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
|
||||
String subject) {
|
||||
String order, String subject) {
|
||||
if (search == null)
|
||||
search = "";
|
||||
search = "%" + search.replaceAll(" ", "% %") + "%";
|
||||
@ -149,6 +176,10 @@ public class MembreService {
|
||||
|
||||
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
|
||||
|
||||
Sort sort = getSort(order);
|
||||
if (sort == null)
|
||||
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
|
||||
|
||||
return baseUni
|
||||
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
|
||||
.chain(ids -> {
|
||||
@ -158,7 +189,7 @@ public class MembreService {
|
||||
.chain(membreModel -> {
|
||||
PanacheQuery<MembreModel> query = repository.find(
|
||||
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ")",
|
||||
Sort.ascending("fname", "lname"), finalSearch, membreModel.getClub(), ids)
|
||||
sort, finalSearch, membreModel.getClub(), ids)
|
||||
.page(Page.ofSize(limit));
|
||||
return getPageResult(query, limit, page);
|
||||
});
|
||||
|
||||
@ -58,13 +58,14 @@ public class MembreAdminEndpoints {
|
||||
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
|
||||
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
|
||||
@Parameter(description = "Club à filter") @QueryParam("club") String club,
|
||||
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
|
||||
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
|
||||
@Parameter(description = "État de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
|
||||
@Parameter(description = "État du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
|
||||
@Parameter(description = "Ordre") @QueryParam("order") String order) {
|
||||
if (limit == null)
|
||||
limit = 50;
|
||||
if (page == null || page < 1)
|
||||
page = 1;
|
||||
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment);
|
||||
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@ -51,12 +51,13 @@ public class MembreClubEndpoints {
|
||||
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
|
||||
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
|
||||
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
|
||||
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment) {
|
||||
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
|
||||
@Parameter(description = "Ordre") @QueryParam("order") String order) {
|
||||
if (limit == null)
|
||||
limit = 50;
|
||||
if (page == null || page < 1)
|
||||
page = 1;
|
||||
return membreService.search(limit, page - 1, search, licenceRequest, payment, securityCtx.getSubject());
|
||||
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, securityCtx.getSubject());
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@ -13,6 +13,7 @@ import * as XLSX from "xlsx-js-style";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faEuroSign} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
|
||||
export function MemberList({source}) {
|
||||
const {hash} = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@ -26,14 +27,19 @@ export function MemberList({source}) {
|
||||
const [stateFilter, setStateFilter] = useState(4)
|
||||
const [lastSearch, setLastSearch] = useState("");
|
||||
const [paymentFilter, setPaymentFilter] = useState(2);
|
||||
const [order, setOrder] = useState("");
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error, refresh} = useFetch(`/member/find/${source}?page=${page}&licenceRequest=${stateFilter}&payment=${paymentFilter}`, setLoading, 1)
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
refresh
|
||||
} = useFetch(`/member/find/${source}?page=${page}&licenceRequest=${stateFilter}&payment=${paymentFilter}&order=${order}`, setLoading, 1)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}`);
|
||||
}, [hash, clubFilter, stateFilter, lastSearch, paymentFilter]);
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}&order=${order}`);
|
||||
}, [hash, clubFilter, stateFilter, lastSearch, paymentFilter, order]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data)
|
||||
@ -79,6 +85,10 @@ export function MemberList({source}) {
|
||||
setLastSearch(search);
|
||||
}
|
||||
|
||||
const onOrderChange = (newOrder) => {
|
||||
setOrder(newOrder.join(","));
|
||||
}
|
||||
|
||||
return <>
|
||||
<div>
|
||||
<div className="row">
|
||||
@ -102,6 +112,14 @@ export function MemberList({source}) {
|
||||
<button className="btn btn-primary" onClick={() => navigate("pay")} style={{marginTop: "0.5rem"}}>Paiement des
|
||||
licences</button>}
|
||||
</div>
|
||||
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Trie</div>
|
||||
<div className="card-body">
|
||||
<OrderBar onOrderChange={onOrderChange} source={source}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Filtre</div>
|
||||
<div className="card-body">
|
||||
@ -371,7 +389,7 @@ function MakeRow({member, showLicenceState, navigate, source}) {
|
||||
const rowContent = <>
|
||||
<div className="row">
|
||||
<span className="col-auto">{(member.licence_number ? String(member.licence_number).padStart(5, '0') : "-------") + " "}
|
||||
{(showLicenceState && member.licence != null && member.licence.pay)? <FontAwesomeIcon icon={faEuroSign}/> : <> </>}</span>
|
||||
{(showLicenceState && member.licence != null && member.licence.pay) ? <FontAwesomeIcon icon={faEuroSign}/> : <> </>}</span>
|
||||
<div className="ms-2 col-auto">
|
||||
<div className="fw-bold">{member.fname} {member.lname}</div>
|
||||
</div>
|
||||
@ -394,9 +412,92 @@ function MakeRow({member, showLicenceState, navigate, source}) {
|
||||
}
|
||||
}
|
||||
|
||||
function OrderBar({onOrderChange, source}) {
|
||||
const [orderCriteria, setOrderCriteria] = useState(['']);
|
||||
|
||||
const handleChange = (index, value) => {
|
||||
const newCriteria = [...orderCriteria];
|
||||
newCriteria[index] = value;
|
||||
|
||||
// Si le dernier critère est rempli, on en ajoute un nouveau
|
||||
if (index === orderCriteria.length - 1 && value !== '') {
|
||||
newCriteria.push('');
|
||||
}
|
||||
// Si un critère (sauf le premier) est réinitialisé, on le supprime
|
||||
else if (value === '' && (index !== 0 || orderCriteria.length > 1)) {
|
||||
newCriteria.splice(index, 1);
|
||||
}
|
||||
|
||||
setOrderCriteria(newCriteria);
|
||||
onOrderChange(newCriteria.filter(c => c !== ''));
|
||||
};
|
||||
|
||||
// Liste de toutes les options possibles
|
||||
const allOptions = [
|
||||
{value: 'lname n', label: 'Nom ↓', base: 'lname'},
|
||||
{value: 'lname i', label: 'Nom ↑', base: 'lname'},
|
||||
{value: 'fname n', label: 'Prénom ↓', base: 'fname'},
|
||||
{value: 'fname i', label: 'Prénom ↑', base: 'fname'},
|
||||
{value: 'categorie n', label: 'Catégorie ↓', base: 'categorie'},
|
||||
{value: 'categorie i', label: 'Catégorie ↑', base: 'categorie'},
|
||||
{value: 'licence n', label: 'Licence ↓', base: 'licence'},
|
||||
{value: 'licence i', label: 'Licence ↑', base: 'licence'},
|
||||
];
|
||||
|
||||
if (source === "admin") {
|
||||
allOptions.push(
|
||||
{value: 'club.name n', label: 'Club ↓', base: 'club.name'},
|
||||
{value: 'club.name i', label: 'Club ↑', base: 'club.name'},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
{orderCriteria.map((criteria, index) => {
|
||||
// Récupère les bases des critères déjà sélectionnés (sauf le courant)
|
||||
const usedBases = orderCriteria
|
||||
.filter((c, i) => c !== '' && i !== index)
|
||||
.map(c => allOptions.find(o => o.value === c)?.base);
|
||||
|
||||
// Filtre les options disponibles
|
||||
const availableOptions = allOptions.filter(option =>
|
||||
!usedBases.includes(option.base) || option.value === criteria
|
||||
);
|
||||
|
||||
return (
|
||||
<select
|
||||
key={index}
|
||||
className="form-select mb-2"
|
||||
value={criteria}
|
||||
onChange={(e) => handleChange(index, e.target.value)}
|
||||
>
|
||||
<option value="">----</option>
|
||||
{availableOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let allClub = []
|
||||
|
||||
function FiltreBar({showLicenceState, setShowLicenceState, data, clubFilter, setClubFilter, source, stateFilter, setStateFilter, paymentFilter, setPaymentFilter}) {
|
||||
function FiltreBar({
|
||||
showLicenceState,
|
||||
setShowLicenceState,
|
||||
data,
|
||||
clubFilter,
|
||||
setClubFilter,
|
||||
source,
|
||||
stateFilter,
|
||||
setStateFilter,
|
||||
paymentFilter,
|
||||
setPaymentFilter
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user