This commit is contained in:
Flatlogic Bot 2026-05-07 01:31:16 +00:00
parent 4e6b10f0bc
commit 99a83d106c
2 changed files with 143 additions and 62 deletions

View File

@ -537,7 +537,7 @@ function scmanutention_validate_item_reference(PDO $db, int $owner_auth_id, stri
];
}
function scmanutention_search_available_items(PDO $db, int $owner_auth_id, string $query, int $limit = 12): array
function scmanutention_search_available_items(PDO $db, int $owner_auth_id, string $query, ?int $limit = 25, int $offset = 0): array
{
$query = trim($query);
if ($query === '') {
@ -548,7 +548,12 @@ function scmanutention_search_available_items(PDO $db, int $owner_auth_id, strin
$exact = $escaped;
$prefix = $escaped . '%';
$contains = '%' . $escaped . '%';
$limit = max(1, min(30, $limit));
$limit_clause = '';
$offset = max(0, $offset);
if ($limit !== null && $limit > 0) {
$limit = max(1, min(100, $limit));
$limit_clause = ' LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset;
}
$sql = "
SELECT *
@ -607,7 +612,7 @@ function scmanutention_search_available_items(PDO $db, int $owner_auth_id, strin
CHAR_LENGTH(result_name) ASC,
result_name ASC,
result_key ASC
LIMIT {$limit}";
{$limit_clause}";
$stmt = $db->prepare($sql);
$stmt->execute([

View File

@ -133,7 +133,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === '
header('Content-Type: application/json; charset=utf-8');
$query = trim((string) ($_GET['q'] ?? ''));
$suggestion_rows = scmanutention_search_available_items($db, $current_owner_auth_id, $query, 14);
$offset = max(0, (int) ($_GET['offset'] ?? 0));
$limit = max(1, min(50, (int) ($_GET['limit'] ?? 25)));
$suggestion_rows = scmanutention_search_available_items($db, $current_owner_auth_id, $query, $limit + 1, $offset);
$has_more = count($suggestion_rows) > $limit;
if ($has_more) {
array_pop($suggestion_rows);
}
$suggestion_custom_ids = [];
foreach ($suggestion_rows as $row) {
@ -165,7 +172,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === '
];
}, $suggestion_rows);
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo json_encode([
'items' => $items,
'hasMore' => $has_more,
'nextOffset' => $offset + count($items),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
@ -809,6 +820,7 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
z-index: 9999;
max-height: 320px;
overflow-y: auto;
overscroll-behavior: contain;
box-shadow: 0 22px 40px rgba(0, 0, 0, 0.38);
}
@ -885,6 +897,13 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
background: rgba(255, 255, 255, 0.07);
}
.picker-status {
padding: 0.65rem 0.8rem 0.45rem;
color: var(--text-soft);
font-size: 0.78rem;
text-align: center;
}
.picker-selection {
display: flex;
justify-content: space-between;
@ -1347,7 +1366,7 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
<input type="hidden" name="action" value="add_item">
<input type="hidden" name="sheet_id" value="<?php echo (int) $selected_sheet['cl_scmanutention_id']; ?>">
<div class="picker" data-item-picker-root data-endpoint="scmanutention.php?ajax=item_suggestions" data-min-query="2">
<div class="picker" data-item-picker-root data-endpoint="scmanutention.php?ajax=item_suggestions" data-min-query="2" data-page-size="25">
<input type="hidden" name="item_source" data-picker-source value="">
<input type="hidden" name="item_scobjs_id" data-picker-scobjs value="">
<input type="hidden" name="item_scitemcustom_id" data-picker-scitemcustom value="">
@ -1365,7 +1384,7 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
<div class="picker-dropdown hidden" data-picker-dropdown></div>
</div>
<div class="helper">La recherche démarre à partir de 2 caractères et distingue les objets de base des objets persos.</div>
<div class="helper">La recherche démarre à partir de 2 caractères, distingue les objets de base des objets persos, et charge la suite au scroll pour accéder à tous les résultats sans agrandir le panneau.</div>
<div class="picker-selection hidden" data-picker-selection>
<div class="picker-selection-main">
@ -1588,6 +1607,7 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
function initPicker(root) {
var endpoint = root.getAttribute('data-endpoint') || 'scmanutention.php?ajax=item_suggestions';
var minQuery = parseInt(root.getAttribute('data-min-query') || '2', 10);
var pageSize = parseInt(root.getAttribute('data-page-size') || '25', 10);
var input = root.querySelector('[data-picker-input]');
var dropdown = root.querySelector('[data-picker-dropdown]');
var sourceInput = root.querySelector('[data-picker-source]');
@ -1602,6 +1622,11 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
var requestTimer = null;
var requestToken = 0;
var selectedItem = null;
var loadedItems = [];
var currentQuery = '';
var nextOffset = 0;
var hasMore = false;
var isFetchingMore = false;
if (!input || !dropdown || !sourceInput || !scobjsInput || !scitemcustomInput || !selection || !nameNode || !metaNode || !imageNode) {
return;
@ -1618,6 +1643,11 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
function hideDropdown() {
dropdown.innerHTML = '';
dropdown.classList.add('hidden');
loadedItems = [];
currentQuery = '';
nextOffset = 0;
hasMore = false;
isFetchingMore = false;
}
function showDropdown() {
@ -1681,76 +1711,105 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
}
}
function renderItems(items, query) {
if (!Array.isArray(items) || items.length === 0) {
function buildOptionHtml(item, index) {
var meta = [];
if (item.type) {
meta.push(escapeHtml(item.type));
}
if (item.subtype) {
meta.push(escapeHtml(item.subtype));
}
if (item.sourceLabel) {
meta.push(escapeHtml(item.sourceLabel));
}
var statsPreview = Array.isArray(item.statsPreview) ? item.statsPreview : [];
var statsHtml = '';
if ((item.source || '') === 'custom') {
if (statsPreview.length > 0) {
statsHtml = '<div class="picker-option-stats">' +
statsPreview.map(function (statText) {
return '<span class="stat-pill">' + escapeHtml(statText) + '</span>';
}).join('') +
((item.statsPreviewMore || 0) > 0 ? '<span class="picker-option-more">+' + escapeHtml(item.statsPreviewMore) + '</span>' : '') +
'</div>';
} else {
statsHtml = '<div class="picker-option-stats picker-option-stats-empty">Aucune stat</div>';
}
}
return '' +
'<button type="button" class="picker-option' + (index === 0 ? ' is-active' : '') + '" ' +
'data-key="' + escapeHtml(item.key || '') + '" ' +
'data-source="' + escapeHtml(item.source || '') + '" ' +
'data-source-label="' + escapeHtml(item.sourceLabel || '') + '" ' +
'data-scobjs-id="' + escapeHtml(item.scobjs_id || '') + '" ' +
'data-scitemcustom-id="' + escapeHtml(item.scitemcustom_id || '') + '" ' +
'data-name="' + escapeHtml(item.name || '') + '" ' +
'data-type="' + escapeHtml(item.type || '') + '" ' +
'data-subtype="' + escapeHtml(item.subtype || '') + '" ' +
'data-uuid="' + escapeHtml(item.uuid || '') + '" ' +
'data-rarity="' + escapeHtml(item.rarity || '') + '">' +
'<div class="picker-option-row">' +
'<div class="picker-option-main">' +
'<strong class="item-name ' + escapeHtml(rarityClass(item.rarity || '')) + '">' + escapeHtml(item.name || '') + '</strong>' +
'<div class="item-submeta">' + meta.join(' / ') + '</div>' +
'</div>' +
statsHtml +
'</div>' +
'</button>';
}
function renderItems(items, query, append) {
var safeItems = Array.isArray(items) ? items : [];
loadedItems = append ? loadedItems.concat(safeItems) : safeItems.slice();
if (loadedItems.length === 0) {
dropdown.innerHTML = '<div class="picker-option"><strong>Aucun objet trouvé</strong><div class="item-submeta">Aucun résultat pour “' + escapeHtml(query) + '”.</div></div>';
showDropdown();
return;
}
dropdown.innerHTML = items.map(function (item, index) {
var meta = [];
if (item.type) {
meta.push(escapeHtml(item.type));
}
if (item.subtype) {
meta.push(escapeHtml(item.subtype));
}
if (item.sourceLabel) {
meta.push(escapeHtml(item.sourceLabel));
}
var html = loadedItems.map(buildOptionHtml).join('');
if (hasMore) {
html += '<div class="picker-status">Fais défiler pour charger la suite…</div>';
} else if (loadedItems.length > pageSize) {
html += '<div class="picker-status">Tous les résultats trouvés sont chargés.</div>';
}
var statsPreview = Array.isArray(item.statsPreview) ? item.statsPreview : [];
var statsHtml = '';
if ((item.source || '') === 'custom') {
if (statsPreview.length > 0) {
statsHtml = '<div class="picker-option-stats">' +
statsPreview.map(function (statText) {
return '<span class="stat-pill">' + escapeHtml(statText) + '</span>';
}).join('') +
((item.statsPreviewMore || 0) > 0 ? '<span class="picker-option-more">+' + escapeHtml(item.statsPreviewMore) + '</span>' : '') +
'</div>';
} else {
statsHtml = '<div class="picker-option-stats picker-option-stats-empty">Aucune stat</div>';
}
}
return '' +
'<button type="button" class="picker-option' + (index === 0 ? ' is-active' : '') + '" ' +
'data-key="' + escapeHtml(item.key || '') + '" ' +
'data-source="' + escapeHtml(item.source || '') + '" ' +
'data-source-label="' + escapeHtml(item.sourceLabel || '') + '" ' +
'data-scobjs-id="' + escapeHtml(item.scobjs_id || '') + '" ' +
'data-scitemcustom-id="' + escapeHtml(item.scitemcustom_id || '') + '" ' +
'data-name="' + escapeHtml(item.name || '') + '" ' +
'data-type="' + escapeHtml(item.type || '') + '" ' +
'data-subtype="' + escapeHtml(item.subtype || '') + '" ' +
'data-uuid="' + escapeHtml(item.uuid || '') + '" ' +
'data-rarity="' + escapeHtml(item.rarity || '') + '">' +
'<div class="picker-option-row">' +
'<div class="picker-option-main">' +
'<strong class="item-name ' + escapeHtml(rarityClass(item.rarity || '')) + '">' + escapeHtml(item.name || '') + '</strong>' +
'<div class="item-submeta">' + meta.join(' / ') + '</div>' +
'</div>' +
statsHtml +
'</div>' +
'</button>';
}).join('');
dropdown.innerHTML = html;
showDropdown();
}
function fetchSuggestions(query) {
function fetchSuggestions(query, options) {
var settings = options || {};
var append = !!settings.append;
var trimmedQuery = (query || '').trim();
if (trimmedQuery.length < minQuery) {
hideDropdown();
return;
}
if (!append) {
currentQuery = trimmedQuery;
nextOffset = 0;
hasMore = false;
isFetchingMore = false;
} else if (isFetchingMore || !hasMore) {
return;
}
var fetchOffset = append ? nextOffset : 0;
var separator = endpoint.indexOf('?') === -1 ? '?' : '&';
var url = endpoint + separator + 'q=' + encodeURIComponent(trimmedQuery);
var currentToken = ++requestToken;
dropdown.innerHTML = '<div class="picker-option"><strong>Recherche en cours...</strong><div class="item-submeta">Chargement des suggestions.</div></div>';
showDropdown();
var url = endpoint + separator + 'q=' + encodeURIComponent(trimmedQuery) + '&offset=' + encodeURIComponent(fetchOffset) + '&limit=' + encodeURIComponent(pageSize);
var currentToken = append ? requestToken : ++requestToken;
if (!append) {
dropdown.innerHTML = '<div class="picker-option"><strong>Recherche en cours...</strong><div class="item-submeta">Chargement des suggestions.</div></div>';
showDropdown();
} else {
isFetchingMore = true;
}
fetch(url, {
headers: { 'X-Requested-With': 'XMLHttpRequest' },
@ -1766,12 +1825,19 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
if (currentToken !== requestToken) {
return;
}
renderItems(payload && Array.isArray(payload.items) ? payload.items : [], trimmedQuery);
hasMore = !!(payload && payload.hasMore);
nextOffset = payload && typeof payload.nextOffset !== 'undefined'
? parseInt(payload.nextOffset, 10) || 0
: ((append ? loadedItems.length : 0) + ((payload && Array.isArray(payload.items)) ? payload.items.length : 0));
isFetchingMore = false;
renderItems(payload && Array.isArray(payload.items) ? payload.items : [], trimmedQuery, append);
})
.catch(function () {
if (currentToken !== requestToken) {
return;
}
isFetchingMore = false;
dropdown.innerHTML = '<div class="picker-option"><strong>Recherche indisponible</strong><div class="item-submeta">Impossible de charger les suggestions.</div></div>';
showDropdown();
});
@ -1797,6 +1863,16 @@ $page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manut
}
});
dropdown.addEventListener('scroll', function () {
if (!hasMore || isFetchingMore || dropdown.classList.contains('hidden')) {
return;
}
if (dropdown.scrollTop + dropdown.clientHeight >= dropdown.scrollHeight - 80) {
fetchSuggestions(currentQuery || input.value || '', { append: true });
}
});
dropdown.addEventListener('click', function (event) {
var option = event.target.closest('.picker-option[data-source]');
if (!option) {