39514-vm/scmanutention.php
Flatlogic Bot 3b2871e0a5 V1.4.7
2026-05-07 00:39:13 +00:00

1917 lines
82 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 dobjets, 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 ? Lancien 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 dobjets + 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 lobjet</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 lobjet..."
aria-label="Modifier lobjet"
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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>