1917 lines
82 KiB
PHP
1917 lines
82 KiB
PHP
<?php
|
||
|
||
require_once __DIR__ . '/db/auth.php';
|
||
require_once __DIR__ . '/db/scstatsitem.php';
|
||
require_once __DIR__ . '/db/scitemcustom.php';
|
||
require_once __DIR__ . '/db/scmanutention.php';
|
||
|
||
auth_start_session();
|
||
auth_bootstrap();
|
||
auth_handle_page_access_post('scmanutention.php', 'Manutention');
|
||
auth_require_page_access('scmanutention.php', 'Manutention');
|
||
scstatsitem_bootstrap();
|
||
scitemcustom_bootstrap();
|
||
scmanutention_bootstrap();
|
||
|
||
function scmanutention_current_owner_auth_id(PDO $db): int
|
||
{
|
||
$session_user = auth_current_user();
|
||
if ($session_user === '') {
|
||
return 0;
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'SELECT cl_auth_id
|
||
FROM tbl_auth
|
||
WHERE cl_auth_user = :user
|
||
LIMIT 1'
|
||
);
|
||
$stmt->execute(['user' => $session_user]);
|
||
|
||
return (int) $stmt->fetchColumn();
|
||
}
|
||
|
||
function scmanutention_redirect(int $sheet_id = 0, string $anchor = ''): void
|
||
{
|
||
$location = 'scmanutention.php';
|
||
if ($sheet_id > 0) {
|
||
$location .= '?sheet=' . $sheet_id;
|
||
}
|
||
if ($anchor !== '') {
|
||
$location .= '#' . rawurlencode(ltrim($anchor, '#'));
|
||
}
|
||
|
||
header('Location: ' . $location);
|
||
exit;
|
||
}
|
||
|
||
function scmanutention_rarity_class(string $rarity): string
|
||
{
|
||
$rarity = strtoupper(trim($rarity));
|
||
|
||
return in_array($rarity, ['L', 'E', 'R', 'U', 'C'], true) ? 'rarity-' . $rarity : '';
|
||
}
|
||
|
||
function scmanutention_item_name(array $item_row): string
|
||
{
|
||
$is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
|
||
|
||
return (string) ($is_custom
|
||
? ($item_row['cl_scmanutentionitem_custom_name'] ?? '')
|
||
: ($item_row['cl_scmanutentionitem_base_name'] ?? ''));
|
||
}
|
||
|
||
function scmanutention_item_type(array $item_row): string
|
||
{
|
||
$is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
|
||
|
||
return (string) ($is_custom
|
||
? ($item_row['cl_scmanutentionitem_custom_type'] ?? '')
|
||
: ($item_row['cl_scmanutentionitem_base_type'] ?? ''));
|
||
}
|
||
|
||
function scmanutention_item_subtype(array $item_row): string
|
||
{
|
||
$is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
|
||
|
||
return (string) ($is_custom
|
||
? ($item_row['cl_scmanutentionitem_custom_subtype'] ?? '')
|
||
: ($item_row['cl_scmanutentionitem_base_subtype'] ?? ''));
|
||
}
|
||
|
||
function scmanutention_item_uuid(array $item_row): string
|
||
{
|
||
$is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
|
||
|
||
return (string) ($is_custom
|
||
? ($item_row['cl_scmanutentionitem_custom_uuid'] ?? '')
|
||
: ($item_row['cl_scmanutentionitem_base_uuid'] ?? ''));
|
||
}
|
||
|
||
function scmanutention_item_rarity(array $item_row): string
|
||
{
|
||
$is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
|
||
|
||
return (string) ($is_custom
|
||
? ($item_row['cl_scmanutentionitem_custom_rarity'] ?? '')
|
||
: ($item_row['cl_scmanutentionitem_base_rarity'] ?? ''));
|
||
}
|
||
|
||
function scmanutention_format_stat_preview(array $stat_row): string
|
||
{
|
||
$sign = (string) ($stat_row['cl_scitemcustomstat_sign'] ?? '');
|
||
$prefix = $sign === '-' ? '-' : ($sign === '+' ? '+' : '');
|
||
$value = (float) ($stat_row['cl_scitemcustomstat_value'] ?? 0);
|
||
$formatted = number_format($value, 2, '.', '');
|
||
$formatted = rtrim(rtrim($formatted, '0'), '.');
|
||
if ($formatted === '') {
|
||
$formatted = '0';
|
||
}
|
||
|
||
$unit = trim((string) ($stat_row['cl_scstatsitem_unit'] ?? ''));
|
||
|
||
return trim((string) ($stat_row['cl_scstatsitem_name'] ?? '') . ' : ' . $prefix . $formatted . ($unit !== '' ? ' ' . $unit : ''));
|
||
}
|
||
|
||
$flash = auth_flash_get();
|
||
$flash_type = $flash['type'] ?? '';
|
||
$flash_message = $flash['message'] ?? '';
|
||
|
||
$db = db();
|
||
$csrf_token = auth_csrf_token();
|
||
$current_owner_auth_id = scmanutention_current_owner_auth_id($db);
|
||
$current_role_label = auth_role_label(auth_current_role());
|
||
$current_session_user = auth_current_user();
|
||
|
||
if ($current_owner_auth_id <= 0) {
|
||
auth_flash_set('error', 'Utilisateur introuvable. Merci de vous reconnecter.');
|
||
header('Location: logout.php');
|
||
exit;
|
||
}
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === 'item_suggestions') {
|
||
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);
|
||
$suggestion_custom_ids = [];
|
||
|
||
foreach ($suggestion_rows as $row) {
|
||
if (((string) ($row['result_source'] ?? '')) === 'custom' && !empty($row['result_scitemcustom_id'])) {
|
||
$suggestion_custom_ids[] = (int) $row['result_scitemcustom_id'];
|
||
}
|
||
}
|
||
|
||
$suggestion_stats_map = scmanutention_fetch_custom_stats_preview_map($db, $suggestion_custom_ids);
|
||
|
||
$items = array_map(static function (array $row) use ($suggestion_stats_map): array {
|
||
$custom_id = (int) ($row['result_scitemcustom_id'] ?? 0);
|
||
$custom_stats = $custom_id > 0 ? ($suggestion_stats_map[$custom_id] ?? []) : [];
|
||
$stats_preview = array_map('scmanutention_format_stat_preview', array_slice($custom_stats, 0, 4));
|
||
|
||
return [
|
||
'key' => (string) $row['result_key'],
|
||
'source' => (string) $row['result_source'],
|
||
'sourceLabel' => ((string) $row['result_source']) === 'custom' ? 'Objet perso' : 'Base d\'objets',
|
||
'scobjs_id' => (int) ($row['result_scobjs_id'] ?? 0),
|
||
'scitemcustom_id' => $custom_id,
|
||
'name' => (string) ($row['result_name'] ?? ''),
|
||
'type' => (string) ($row['result_type'] ?? ''),
|
||
'subtype' => (string) ($row['result_subtype'] ?? ''),
|
||
'uuid' => (string) ($row['result_uuid'] ?? ''),
|
||
'rarity' => (string) ($row['result_rarity'] ?? ''),
|
||
'statsPreview' => $stats_preview,
|
||
'statsPreviewMore' => max(0, count($custom_stats) - count($stats_preview)),
|
||
];
|
||
}, $suggestion_rows);
|
||
|
||
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
exit;
|
||
}
|
||
|
||
$selected_sheet_id = isset($_GET['sheet']) ? (int) $_GET['sheet'] : 0;
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$csrf = isset($_POST['csrf_token']) ? (string) $_POST['csrf_token'] : null;
|
||
if (!auth_validate_csrf($csrf)) {
|
||
auth_flash_set('error', 'Jeton CSRF invalide.');
|
||
scmanutention_redirect($selected_sheet_id);
|
||
}
|
||
|
||
$action = trim((string) ($_POST['action'] ?? ''));
|
||
|
||
if ($action === 'create_sheet') {
|
||
$title = scmanutention_clean_text($_POST['title'] ?? '');
|
||
$type = scmanutention_clean_text($_POST['type'] ?? '');
|
||
$subtype = scmanutention_clean_text($_POST['subtype'] ?? '');
|
||
$description = scmanutention_clean_text($_POST['description'] ?? '');
|
||
$share_enabled = isset($_POST['share_enabled']) ? 1 : 0;
|
||
|
||
if ($title === '') {
|
||
auth_flash_set('error', 'Le nom de la fiche est obligatoire.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'INSERT INTO tbl_scmanutentions (
|
||
cl_scmanutention_owner_auth_id,
|
||
cl_scmanutention_title,
|
||
cl_scmanutention_type,
|
||
cl_scmanutention_subtype,
|
||
cl_scmanutention_description,
|
||
cl_scmanutention_share_token,
|
||
cl_scmanutention_share_enabled
|
||
) VALUES (
|
||
:owner_auth_id,
|
||
:title,
|
||
:type,
|
||
:subtype,
|
||
:description,
|
||
:share_token,
|
||
:share_enabled
|
||
)'
|
||
);
|
||
$stmt->execute([
|
||
'owner_auth_id' => $current_owner_auth_id,
|
||
'title' => $title,
|
||
'type' => $type,
|
||
'subtype' => $subtype,
|
||
'description' => $description !== '' ? $description : null,
|
||
'share_token' => scmanutention_generate_share_token(),
|
||
'share_enabled' => $share_enabled,
|
||
]);
|
||
|
||
$new_sheet_id = (int) $db->lastInsertId();
|
||
auth_flash_set('success', 'Fiche créée avec succès.');
|
||
scmanutention_redirect($new_sheet_id, 'sheet-settings');
|
||
}
|
||
|
||
if ($action === 'update_sheet') {
|
||
$sheet_id = (int) ($_POST['sheet_id'] ?? 0);
|
||
$sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
|
||
if (!$sheet) {
|
||
auth_flash_set('error', 'Fiche introuvable.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
$title = scmanutention_clean_text($_POST['title'] ?? '');
|
||
$type = scmanutention_clean_text($_POST['type'] ?? '');
|
||
$subtype = scmanutention_clean_text($_POST['subtype'] ?? '');
|
||
$description = scmanutention_clean_text($_POST['description'] ?? '');
|
||
$share_enabled = isset($_POST['share_enabled']) ? 1 : 0;
|
||
|
||
if ($title === '') {
|
||
auth_flash_set('error', 'Le nom de la fiche est obligatoire.');
|
||
scmanutention_redirect($sheet_id, 'sheet-settings');
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'UPDATE tbl_scmanutentions
|
||
SET cl_scmanutention_title = :title,
|
||
cl_scmanutention_type = :type,
|
||
cl_scmanutention_subtype = :subtype,
|
||
cl_scmanutention_description = :description,
|
||
cl_scmanutention_share_enabled = :share_enabled
|
||
WHERE cl_scmanutention_id = :sheet_id'
|
||
);
|
||
$stmt->execute([
|
||
'title' => $title,
|
||
'type' => $type,
|
||
'subtype' => $subtype,
|
||
'description' => $description !== '' ? $description : null,
|
||
'share_enabled' => $share_enabled,
|
||
'sheet_id' => $sheet_id,
|
||
]);
|
||
|
||
auth_flash_set('success', 'Fiche mise à jour.');
|
||
scmanutention_redirect($sheet_id, 'sheet-settings');
|
||
}
|
||
|
||
if ($action === 'regenerate_share') {
|
||
$sheet_id = (int) ($_POST['sheet_id'] ?? 0);
|
||
$sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
|
||
if (!$sheet) {
|
||
auth_flash_set('error', 'Fiche introuvable.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'UPDATE tbl_scmanutentions
|
||
SET cl_scmanutention_share_token = :share_token
|
||
WHERE cl_scmanutention_id = :sheet_id'
|
||
);
|
||
$stmt->execute([
|
||
'share_token' => scmanutention_generate_share_token(),
|
||
'sheet_id' => $sheet_id,
|
||
]);
|
||
|
||
auth_flash_set('success', 'Lien public régénéré.');
|
||
scmanutention_redirect($sheet_id, 'sheet-settings');
|
||
}
|
||
|
||
if ($action === 'delete_sheet') {
|
||
$sheet_id = (int) ($_POST['sheet_id'] ?? 0);
|
||
$sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
|
||
if (!$sheet) {
|
||
auth_flash_set('error', 'Fiche introuvable.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
$stmt = $db->prepare('DELETE FROM tbl_scmanutentions WHERE cl_scmanutention_id = :sheet_id');
|
||
$stmt->execute(['sheet_id' => $sheet_id]);
|
||
|
||
auth_flash_set('success', 'Fiche supprimée.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
if ($action === 'add_item') {
|
||
$sheet_id = (int) ($_POST['sheet_id'] ?? 0);
|
||
$sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
|
||
if (!$sheet) {
|
||
auth_flash_set('error', 'Fiche introuvable.');
|
||
scmanutention_redirect();
|
||
}
|
||
|
||
$source = trim((string) ($_POST['item_source'] ?? ''));
|
||
$scobjs_id = (int) ($_POST['item_scobjs_id'] ?? 0);
|
||
$scitemcustom_id = (int) ($_POST['item_scitemcustom_id'] ?? 0);
|
||
$quantity = scmanutention_normalize_quantity($_POST['quantity'] ?? 1);
|
||
$extra_info = scmanutention_clean_text($_POST['extra_info'] ?? '');
|
||
|
||
$item_reference = scmanutention_validate_item_reference($db, $current_owner_auth_id, $source, $scobjs_id, $scitemcustom_id);
|
||
if (!$item_reference) {
|
||
auth_flash_set('error', 'Objet invalide ou inaccessible.');
|
||
scmanutention_redirect($sheet_id, 'item-add-card');
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'INSERT INTO tbl_scmanutentionitems (
|
||
cl_scmanutentionitem_manutention_id,
|
||
cl_scmanutentionitem_source,
|
||
cl_scmanutentionitem_scobjs_id,
|
||
cl_scmanutentionitem_scitemcustom_id,
|
||
cl_scmanutentionitem_quantity,
|
||
cl_scmanutentionitem_extra_info,
|
||
cl_scmanutentionitem_sort_order
|
||
) VALUES (
|
||
:sheet_id,
|
||
:source,
|
||
:scobjs_id,
|
||
:scitemcustom_id,
|
||
:quantity,
|
||
:extra_info,
|
||
:sort_order
|
||
)'
|
||
);
|
||
$stmt->execute([
|
||
'sheet_id' => $sheet_id,
|
||
'source' => $item_reference['source'],
|
||
'scobjs_id' => $item_reference['scobjs_id'] > 0 ? $item_reference['scobjs_id'] : null,
|
||
'scitemcustom_id' => $item_reference['scitemcustom_id'] > 0 ? $item_reference['scitemcustom_id'] : null,
|
||
'quantity' => $quantity,
|
||
'extra_info' => $extra_info !== '' ? $extra_info : null,
|
||
'sort_order' => scmanutention_next_item_sort_order($db, $sheet_id),
|
||
]);
|
||
|
||
auth_flash_set('success', 'Objet ajouté à la fiche.');
|
||
scmanutention_redirect($sheet_id, 'sheet-items');
|
||
}
|
||
|
||
if ($action === 'update_item') {
|
||
$item_id = (int) ($_POST['item_id'] ?? 0);
|
||
$item_row = scmanutention_find_owned_item($db, $item_id, $current_owner_auth_id);
|
||
if (!$item_row) {
|
||
auth_flash_set('error', 'Ligne introuvable.');
|
||
scmanutention_redirect($selected_sheet_id);
|
||
}
|
||
|
||
$sheet_id = (int) $item_row['cl_scmanutentionitem_manutention_id'];
|
||
$source = trim((string) ($_POST['item_source'] ?? ''));
|
||
$scobjs_id = (int) ($_POST['item_scobjs_id'] ?? 0);
|
||
$scitemcustom_id = (int) ($_POST['item_scitemcustom_id'] ?? 0);
|
||
$quantity = scmanutention_normalize_quantity($_POST['quantity'] ?? 1);
|
||
$extra_info = scmanutention_clean_text($_POST['extra_info'] ?? '');
|
||
|
||
$item_reference = scmanutention_validate_item_reference($db, $current_owner_auth_id, $source, $scobjs_id, $scitemcustom_id);
|
||
if (!$item_reference) {
|
||
auth_flash_set('error', 'Objet invalide ou inaccessible.');
|
||
scmanutention_redirect($sheet_id, 'manutention-item-' . $item_id);
|
||
}
|
||
|
||
$stmt = $db->prepare(
|
||
'UPDATE tbl_scmanutentionitems
|
||
SET cl_scmanutentionitem_source = :source,
|
||
cl_scmanutentionitem_scobjs_id = :scobjs_id,
|
||
cl_scmanutentionitem_scitemcustom_id = :scitemcustom_id,
|
||
cl_scmanutentionitem_quantity = :quantity,
|
||
cl_scmanutentionitem_extra_info = :extra_info
|
||
WHERE cl_scmanutentionitem_id = :item_id'
|
||
);
|
||
$stmt->execute([
|
||
'source' => $item_reference['source'],
|
||
'scobjs_id' => $item_reference['scobjs_id'] > 0 ? $item_reference['scobjs_id'] : null,
|
||
'scitemcustom_id' => $item_reference['scitemcustom_id'] > 0 ? $item_reference['scitemcustom_id'] : null,
|
||
'quantity' => $quantity,
|
||
'extra_info' => $extra_info !== '' ? $extra_info : null,
|
||
'item_id' => $item_id,
|
||
]);
|
||
|
||
auth_flash_set('success', 'Ligne mise à jour.');
|
||
scmanutention_redirect($sheet_id, 'manutention-item-' . $item_id);
|
||
}
|
||
|
||
if ($action === 'delete_item') {
|
||
$item_id = (int) ($_POST['item_id'] ?? 0);
|
||
$item_row = scmanutention_find_owned_item($db, $item_id, $current_owner_auth_id);
|
||
if (!$item_row) {
|
||
auth_flash_set('error', 'Ligne introuvable.');
|
||
scmanutention_redirect($selected_sheet_id);
|
||
}
|
||
|
||
$sheet_id = (int) $item_row['cl_scmanutentionitem_manutention_id'];
|
||
$stmt = $db->prepare('DELETE FROM tbl_scmanutentionitems WHERE cl_scmanutentionitem_id = :item_id');
|
||
$stmt->execute(['item_id' => $item_id]);
|
||
scmanutention_reindex_items($db, $sheet_id);
|
||
|
||
auth_flash_set('success', 'Ligne supprimée.');
|
||
scmanutention_redirect($sheet_id, 'sheet-items');
|
||
}
|
||
|
||
auth_flash_set('error', 'Action inconnue.');
|
||
scmanutention_redirect($selected_sheet_id);
|
||
}
|
||
|
||
$stmt_sheets = $db->prepare(
|
||
'SELECT
|
||
m.*,
|
||
COUNT(mi.cl_scmanutentionitem_id) AS cl_item_total
|
||
FROM tbl_scmanutentions m
|
||
LEFT JOIN tbl_scmanutentionitems mi ON mi.cl_scmanutentionitem_manutention_id = m.cl_scmanutention_id
|
||
WHERE m.cl_scmanutention_owner_auth_id = :owner_auth_id
|
||
GROUP BY m.cl_scmanutention_id
|
||
ORDER BY m.cl_scmanutention_updated_at DESC, m.cl_scmanutention_id DESC'
|
||
);
|
||
$stmt_sheets->execute(['owner_auth_id' => $current_owner_auth_id]);
|
||
$sheets = $stmt_sheets->fetchAll() ?: [];
|
||
|
||
if ($selected_sheet_id <= 0 && !empty($sheets)) {
|
||
$selected_sheet_id = (int) $sheets[0]['cl_scmanutention_id'];
|
||
}
|
||
|
||
$selected_sheet = $selected_sheet_id > 0
|
||
? scmanutention_find_owned_sheet($db, $selected_sheet_id, $current_owner_auth_id)
|
||
: null;
|
||
|
||
$selected_items = $selected_sheet
|
||
? scmanutention_fetch_items($db, (int) $selected_sheet['cl_scmanutention_id'])
|
||
: [];
|
||
$custom_stats_map = scmanutention_fetch_custom_stats_map($db, $selected_items);
|
||
$page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manutention');
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Manutention</title>
|
||
<link rel="stylesheet" type="text/css" href="css/styles.css">
|
||
<link rel="stylesheet" type="text/css" href="css/default.css">
|
||
<style>
|
||
:root {
|
||
--primary: #a29b78;
|
||
--primary-soft: rgba(162, 155, 120, 0.18);
|
||
--primary-border: rgba(162, 155, 120, 0.34);
|
||
--surface: rgba(12, 16, 24, 0.86);
|
||
--surface-soft: rgba(255, 255, 255, 0.04);
|
||
--surface-border: rgba(255, 255, 255, 0.08);
|
||
--text-main: #f3efe2;
|
||
--text-soft: rgba(243, 239, 226, 0.72);
|
||
--danger: #ff7d7d;
|
||
--success: #9fe29f;
|
||
--rarity-L: #ff8000;
|
||
--rarity-E: #a335ee;
|
||
--rarity-R: #0070dd;
|
||
--rarity-U: #1eff00;
|
||
--rarity-C: #ffffff;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
background:
|
||
linear-gradient(rgba(8, 10, 15, 0.8), rgba(8, 10, 15, 0.92)),
|
||
url('https://robertsspaceindustries.com/media/1vllgn95062syr/background_blur/REACT-Background.jpg') center/cover fixed;
|
||
color: var(--text-main);
|
||
font-family: Arial, Helvetica, sans-serif;
|
||
}
|
||
|
||
.admin-layout {
|
||
width: min(1480px, calc(100vw - 2rem));
|
||
margin: 0 auto;
|
||
padding: 1.2rem 0 2rem;
|
||
}
|
||
|
||
.admin-topbar,
|
||
.panel,
|
||
.item-card,
|
||
.sheet-card,
|
||
.picker-selection,
|
||
.picker-dropdown,
|
||
.flash {
|
||
background: var(--surface);
|
||
border: 1px solid var(--primary-border);
|
||
border-radius: 18px;
|
||
box-shadow: 0 18px 42px rgba(0, 0, 0, 0.32);
|
||
backdrop-filter: blur(12px);
|
||
}
|
||
|
||
.admin-topbar {
|
||
padding: 1.25rem 1.35rem;
|
||
margin-bottom: 1rem;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.topbar-info h1 {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
background: linear-gradient(90deg, #fff, var(--primary));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.topbar-info p {
|
||
margin: 0.25rem 0 0;
|
||
color: var(--primary);
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.topbar-actions {
|
||
display: flex;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.session-user {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.nav-tabs {
|
||
display: flex;
|
||
gap: 1rem;
|
||
margin-bottom: 2rem;
|
||
border-bottom: 1px solid var(--primary-border);
|
||
padding-bottom: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.nav-tabs a {
|
||
text-decoration: none;
|
||
color: #888;
|
||
text-transform: uppercase;
|
||
font-size: 0.9rem;
|
||
transition: color 0.3s;
|
||
}
|
||
|
||
.nav-tabs a:hover,
|
||
.nav-tabs a.active {
|
||
color: var(--primary);
|
||
}
|
||
|
||
.flash {
|
||
padding: 0.9rem 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.flash.success { border-color: rgba(159, 226, 159, 0.44); color: var(--success); }
|
||
.flash.error { border-color: rgba(255, 125, 125, 0.44); color: #ffd1d1; }
|
||
|
||
.layout {
|
||
display: grid;
|
||
grid-template-columns: 360px minmax(0, 1fr);
|
||
gap: 1rem;
|
||
align-items: start;
|
||
}
|
||
|
||
.stack {
|
||
display: grid;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.panel {
|
||
padding: 1rem;
|
||
position: relative;
|
||
overflow: visible;
|
||
}
|
||
|
||
.panel h2,
|
||
.panel h3 {
|
||
margin: 0 0 0.8rem;
|
||
}
|
||
|
||
#item-add-card {
|
||
z-index: 120;
|
||
}
|
||
|
||
#sheet-items {
|
||
z-index: 10;
|
||
}
|
||
|
||
.panel p {
|
||
margin-top: 0;
|
||
color: var(--text-soft);
|
||
}
|
||
|
||
.sheet-list {
|
||
display: grid;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.sheet-card {
|
||
padding: 0.9rem;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
display: grid;
|
||
gap: 0.55rem;
|
||
transition: transform 0.15s ease, border-color 0.15s ease;
|
||
}
|
||
|
||
.sheet-card:hover { transform: translateY(-1px); }
|
||
.sheet-card.is-active {
|
||
border-color: rgba(162, 155, 120, 0.58);
|
||
background: rgba(162, 155, 120, 0.14);
|
||
}
|
||
|
||
.sheet-meta,
|
||
.item-meta,
|
||
.hero-meta,
|
||
.stats-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.45rem;
|
||
align-items: center;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0.34rem 0.62rem;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
font-size: 0.8rem;
|
||
color: var(--text-soft);
|
||
}
|
||
|
||
.badge.public { color: #d8ffd8; border-color: rgba(159, 226, 159, 0.34); }
|
||
.badge.private { color: #ffd8d8; border-color: rgba(255, 125, 125, 0.28); }
|
||
.badge.source-custom { color: #d9c2ff; border-color: rgba(163, 53, 238, 0.35); }
|
||
|
||
.form-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.field {
|
||
display: grid;
|
||
gap: 0.38rem;
|
||
}
|
||
|
||
.field.full { grid-column: 1 / -1; }
|
||
|
||
.field label {
|
||
font-size: 0.84rem;
|
||
color: var(--text-soft);
|
||
}
|
||
|
||
.input,
|
||
.textarea,
|
||
.picker-input {
|
||
width: 100%;
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-main);
|
||
padding: 0.82rem 0.95rem;
|
||
outline: none;
|
||
}
|
||
|
||
.textarea {
|
||
min-height: 110px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.checkbox-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.btn-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.65rem;
|
||
margin-top: 0.95rem;
|
||
}
|
||
|
||
.btn,
|
||
.btn-modern {
|
||
padding: 0.6rem 1.2rem;
|
||
border: 1px solid var(--primary);
|
||
background: transparent;
|
||
color: #fff;
|
||
font-size: 0.9rem;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
border-radius: 4px;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 5px;
|
||
box-sizing: border-box;
|
||
appearance: none;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.btn:hover,
|
||
.btn-modern:hover {
|
||
background: var(--primary);
|
||
color: #111;
|
||
box-shadow: 0 0 15px rgba(162, 155, 120, 0.35);
|
||
}
|
||
|
||
.btn-secondary {
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-danger {
|
||
border-color: var(--danger);
|
||
color: var(--danger);
|
||
}
|
||
|
||
.btn-danger:hover {
|
||
background: var(--danger);
|
||
color: #fff;
|
||
box-shadow: 0 0 15px rgba(255, 125, 125, 0.22);
|
||
}
|
||
|
||
.btn-small {
|
||
padding: 0.45rem 0.75rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.hero {
|
||
display: grid;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.hero-title-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
align-items: start;
|
||
}
|
||
|
||
.hero h2 {
|
||
margin: 0;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.hero-description {
|
||
margin: 0;
|
||
color: var(--text-soft);
|
||
}
|
||
|
||
.share-box {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
gap: 0.7rem;
|
||
align-items: center;
|
||
}
|
||
|
||
.share-box input {
|
||
min-width: 0;
|
||
}
|
||
|
||
.picker {
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
position: relative;
|
||
z-index: 40;
|
||
}
|
||
|
||
.picker-wrap {
|
||
position: relative;
|
||
z-index: 50;
|
||
overflow: visible;
|
||
}
|
||
|
||
.picker-dropdown {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: calc(100% + 0.45rem);
|
||
padding: 0.4rem;
|
||
z-index: 9999;
|
||
max-height: 320px;
|
||
overflow-y: auto;
|
||
box-shadow: 0 22px 40px rgba(0, 0, 0, 0.38);
|
||
}
|
||
|
||
.picker-dropdown.hidden,
|
||
.picker-selection.hidden {
|
||
display: none;
|
||
}
|
||
|
||
.picker-option {
|
||
width: 100%;
|
||
display: grid;
|
||
gap: 0.3rem;
|
||
text-align: left;
|
||
background: transparent;
|
||
color: var(--text-main);
|
||
border: 0;
|
||
border-radius: 12px;
|
||
padding: 0.75rem 0.8rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.picker-option-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
gap: 0.8rem;
|
||
align-items: start;
|
||
}
|
||
|
||
.picker-option-main {
|
||
min-width: 0;
|
||
display: grid;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.picker-option-stats {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
align-items: flex-start;
|
||
gap: 0.35rem;
|
||
max-width: min(48%, 420px);
|
||
}
|
||
|
||
.picker-option-stats .stat-pill {
|
||
min-height: 26px;
|
||
padding: 0.28rem 0.55rem;
|
||
font-size: 0.74rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.picker-option-stats-empty {
|
||
align-self: center;
|
||
color: var(--text-soft);
|
||
font-size: 0.82rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.picker-option-more {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 26px;
|
||
padding: 0.28rem 0.55rem;
|
||
border-radius: 12px;
|
||
background: rgba(255,255,255,0.08);
|
||
border: 1px solid rgba(255,255,255,0.14);
|
||
color: var(--text-soft);
|
||
font-size: 0.74rem;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.picker-option:hover,
|
||
.picker-option.is-active {
|
||
background: rgba(255, 255, 255, 0.07);
|
||
}
|
||
|
||
.picker-selection {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 0.8rem;
|
||
align-items: center;
|
||
padding: 0.8rem;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.picker-selection-main,
|
||
.picker-item-main {
|
||
display: flex;
|
||
gap: 0.8rem;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
.item-preview {
|
||
width: 54px;
|
||
height: 54px;
|
||
object-fit: cover;
|
||
border-radius: 14px;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
flex: none;
|
||
}
|
||
|
||
.picker-item-text,
|
||
.item-text {
|
||
min-width: 0;
|
||
display: grid;
|
||
gap: 5px;
|
||
}
|
||
|
||
.item-name {
|
||
font-size: 1.02rem;
|
||
font-weight: 700;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.rarity-L { color: var(--rarity-L); text-shadow: 0 0 12px rgba(255, 128, 0, 0.28); }
|
||
.rarity-E { color: var(--rarity-E); text-shadow: 0 0 12px rgba(163, 53, 238, 0.28); }
|
||
.rarity-R { color: var(--rarity-R); text-shadow: 0 0 12px rgba(0, 112, 221, 0.28); }
|
||
.rarity-U { color: var(--rarity-U); text-shadow: 0 0 12px rgba(30, 255, 0, 0.28); }
|
||
.rarity-C { color: var(--rarity-C); }
|
||
|
||
.muted,
|
||
.item-submeta,
|
||
.helper {
|
||
color: var(--text-soft);
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.items-list {
|
||
display: grid;
|
||
gap: 0.9rem;
|
||
}
|
||
|
||
.item-card {
|
||
padding: 0.95rem;
|
||
display: grid;
|
||
gap: 0.8rem;
|
||
position: relative;
|
||
isolation: isolate;
|
||
}
|
||
.item-head {
|
||
display: flex;
|
||
gap: 0.9rem;
|
||
align-items: start;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.item-title-line {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.45rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.item-quantity-prefix {
|
||
font-weight: 800;
|
||
color: #fff2ca;
|
||
letter-spacing: 0.02em;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.item-extra {
|
||
padding: 0.7rem 0.85rem;
|
||
border-radius: 14px;
|
||
background: rgba(255,255,255,0.04);
|
||
border: 1px solid rgba(255,255,255,0.07);
|
||
color: var(--text-main);
|
||
}
|
||
.item-actions {
|
||
display: flex;
|
||
gap: 0.55rem;
|
||
flex-wrap: wrap;
|
||
align-items: flex-start;
|
||
justify-content: flex-end;
|
||
margin-left: auto;
|
||
flex: none;
|
||
position: relative;
|
||
}
|
||
|
||
details.item-edit {
|
||
position: relative;
|
||
flex: none;
|
||
}
|
||
|
||
details.item-edit summary.item-edit-toggle {
|
||
list-style: none;
|
||
white-space: nowrap;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
details.item-edit summary.item-edit-toggle::-webkit-details-marker { display: none; }
|
||
|
||
details.item-edit[open] summary.item-edit-toggle {
|
||
background: var(--primary);
|
||
color: #111;
|
||
box-shadow: 0 0 15px rgba(162, 155, 120, 0.35);
|
||
}
|
||
|
||
.item-edit-backdrop {
|
||
display: none;
|
||
}
|
||
|
||
details.item-edit[open] .item-edit-backdrop {
|
||
display: block;
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 10000;
|
||
background: rgba(6, 10, 18, 0.78);
|
||
backdrop-filter: blur(3px);
|
||
}
|
||
|
||
.item-edit-panel {
|
||
display: none;
|
||
}
|
||
|
||
details.item-edit[open] .item-edit-panel {
|
||
display: block;
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
right: auto;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 10001;
|
||
width: min(860px, calc(100vw - 2rem));
|
||
max-height: calc(100vh - 2rem);
|
||
overflow-y: auto;
|
||
padding: 1rem;
|
||
border-radius: 18px;
|
||
background: rgba(11, 16, 26, 0.985);
|
||
border: 1px solid rgba(255,255,255,0.10);
|
||
box-shadow: 0 28px 64px rgba(0,0,0,0.52);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.item-edit-panel-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 0.8rem;
|
||
margin-bottom: 0.95rem;
|
||
}
|
||
|
||
.item-edit-panel-title {
|
||
display: grid;
|
||
gap: 0.28rem;
|
||
}
|
||
|
||
.item-edit-panel-title strong {
|
||
font-size: 1rem;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.item-edit-panel form {
|
||
margin: 0;
|
||
}
|
||
|
||
body.item-edit-open {
|
||
overflow: hidden;
|
||
}
|
||
|
||
.stats-list {
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.stat-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-height: 30px;
|
||
padding: 0.38rem 0.72rem;
|
||
border-radius: 12px;
|
||
background: linear-gradient(135deg, rgba(26, 74, 42, 0.9), rgba(18, 46, 28, 0.82));
|
||
border: 1px solid rgba(90, 255, 150, 0.85);
|
||
box-shadow: inset 0 0 0 1px rgba(140, 255, 188, 0.18), 0 0 0 1px rgba(34, 110, 58, 0.28), 0 10px 22px rgba(0,0,0,0.18);
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
color: #dcffe9;
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 1.3rem;
|
||
border-radius: 18px;
|
||
background: rgba(255,255,255,0.04);
|
||
border: 1px dashed rgba(255,255,255,0.18);
|
||
color: var(--text-soft);
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.layout {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 700px) {
|
||
.admin-layout {
|
||
width: min(100%, calc(100vw - 1rem));
|
||
}
|
||
|
||
.form-grid,
|
||
.share-box {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.picker-option-row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.picker-option-stats {
|
||
justify-content: flex-start;
|
||
max-width: 100%;
|
||
}
|
||
.picker-selection,
|
||
.hero-title-row,
|
||
.admin-topbar {
|
||
align-items: stretch;
|
||
}
|
||
|
||
.item-head {
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.item-actions {
|
||
width: 100%;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
details.item-edit[open] .item-edit-panel {
|
||
width: min(100vw - 1rem, 860px);
|
||
max-height: calc(100vh - 1rem);
|
||
padding: 0.9rem;
|
||
}
|
||
|
||
.item-edit-panel-head {
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<?php echo $page_access_widget; ?>
|
||
<div class="admin-layout">
|
||
<header class="admin-topbar">
|
||
<div class="topbar-info">
|
||
<h1>MANUTENTION</h1>
|
||
<p>Gestion de fiches privées/publiques avec ajout interactif d’objets, quantités et informations complémentaires.</p>
|
||
<p>Niveau d'accès : <strong><?php echo htmlspecialchars($current_role_label, ENT_QUOTES, 'UTF-8'); ?></strong></p>
|
||
</div>
|
||
<div class="topbar-actions">
|
||
<span class="session-user">Connecté : <strong><?php echo htmlspecialchars($current_session_user, ENT_QUOTES, 'UTF-8'); ?></strong></span>
|
||
<a href="index.php" class="btn-modern">Retour au site</a>
|
||
<a href="logout.php" class="btn-modern btn-danger">Déconnexion</a>
|
||
</div>
|
||
</header>
|
||
|
||
<?php echo auth_render_app_nav('scmanutention.php'); ?>
|
||
|
||
<?php if ($flash_message !== ''): ?>
|
||
<div class="flash <?php echo $flash_type === 'success' ? 'success' : 'error'; ?>">
|
||
<?php echo htmlspecialchars($flash_message, ENT_QUOTES, 'UTF-8'); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="layout">
|
||
<aside class="stack">
|
||
<?php if ($selected_sheet): ?>
|
||
<section class="panel" id="sheet-settings">
|
||
<h2>Paramètres de la fiche</h2>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="update_sheet">
|
||
<input type="hidden" name="sheet_id" value="<?php echo (int) $selected_sheet['cl_scmanutention_id']; ?>">
|
||
|
||
<div class="form-grid">
|
||
<div class="field full">
|
||
<label for="sheet-title">Nom de la fiche</label>
|
||
<input class="input" id="sheet-title" type="text" name="title" value="<?php echo htmlspecialchars((string) $selected_sheet['cl_scmanutention_title'], ENT_QUOTES, 'UTF-8'); ?>" required>
|
||
</div>
|
||
<div class="field">
|
||
<label for="sheet-type">Type</label>
|
||
<input class="input" id="sheet-type" type="text" name="type" value="<?php echo htmlspecialchars((string) ($selected_sheet['cl_scmanutention_type'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" placeholder="Magasin, échange, expédition...">
|
||
</div>
|
||
<div class="field">
|
||
<label for="sheet-subtype">Sous-type</label>
|
||
<input class="input" id="sheet-subtype" type="text" name="subtype" value="<?php echo htmlspecialchars((string) ($selected_sheet['cl_scmanutention_subtype'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" placeholder="Armes, composants, lots...">
|
||
</div>
|
||
<div class="field full">
|
||
<label for="sheet-description">Description</label>
|
||
<textarea class="textarea" id="sheet-description" name="description" placeholder="Résumé interne ou public de la fiche..."><?php echo htmlspecialchars((string) ($selected_sheet['cl_scmanutention_description'] ?? ''), ENT_QUOTES, 'UTF-8'); ?></textarea>
|
||
</div>
|
||
<div class="field full">
|
||
<label class="checkbox-row">
|
||
<input type="checkbox" name="share_enabled" value="1" <?php echo !empty($selected_sheet['cl_scmanutention_share_enabled']) ? 'checked' : ''; ?>>
|
||
<span>Rendre cette fiche publique via son lien dédié</span>
|
||
</label>
|
||
</div>
|
||
<div class="field full">
|
||
<label for="sheet-share-link">Lien dédié</label>
|
||
<div class="share-box">
|
||
<input class="input" id="sheet-share-link" type="text" readonly value="<?php echo htmlspecialchars(scmanutention_sheet_share_url((string) $selected_sheet['cl_scmanutention_share_token']), ENT_QUOTES, 'UTF-8'); ?>">
|
||
<button type="button" class="btn btn-secondary btn-small" data-copy-target="sheet-share-link">Copier</button>
|
||
</div>
|
||
<div class="helper">Le lien existe toujours, mais il ne devient consultable publiquement que si la case ci-dessus est cochée.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-row">
|
||
<button type="submit" class="btn">Enregistrer</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="btn-row">
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="regenerate_share">
|
||
<input type="hidden" name="sheet_id" value="<?php echo (int) $selected_sheet['cl_scmanutention_id']; ?>">
|
||
<button type="submit" class="btn btn-secondary btn-small" onclick="return confirm('Régénérer le lien public ? L’ancien lien cessera de fonctionner.');">Régénérer le lien</button>
|
||
</form>
|
||
<form method="post" onsubmit="return confirm('Supprimer cette fiche et toutes ses lignes ?');">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="delete_sheet">
|
||
<input type="hidden" name="sheet_id" value="<?php echo (int) $selected_sheet['cl_scmanutention_id']; ?>">
|
||
<button type="submit" class="btn btn-danger btn-small">Supprimer la fiche</button>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
<?php endif; ?>
|
||
|
||
<section class="panel">
|
||
<h2>Créer une nouvelle fiche</h2>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="create_sheet">
|
||
<div class="form-grid">
|
||
<div class="field full">
|
||
<label for="create-title">Nom</label>
|
||
<input class="input" id="create-title" type="text" name="title" placeholder="Ex : Stock armurerie - Alpha" required>
|
||
</div>
|
||
<div class="field">
|
||
<label for="create-type">Type</label>
|
||
<input class="input" id="create-type" type="text" name="type" placeholder="Magasin">
|
||
</div>
|
||
<div class="field">
|
||
<label for="create-subtype">Sous-type</label>
|
||
<input class="input" id="create-subtype" type="text" name="subtype" placeholder="Armes">
|
||
</div>
|
||
<div class="field full">
|
||
<label for="create-description">Description</label>
|
||
<textarea class="textarea" id="create-description" name="description" placeholder="Notes générales, contexte, contraintes..."></textarea>
|
||
</div>
|
||
<div class="field full">
|
||
<label class="checkbox-row">
|
||
<input type="checkbox" name="share_enabled" value="1">
|
||
<span>Créer directement la fiche en mode public</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button type="submit" class="btn">Créer la fiche</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="panel">
|
||
<h2>Mes fiches</h2>
|
||
<?php if (empty($sheets)): ?>
|
||
<div class="empty-state">Aucune fiche pour le moment. Crée la première à gauche pour commencer.</div>
|
||
<?php else: ?>
|
||
<div class="sheet-list">
|
||
<?php foreach ($sheets as $sheet_row): ?>
|
||
<?php
|
||
$sheet_id = (int) $sheet_row['cl_scmanutention_id'];
|
||
$is_active = $selected_sheet && (int) $selected_sheet['cl_scmanutention_id'] === $sheet_id;
|
||
$sheet_type = trim((string) ($sheet_row['cl_scmanutention_type'] ?? ''));
|
||
$sheet_subtype = trim((string) ($sheet_row['cl_scmanutention_subtype'] ?? ''));
|
||
?>
|
||
<a href="scmanutention.php?sheet=<?php echo $sheet_id; ?>" class="sheet-card<?php echo $is_active ? ' is-active' : ''; ?>">
|
||
<strong><?php echo htmlspecialchars((string) $sheet_row['cl_scmanutention_title'], ENT_QUOTES, 'UTF-8'); ?></strong>
|
||
<div class="sheet-meta">
|
||
<span class="badge <?php echo !empty($sheet_row['cl_scmanutention_share_enabled']) ? 'public' : 'private'; ?>"><?php echo !empty($sheet_row['cl_scmanutention_share_enabled']) ? 'Public' : 'Privé'; ?></span>
|
||
<span class="badge"><?php echo (int) ($sheet_row['cl_item_total'] ?? 0); ?> ligne(s)</span>
|
||
</div>
|
||
<div class="muted">
|
||
<?php if ($sheet_type !== ''): ?><?php echo htmlspecialchars($sheet_type, ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>
|
||
<?php if ($sheet_subtype !== ''): ?><?php echo $sheet_type !== '' ? ' / ' : ''; ?><?php echo htmlspecialchars($sheet_subtype, ENT_QUOTES, 'UTF-8'); ?><?php endif; ?>
|
||
<?php if ($sheet_type === '' && $sheet_subtype === ''): ?>Sans type renseigné<?php endif; ?>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</section>
|
||
</aside>
|
||
|
||
<main class="stack">
|
||
<?php if (!$selected_sheet): ?>
|
||
<section class="panel">
|
||
<h2>Aucune fiche sélectionnée</h2>
|
||
<div class="empty-state">Crée une fiche ou sélectionne-en une dans la colonne de gauche pour commencer à ajouter des objets.</div>
|
||
</section>
|
||
<?php else: ?>
|
||
<section class="panel hero">
|
||
<div class="hero-title-row">
|
||
<div>
|
||
<h2><?php echo htmlspecialchars((string) $selected_sheet['cl_scmanutention_title'], ENT_QUOTES, 'UTF-8'); ?></h2>
|
||
<div class="hero-meta">
|
||
<?php if (!empty($selected_sheet['cl_scmanutention_type'])): ?>
|
||
<span class="badge"><?php echo htmlspecialchars((string) $selected_sheet['cl_scmanutention_type'], ENT_QUOTES, 'UTF-8'); ?></span>
|
||
<?php endif; ?>
|
||
<?php if (!empty($selected_sheet['cl_scmanutention_subtype'])): ?>
|
||
<span class="badge"><?php echo htmlspecialchars((string) $selected_sheet['cl_scmanutention_subtype'], ENT_QUOTES, 'UTF-8'); ?></span>
|
||
<?php endif; ?>
|
||
<span class="badge <?php echo !empty($selected_sheet['cl_scmanutention_share_enabled']) ? 'public' : 'private'; ?>"><?php echo !empty($selected_sheet['cl_scmanutention_share_enabled']) ? 'Mode public actif' : 'Mode privé'; ?></span>
|
||
<span class="badge"><?php echo count($selected_items); ?> objet(s)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php if (!empty($selected_sheet['cl_scmanutention_description'])): ?>
|
||
<p class="hero-description"><?php echo nl2br(htmlspecialchars((string) $selected_sheet['cl_scmanutention_description'], ENT_QUOTES, 'UTF-8')); ?></p>
|
||
<?php endif; ?>
|
||
<div class="share-box">
|
||
<input class="input" id="hero-share-link" type="text" readonly value="<?php echo htmlspecialchars(scmanutention_sheet_share_url((string) $selected_sheet['cl_scmanutention_share_token']), ENT_QUOTES, 'UTF-8'); ?>">
|
||
<button type="button" class="btn btn-secondary btn-small" data-copy-target="hero-share-link">Copier le lien</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" id="item-add-card">
|
||
<h2>Ajouter un objet</h2>
|
||
<p>Recherche interactive inspirée de la page objets perso : base d’objets + objets personnalisés, avec quantité et info libre.</p>
|
||
<form method="post" class="picker-form">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<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">
|
||
<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="">
|
||
|
||
<div class="picker-wrap">
|
||
<input
|
||
type="search"
|
||
class="picker-input"
|
||
placeholder="Rechercher un objet base ou perso..."
|
||
aria-label="Rechercher un objet"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
data-picker-input
|
||
>
|
||
<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="picker-selection hidden" data-picker-selection>
|
||
<div class="picker-selection-main">
|
||
<img src="" alt="" class="item-preview hidden" data-picker-image>
|
||
<div class="picker-item-text">
|
||
<strong class="item-name" data-picker-name></strong>
|
||
<div class="item-submeta" data-picker-meta></div>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-danger btn-small" data-picker-clear>Effacer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid" style="margin-top:0.95rem;">
|
||
<div class="field">
|
||
<label for="add-quantity">Quantité</label>
|
||
<input class="input" id="add-quantity" type="number" min="1" max="999999" name="quantity" value="1">
|
||
</div>
|
||
<div class="field">
|
||
<label for="add-extra-info">Info supplémentaire</label>
|
||
<input class="input" id="add-extra-info" type="text" name="extra_info" placeholder="Case libre facultative">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="btn-row">
|
||
<button type="submit" class="btn" data-picker-submit disabled>Ajouter à la fiche</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="panel" id="sheet-items">
|
||
<h2>Objets de la fiche</h2>
|
||
<?php if (empty($selected_items)): ?>
|
||
<div class="empty-state">Cette fiche ne contient encore aucun objet. Utilise la recherche ci-dessus pour commencer.</div>
|
||
<?php else: ?>
|
||
<div class="items-list">
|
||
<?php foreach ($selected_items as $item_row): ?>
|
||
<?php
|
||
$item_id = (int) $item_row['cl_scmanutentionitem_id'];
|
||
$item_name = scmanutention_item_name($item_row);
|
||
$item_type = scmanutention_item_type($item_row);
|
||
$item_subtype = scmanutention_item_subtype($item_row);
|
||
$item_uuid = scmanutention_item_uuid($item_row);
|
||
$item_rarity = scmanutention_item_rarity($item_row);
|
||
$item_rarity_class = scmanutention_rarity_class($item_rarity);
|
||
$item_source = (string) ($item_row['cl_scmanutentionitem_source'] ?? 'base');
|
||
$item_scobjs_id = (int) ($item_row['cl_scmanutentionitem_scobjs_id'] ?? 0);
|
||
$item_scitemcustom_id = (int) ($item_row['cl_scmanutentionitem_scitemcustom_id'] ?? 0);
|
||
$item_stats = $item_source === 'custom' ? ($custom_stats_map[$item_scitemcustom_id] ?? []) : [];
|
||
?>
|
||
<article class="item-card" id="manutention-item-<?php echo $item_id; ?>">
|
||
<div class="item-head">
|
||
<div class="picker-item-main">
|
||
<?php if ($item_uuid !== ''): ?>
|
||
<img src="https://cstone.space/uifimages/<?php echo htmlspecialchars($item_uuid, ENT_QUOTES, 'UTF-8'); ?>.png" alt="" class="item-preview">
|
||
<?php endif; ?>
|
||
<div class="item-text">
|
||
<div class="item-title-line">
|
||
<span class="item-quantity-prefix"><?php echo (int) ($item_row['cl_scmanutentionitem_quantity'] ?? 1); ?>x</span>
|
||
<strong class="item-name <?php echo htmlspecialchars($item_rarity_class, ENT_QUOTES, 'UTF-8'); ?>"><?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?></strong>
|
||
</div>
|
||
<div class="item-meta">
|
||
<span class="badge"><?php echo htmlspecialchars($item_type !== '' ? $item_type : 'Sans type', ENT_QUOTES, 'UTF-8'); ?></span>
|
||
<?php if ($item_subtype !== ''): ?>
|
||
<span class="badge"><?php echo htmlspecialchars($item_subtype, ENT_QUOTES, 'UTF-8'); ?></span>
|
||
<?php endif; ?>
|
||
<span class="badge <?php echo $item_source === 'custom' ? 'source-custom' : ''; ?>"><?php echo $item_source === 'custom' ? 'Objet perso' : 'Base d\'objets'; ?></span>
|
||
<?php if (!empty($item_stats)): ?>
|
||
<?php foreach ($item_stats as $stat_row): ?>
|
||
<span class="stat-pill"><?php echo htmlspecialchars(scmanutention_format_stat_preview($stat_row), ENT_QUOTES, 'UTF-8'); ?></span>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="item-actions">
|
||
<form method="post" onsubmit="return confirm('Supprimer cette ligne ?');">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="delete_item">
|
||
<input type="hidden" name="item_id" value="<?php echo $item_id; ?>">
|
||
<button type="submit" class="btn btn-danger btn-small">Supprimer</button>
|
||
</form>
|
||
|
||
<details class="item-edit" data-item-edit>
|
||
<summary class="btn btn-secondary btn-small item-edit-toggle">Modifier</summary>
|
||
<div class="item-edit-backdrop" data-item-edit-close aria-hidden="true"></div>
|
||
<div class="item-edit-panel" role="dialog" aria-modal="true" aria-label="Modifier <?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<div class="item-edit-panel-head">
|
||
<div class="item-edit-panel-title">
|
||
<strong>Modifier l’objet</strong>
|
||
<span class="item-submeta"><?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?></span>
|
||
</div>
|
||
<button type="button" class="btn btn-secondary btn-small" data-item-edit-close>Fermer</button>
|
||
</div>
|
||
<form method="post">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="action" value="update_item">
|
||
<input type="hidden" name="item_id" value="<?php echo $item_id; ?>">
|
||
|
||
<div
|
||
class="picker"
|
||
data-item-picker-root
|
||
data-endpoint="scmanutention.php?ajax=item_suggestions"
|
||
data-min-query="2"
|
||
data-initial-source="<?php echo htmlspecialchars($item_source, ENT_QUOTES, 'UTF-8'); ?>"
|
||
data-initial-scobjs="<?php echo $item_scobjs_id; ?>"
|
||
data-initial-scitemcustom="<?php echo $item_scitemcustom_id; ?>"
|
||
data-initial-name="<?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?>"
|
||
data-initial-type="<?php echo htmlspecialchars($item_type, ENT_QUOTES, 'UTF-8'); ?>"
|
||
data-initial-subtype="<?php echo htmlspecialchars($item_subtype, ENT_QUOTES, 'UTF-8'); ?>"
|
||
data-initial-uuid="<?php echo htmlspecialchars($item_uuid, ENT_QUOTES, 'UTF-8'); ?>"
|
||
data-initial-rarity="<?php echo htmlspecialchars($item_rarity, ENT_QUOTES, 'UTF-8'); ?>"
|
||
>
|
||
<input type="hidden" name="item_source" data-picker-source value="<?php echo htmlspecialchars($item_source, ENT_QUOTES, 'UTF-8'); ?>">
|
||
<input type="hidden" name="item_scobjs_id" data-picker-scobjs value="<?php echo $item_scobjs_id; ?>">
|
||
<input type="hidden" name="item_scitemcustom_id" data-picker-scitemcustom value="<?php echo $item_scitemcustom_id; ?>">
|
||
|
||
<div class="picker-wrap">
|
||
<input
|
||
type="search"
|
||
class="picker-input"
|
||
placeholder="Modifier l’objet..."
|
||
aria-label="Modifier l’objet"
|
||
autocomplete="off"
|
||
spellcheck="false"
|
||
data-picker-input
|
||
value="<?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?>"
|
||
>
|
||
<div class="picker-dropdown hidden" data-picker-dropdown></div>
|
||
</div>
|
||
|
||
<div class="picker-selection" data-picker-selection>
|
||
<div class="picker-selection-main">
|
||
<img src="<?php echo $item_uuid !== '' ? 'https://cstone.space/uifimages/' . htmlspecialchars($item_uuid, ENT_QUOTES, 'UTF-8') . '.png' : ''; ?>" alt="" class="item-preview<?php echo $item_uuid !== '' ? '' : ' hidden'; ?>" data-picker-image>
|
||
<div class="picker-item-text">
|
||
<strong class="item-name <?php echo htmlspecialchars($item_rarity_class, ENT_QUOTES, 'UTF-8'); ?>" data-picker-name><?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?></strong>
|
||
<div class="item-submeta" data-picker-meta>
|
||
<?php
|
||
$meta_parts = [];
|
||
if ($item_type !== '') {
|
||
$meta_parts[] = $item_type;
|
||
}
|
||
if ($item_subtype !== '') {
|
||
$meta_parts[] = $item_subtype;
|
||
}
|
||
$meta_parts[] = $item_source === 'custom' ? 'Objet perso' : 'Base d\'objets';
|
||
echo htmlspecialchars(implode(' / ', $meta_parts), ENT_QUOTES, 'UTF-8');
|
||
?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-danger btn-small" data-picker-clear>Effacer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-grid" style="margin-top:0.95rem;">
|
||
<div class="field">
|
||
<label>Quantité</label>
|
||
<input class="input" type="number" min="1" max="999999" name="quantity" value="<?php echo (int) ($item_row['cl_scmanutentionitem_quantity'] ?? 1); ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>Info supplémentaire</label>
|
||
<input class="input" type="text" name="extra_info" value="<?php echo htmlspecialchars((string) ($item_row['cl_scmanutentionitem_extra_info'] ?? ''), ENT_QUOTES, 'UTF-8'); ?>" placeholder="Case libre facultative">
|
||
</div>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button type="submit" class="btn" data-picker-submit>Enregistrer les modifications</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</article>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</section>
|
||
<?php endif; ?>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
function escapeHtml(value) {
|
||
return (value || '')
|
||
.toString()
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
function rarityClass(rarity) {
|
||
var value = (rarity || '').toString().trim().toUpperCase();
|
||
return ['L', 'E', 'R', 'U', 'C'].indexOf(value) !== -1 ? 'rarity-' + value : '';
|
||
}
|
||
|
||
Array.prototype.slice.call(document.querySelectorAll('[data-copy-target]')).forEach(function (button) {
|
||
button.addEventListener('click', function () {
|
||
var targetId = button.getAttribute('data-copy-target');
|
||
var input = targetId ? document.getElementById(targetId) : null;
|
||
if (!input) {
|
||
return;
|
||
}
|
||
|
||
input.focus();
|
||
input.select();
|
||
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(input.value).catch(function () {
|
||
document.execCommand('copy');
|
||
});
|
||
} else {
|
||
document.execCommand('copy');
|
||
}
|
||
});
|
||
});
|
||
|
||
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 input = root.querySelector('[data-picker-input]');
|
||
var dropdown = root.querySelector('[data-picker-dropdown]');
|
||
var sourceInput = root.querySelector('[data-picker-source]');
|
||
var scobjsInput = root.querySelector('[data-picker-scobjs]');
|
||
var scitemcustomInput = root.querySelector('[data-picker-scitemcustom]');
|
||
var selection = root.querySelector('[data-picker-selection]');
|
||
var nameNode = root.querySelector('[data-picker-name]');
|
||
var metaNode = root.querySelector('[data-picker-meta]');
|
||
var imageNode = root.querySelector('[data-picker-image]');
|
||
var clearButton = root.querySelector('[data-picker-clear]');
|
||
var submitButton = root.closest('form') ? root.closest('form').querySelector('[data-picker-submit]') : null;
|
||
var requestTimer = null;
|
||
var requestToken = 0;
|
||
var selectedItem = null;
|
||
|
||
if (!input || !dropdown || !sourceInput || !scobjsInput || !scitemcustomInput || !selection || !nameNode || !metaNode || !imageNode) {
|
||
return;
|
||
}
|
||
|
||
function updateSubmitState() {
|
||
if (!submitButton) {
|
||
return;
|
||
}
|
||
|
||
submitButton.disabled = !sourceInput.value || !scobjsInput.value;
|
||
}
|
||
|
||
function hideDropdown() {
|
||
dropdown.innerHTML = '';
|
||
dropdown.classList.add('hidden');
|
||
}
|
||
|
||
function showDropdown() {
|
||
dropdown.classList.remove('hidden');
|
||
}
|
||
|
||
function updateSelectedItem(item) {
|
||
selectedItem = item || null;
|
||
|
||
if (!selectedItem) {
|
||
sourceInput.value = '';
|
||
scobjsInput.value = '';
|
||
scitemcustomInput.value = '';
|
||
selection.classList.add('hidden');
|
||
nameNode.textContent = '';
|
||
nameNode.className = 'item-name';
|
||
metaNode.textContent = '';
|
||
imageNode.classList.add('hidden');
|
||
imageNode.setAttribute('src', '');
|
||
imageNode.setAttribute('alt', '');
|
||
updateSubmitState();
|
||
return;
|
||
}
|
||
|
||
sourceInput.value = selectedItem.source || '';
|
||
scobjsInput.value = selectedItem.scobjs_id || '';
|
||
scitemcustomInput.value = selectedItem.scitemcustom_id || '';
|
||
selection.classList.remove('hidden');
|
||
nameNode.textContent = selectedItem.name || '';
|
||
nameNode.className = 'item-name ' + rarityClass(selectedItem.rarity || '');
|
||
|
||
var meta = [];
|
||
if (selectedItem.type) {
|
||
meta.push(selectedItem.type);
|
||
}
|
||
if (selectedItem.subtype) {
|
||
meta.push(selectedItem.subtype);
|
||
}
|
||
if (selectedItem.sourceLabel) {
|
||
meta.push(selectedItem.sourceLabel);
|
||
}
|
||
metaNode.textContent = meta.join(' / ');
|
||
|
||
if (selectedItem.uuid) {
|
||
imageNode.classList.remove('hidden');
|
||
imageNode.setAttribute('src', 'https://cstone.space/uifimages/' + encodeURIComponent(selectedItem.uuid) + '.png');
|
||
imageNode.setAttribute('alt', selectedItem.name || 'Objet');
|
||
} else {
|
||
imageNode.classList.add('hidden');
|
||
imageNode.setAttribute('src', '');
|
||
imageNode.setAttribute('alt', '');
|
||
}
|
||
|
||
updateSubmitState();
|
||
}
|
||
|
||
function clearSelection(keepInput) {
|
||
updateSelectedItem(null);
|
||
if (!keepInput) {
|
||
input.value = '';
|
||
}
|
||
}
|
||
|
||
function renderItems(items, query) {
|
||
if (!Array.isArray(items) || items.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 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('');
|
||
showDropdown();
|
||
}
|
||
|
||
function fetchSuggestions(query) {
|
||
var trimmedQuery = (query || '').trim();
|
||
if (trimmedQuery.length < minQuery) {
|
||
hideDropdown();
|
||
return;
|
||
}
|
||
|
||
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();
|
||
|
||
fetch(url, {
|
||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||
credentials: 'same-origin'
|
||
})
|
||
.then(function (response) {
|
||
if (!response.ok) {
|
||
throw new Error('HTTP ' + response.status);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(function (payload) {
|
||
if (currentToken !== requestToken) {
|
||
return;
|
||
}
|
||
renderItems(payload && Array.isArray(payload.items) ? payload.items : [], trimmedQuery);
|
||
})
|
||
.catch(function () {
|
||
if (currentToken !== requestToken) {
|
||
return;
|
||
}
|
||
dropdown.innerHTML = '<div class="picker-option"><strong>Recherche indisponible</strong><div class="item-submeta">Impossible de charger les suggestions.</div></div>';
|
||
showDropdown();
|
||
});
|
||
}
|
||
|
||
input.addEventListener('input', function () {
|
||
clearSelection(true);
|
||
window.clearTimeout(requestTimer);
|
||
requestTimer = window.setTimeout(function () {
|
||
fetchSuggestions(input.value || '');
|
||
}, 120);
|
||
});
|
||
|
||
input.addEventListener('focus', function () {
|
||
if ((input.value || '').trim() !== '' && dropdown.innerHTML.trim() !== '') {
|
||
showDropdown();
|
||
}
|
||
});
|
||
|
||
input.addEventListener('keydown', function (event) {
|
||
if (event.key === 'Escape') {
|
||
hideDropdown();
|
||
}
|
||
});
|
||
|
||
dropdown.addEventListener('click', function (event) {
|
||
var option = event.target.closest('.picker-option[data-source]');
|
||
if (!option) {
|
||
return;
|
||
}
|
||
|
||
var item = {
|
||
key: option.getAttribute('data-key') || '',
|
||
source: option.getAttribute('data-source') || '',
|
||
sourceLabel: option.getAttribute('data-source-label') || '',
|
||
scobjs_id: option.getAttribute('data-scobjs-id') || '',
|
||
scitemcustom_id: option.getAttribute('data-scitemcustom-id') || '',
|
||
name: option.getAttribute('data-name') || '',
|
||
type: option.getAttribute('data-type') || '',
|
||
subtype: option.getAttribute('data-subtype') || '',
|
||
uuid: option.getAttribute('data-uuid') || '',
|
||
rarity: option.getAttribute('data-rarity') || ''
|
||
};
|
||
|
||
input.value = item.name || '';
|
||
updateSelectedItem(item);
|
||
hideDropdown();
|
||
});
|
||
|
||
document.addEventListener('click', function (event) {
|
||
if (!event.target.closest('[data-item-picker-root]')) {
|
||
hideDropdown();
|
||
}
|
||
});
|
||
|
||
if (clearButton) {
|
||
clearButton.addEventListener('click', function () {
|
||
clearSelection(false);
|
||
hideDropdown();
|
||
input.focus();
|
||
});
|
||
}
|
||
|
||
var initialSource = root.getAttribute('data-initial-source') || '';
|
||
var initialScobjs = root.getAttribute('data-initial-scobjs') || '';
|
||
if (initialSource && initialScobjs) {
|
||
updateSelectedItem({
|
||
source: initialSource,
|
||
sourceLabel: initialSource === 'custom' ? 'Objet perso' : 'Base d\'objets',
|
||
scobjs_id: initialScobjs,
|
||
scitemcustom_id: root.getAttribute('data-initial-scitemcustom') || '',
|
||
name: root.getAttribute('data-initial-name') || '',
|
||
type: root.getAttribute('data-initial-type') || '',
|
||
subtype: root.getAttribute('data-initial-subtype') || '',
|
||
uuid: root.getAttribute('data-initial-uuid') || '',
|
||
rarity: root.getAttribute('data-initial-rarity') || ''
|
||
});
|
||
} else {
|
||
updateSubmitState();
|
||
}
|
||
|
||
var form = root.closest('form');
|
||
if (form) {
|
||
form.addEventListener('submit', function (event) {
|
||
if (!sourceInput.value || !scobjsInput.value) {
|
||
event.preventDefault();
|
||
input.focus();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
function syncItemEditState() {
|
||
document.body.classList.toggle('item-edit-open', !!document.querySelector('details.item-edit[open]'));
|
||
}
|
||
|
||
Array.prototype.slice.call(document.querySelectorAll('details.item-edit')).forEach(function (detailsNode) {
|
||
detailsNode.addEventListener('toggle', function () {
|
||
if (detailsNode.open) {
|
||
Array.prototype.slice.call(document.querySelectorAll('details.item-edit[open]')).forEach(function (otherNode) {
|
||
if (otherNode !== detailsNode) {
|
||
otherNode.open = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
syncItemEditState();
|
||
});
|
||
});
|
||
|
||
document.addEventListener('click', function (event) {
|
||
var closeTrigger = event.target.closest('[data-item-edit-close]');
|
||
if (!closeTrigger) {
|
||
return;
|
||
}
|
||
|
||
var detailsNode = closeTrigger.closest('details.item-edit');
|
||
if (!detailsNode) {
|
||
return;
|
||
}
|
||
|
||
detailsNode.open = false;
|
||
syncItemEditState();
|
||
});
|
||
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key !== 'Escape') {
|
||
return;
|
||
}
|
||
|
||
Array.prototype.slice.call(document.querySelectorAll('details.item-edit[open]')).forEach(function (detailsNode) {
|
||
detailsNode.open = false;
|
||
});
|
||
syncItemEditState();
|
||
});
|
||
|
||
Array.prototype.slice.call(document.querySelectorAll('[data-item-picker-root]')).forEach(initPicker);
|
||
syncItemEditState();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|