diff --git a/assets/pasted-20260416-003522-c5c88e5c.png b/assets/pasted-20260416-003522-c5c88e5c.png new file mode 100644 index 0000000..2efb2ad Binary files /dev/null and b/assets/pasted-20260416-003522-c5c88e5c.png differ diff --git a/assets/pasted-20260416-004050-063b479c.png b/assets/pasted-20260416-004050-063b479c.png new file mode 100644 index 0000000..9d1d802 Binary files /dev/null and b/assets/pasted-20260416-004050-063b479c.png differ diff --git a/database/full.sql b/database/full.sql index 3e2cbb5..43f0078 100644 --- a/database/full.sql +++ b/database/full.sql @@ -18757,6 +18757,7 @@ CREATE TABLE IF NOT EXISTS tbl_sccharacteritems ( cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL, cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '', + cl_sccharacteritem_quantity INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_note TEXT DEFAULT NULL, cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_sccharacteritem_character (cl_sccharacteritem_character_id), diff --git a/database/schema.sql b/database/schema.sql index 9b0ee00..ae6fe80 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -236,6 +236,7 @@ CREATE TABLE IF NOT EXISTS tbl_sccharacteritems ( cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL, cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '', + cl_sccharacteritem_quantity INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_note TEXT DEFAULT NULL, cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_sccharacteritem_character (cl_sccharacteritem_character_id), diff --git a/db/sccharacters.php b/db/sccharacters.php index d5ecbb5..54c1b55 100644 --- a/db/sccharacters.php +++ b/db/sccharacters.php @@ -138,6 +138,7 @@ function sccharacters_bootstrap(): void cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL, cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '', + cl_sccharacteritem_quantity INT UNSIGNED DEFAULT NULL, cl_sccharacteritem_note TEXT DEFAULT NULL, cl_sccharacteritem_sort_order INT UNSIGNED NOT NULL DEFAULT 0, cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -175,6 +176,13 @@ function sccharacters_bootstrap(): void ); } + if (!sccharacters_column_exists($db, 'tbl_sccharacteritems', 'cl_sccharacteritem_quantity')) { + $db->exec( + 'ALTER TABLE tbl_sccharacteritems + ADD COLUMN cl_sccharacteritem_quantity INT UNSIGNED NULL AFTER cl_sccharacteritem_slot' + ); + } + if (!sccharacters_column_exists($db, 'tbl_sccharacteritems', 'cl_sccharacteritem_sort_order')) { $db->exec( 'ALTER TABLE tbl_sccharacteritems diff --git a/sccharacter.php b/sccharacter.php index cb55202..83e3e1f 100644 --- a/sccharacter.php +++ b/sccharacter.php @@ -221,7 +221,7 @@ $character_items_by_category = sccharacters_sort_items_by_category_order( .equipment-sections { display: flex; flex-direction: column; - gap: 1rem; + gap: 2.2rem; } .equipment-section { @@ -233,7 +233,6 @@ $character_items_by_category = sccharacters_sort_items_by_category_order( .equipment-section-head { display: flex; align-items: center; - justify-content: space-between; gap: 0.8rem; } @@ -243,6 +242,19 @@ $character_items_by_category = sccharacters_sort_items_by_category_order( letter-spacing: 0.08em; text-transform: uppercase; font-size: 1rem; + display: flex; + align-items: center; + gap: 0.85rem; + width: 100%; + } + + .equipment-section-title::after { + content: ''; + flex: 1 1 auto; + height: 2px; + background: currentColor; + border-radius: 999px; + opacity: 0.95; } .grid { @@ -346,7 +358,6 @@ $character_items_by_category = sccharacters_sort_items_by_category_order( -

Équipement

Ce personnage n’a pas encore d’équipement attribué.

@@ -357,7 +368,6 @@ $character_items_by_category = sccharacters_sort_items_by_category_order(

- objet(s)
@@ -380,6 +390,10 @@ $character_items_by_category = sccharacters_sort_items_by_category_order( $type, $subtype ); + $quantity = isset($item_row['cl_sccharacteritem_quantity']) && (int) $item_row['cl_sccharacteritem_quantity'] > 0 + ? (int) $item_row['cl_sccharacteritem_quantity'] + : null; + $title = $quantity !== null ? $quantity . 'x ' . $name : $name; $note = trim((string) ($item_row['cl_sccharacteritem_note'] ?? '')); $stats = $is_custom ? ($custom_stats_by_itemcustom[(int) ($item_row['cl_sccharacteritem_scitemcustom_id'] ?? 0)] ?? []) : []; ?> @@ -390,7 +404,7 @@ $character_items_by_category = sccharacters_sort_items_by_category_order(
-

+

diff --git a/sccharacters.php b/sccharacters.php index f44d1f7..9e9570a 100644 --- a/sccharacters.php +++ b/sccharacters.php @@ -36,6 +36,26 @@ function sccharacters_clean_text(?string $value): string return trim((string) $value); } +function sccharacters_normalize_item_quantity($value): ?int +{ + if ($value === null) { + return null; + } + + $value = trim((string) $value); + if ($value === '' || !preg_match('/^\d+$/', $value)) { + return null; + } + + $quantity = (int) $value; + + if ($quantity <= 0) { + return null; + } + + return min($quantity, 999999); +} + function sccharacters_is_valid_url(string $value): bool { if ($value === '') { @@ -203,7 +223,7 @@ function sccharacters_parse_item_key(string $item_key): ?array ]; } -function sccharacters_merge_selected_items(array $staged_items, array $selected_items, array $item_slots, array $item_notes): array +function sccharacters_merge_selected_items(array $staged_items, array $selected_items, array $item_slots, array $item_notes, array $item_quantities = []): array { $selected_items = array_values(array_unique(array_map('strval', $selected_items))); $staged_count = 0; @@ -221,6 +241,7 @@ function sccharacters_merge_selected_items(array $staged_items, array $selected_ 'source_id' => $parsed['source_id'], 'category' => sccharacters_clean_text($item_slots[$item_key] ?? ''), 'note' => sccharacters_clean_text($item_notes[$item_key] ?? ''), + 'quantity' => sccharacters_normalize_item_quantity($item_quantities[$item_key] ?? null), ]; $staged_count++; } @@ -627,6 +648,7 @@ function sccharacters_attach_item( string $source, int $source_id, string $requested_category, + ?int $quantity, string $note, ?string &$error_message = null ): bool { @@ -671,6 +693,7 @@ function sccharacters_attach_item( cl_sccharacteritem_scobjs_id, cl_sccharacteritem_scitemcustom_id, cl_sccharacteritem_slot, + cl_sccharacteritem_quantity, cl_sccharacteritem_note, cl_sccharacteritem_sort_order ) VALUES ( @@ -679,6 +702,7 @@ function sccharacters_attach_item( :scobjs_id, NULL, :slot, + :quantity, :note, :sort_order )' @@ -688,6 +712,7 @@ function sccharacters_attach_item( 'source' => 'base', 'scobjs_id' => $source_id, 'slot' => $category, + 'quantity' => $quantity, 'note' => $note !== '' ? $note : null, 'sort_order' => $sort_order, ]); @@ -728,6 +753,7 @@ function sccharacters_attach_item( cl_sccharacteritem_scobjs_id, cl_sccharacteritem_scitemcustom_id, cl_sccharacteritem_slot, + cl_sccharacteritem_quantity, cl_sccharacteritem_note, cl_sccharacteritem_sort_order ) VALUES ( @@ -736,6 +762,7 @@ function sccharacters_attach_item( NULL, :scitemcustom_id, :slot, + :quantity, :note, :sort_order )' @@ -745,6 +772,7 @@ function sccharacters_attach_item( 'source' => 'custom', 'scitemcustom_id' => $source_id, 'slot' => $category, + 'quantity' => $quantity, 'note' => $note !== '' ? $note : null, 'sort_order' => $sort_order, ]); @@ -756,6 +784,79 @@ function sccharacters_attach_item( return false; } +function sccharacters_update_character_item( + PDO $db, + int $owner_auth_id, + int $character_id, + int $character_item_id, + string $requested_category, + ?int $quantity, + string $note, + ?string &$error_message = null +): bool { + if ($character_id <= 0 || $owner_auth_id <= 0 || $character_item_id <= 0) { + $error_message = 'Paramètres de mise à jour invalides.'; + return false; + } + + $stmt_item = $db->prepare( + "SELECT + ci.cl_sccharacteritem_id, + ci.cl_sccharacteritem_source, + bo.cl_scobjs_type AS cl_sccharacteritem_base_type, + bo.cl_scobjs_subtype AS cl_sccharacteritem_base_subtype, + oo.cl_scobjs_type AS cl_sccharacteritem_custom_type, + oo.cl_scobjs_subtype AS cl_sccharacteritem_custom_subtype + FROM tbl_sccharacteritems ci + INNER JOIN tbl_sccharacters c ON c.cl_sccharacter_id = ci.cl_sccharacteritem_character_id + LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = ci.cl_sccharacteritem_scobjs_id + LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = ci.cl_sccharacteritem_scitemcustom_id + LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id + WHERE ci.cl_sccharacteritem_id = :character_item_id + AND c.cl_sccharacter_id = :character_id + AND c.cl_sccharacter_owner_auth_id = :owner_auth_id + LIMIT 1" + ); + $stmt_item->execute([ + 'character_item_id' => $character_item_id, + 'character_id' => $character_id, + 'owner_auth_id' => $owner_auth_id, + ]); + $item_row = $stmt_item->fetch(); + + if (!$item_row) { + $error_message = 'Objet introuvable ou non autorisé.'; + return false; + } + + $is_custom = (string) ($item_row['cl_sccharacteritem_source'] ?? '') === 'custom'; + $item_type = $is_custom + ? (string) ($item_row['cl_sccharacteritem_custom_type'] ?? '') + : (string) ($item_row['cl_sccharacteritem_base_type'] ?? ''); + $item_subtype = $is_custom + ? (string) ($item_row['cl_sccharacteritem_custom_subtype'] ?? '') + : (string) ($item_row['cl_sccharacteritem_base_subtype'] ?? ''); + $category = sccharacters_resolve_item_category($requested_category, $item_type, $item_subtype); + + $stmt_update = $db->prepare( + 'UPDATE tbl_sccharacteritems + SET cl_sccharacteritem_slot = :slot, + cl_sccharacteritem_quantity = :quantity, + cl_sccharacteritem_note = :note + WHERE cl_sccharacteritem_id = :character_item_id + AND cl_sccharacteritem_character_id = :character_id' + ); + $stmt_update->execute([ + 'slot' => $category, + 'quantity' => $quantity, + 'note' => $note !== '' ? $note : null, + 'character_item_id' => $character_item_id, + 'character_id' => $character_id, + ]); + + return true; +} + $flash = auth_flash_get(); $flash_type = $flash['type'] ?? ''; $flash_message = $flash['message'] ?? ''; @@ -1048,6 +1149,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $character_id = (int) ($_POST['character_id'] ?? 0); $obj_id = (int) ($_POST['base_obj_id'] ?? 0); $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); + $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); $note = sccharacters_clean_text($_POST['item_note'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); @@ -1062,6 +1164,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'base', $obj_id, $requested_category, + $quantity, $note, $error_message )) { @@ -1079,6 +1182,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $character_id = (int) ($_POST['character_id'] ?? 0); $itemcustom_id = (int) ($_POST['custom_item_id'] ?? 0); $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); + $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); $note = sccharacters_clean_text($_POST['item_note'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'custom'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); @@ -1093,6 +1197,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'custom', $itemcustom_id, $requested_category, + $quantity, $note, $error_message )) { @@ -1105,6 +1210,40 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true, $item_page_context, $item_per_page_context)); exit; } + + if ($action === 'update_character_item') { + $character_item_id = (int) ($_POST['character_item_id'] ?? 0); + $character_id = (int) ($_POST['character_id'] ?? 0); + $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); + $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); + $note = sccharacters_clean_text($_POST['item_note'] ?? ''); + $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); + $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); + $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); + $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); + $item_panel_context = (string) ($_POST['item_panel_context'] ?? '') === '1'; + $error_message = null; + + if (!sccharacters_update_character_item( + $db, + $current_owner_auth_id, + $character_id, + $character_item_id, + $requested_category, + $quantity, + $note, + $error_message + )) { + auth_flash_set('error', $error_message ?? 'Impossible de mettre à jour cet objet.'); + header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); + exit; + } + + auth_flash_set('success', 'Objet mis à jour.'); + header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); + exit; + } + if ($action === 'move_character_category') { $character_id = (int) ($_POST['character_id'] ?? 0); $category_key = sccharacters_clean_text($_POST['category_key'] ?? ''); @@ -2082,11 +2221,93 @@ if ($selected_character) { margin-top: auto; display: flex; flex-wrap: wrap; - gap: 0.6rem; - align-items: center; + gap: 0.75rem; + align-items: flex-start; justify-content: space-between; } + .equipment-action-buttons { + display: grid; + grid-template-columns: repeat(2, auto); + align-items: start; + gap: 0.55rem; + justify-content: end; + } + + .equipment-edit { + width: auto; + margin-top: 0; + } + + .equipment-edit[open] { + grid-column: 1 / -1; + width: min(100%, 34rem); + justify-self: end; + } + + .equipment-edit-summary, + .equipment-icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.45rem; + min-width: 2.45rem; + height: 2.45rem; + min-height: 2.45rem; + padding: 0; + cursor: pointer; + list-style: none; + line-height: 1; + } + + .equipment-edit-summary::-webkit-details-marker { + display: none; + } + + .equipment-edit-form { + margin-top: 0.85rem; + padding: 0.95rem; + border-radius: 14px; + border: 1px solid rgba(255,255,255,0.08); + background: rgba(255,255,255,0.04); + display: grid; + gap: 0.85rem; + width: min(100%, 34rem); + } + + .equipment-edit-grid { + display: grid; + grid-template-columns: minmax(110px, 135px) minmax(0, 1fr); + gap: 0.75rem; + align-items: start; + } + + .equipment-edit-grid textarea { + grid-column: 1 / -1; + min-height: 96px; + resize: vertical; + } + + .item-attach-meta textarea { + grid-column: 1 / -1; + min-height: 72px; + resize: vertical; + } + + .equipment-edit-form input, + .equipment-edit-form select, + .equipment-edit-form textarea, + .item-attach-meta input, + .item-attach-meta select, + .item-attach-meta textarea { + width: 100%; + } + + .equipment-edit-form-actions { + display: flex; + justify-content: flex-end; + } + .equipment-reorder-form { display: none; } @@ -2219,13 +2440,13 @@ if ($selected_character) { .search-result { padding: 0.95rem; display: grid; - grid-template-columns: minmax(0, 1fr) 310px; + grid-template-columns: minmax(0, 1fr) 360px; gap: 1rem; align-items: start; } body.show-item-preview .search-result { - grid-template-columns: 96px minmax(0, 1fr) 310px; + grid-template-columns: 96px minmax(0, 1fr) 360px; } .search-result strong { display: block; margin-bottom: 0.25rem; } @@ -2305,27 +2526,35 @@ if ($selected_character) { .item-attach-form { display: grid; - gap: 0.65rem; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0.75rem; + align-self: stretch; + align-items: stretch; } .item-attach-meta { display: grid; - grid-template-columns: minmax(0, 180px) minmax(0, 1fr); + grid-template-columns: minmax(96px, 112px) minmax(0, 1fr); gap: 0.55rem; + align-items: start; } .item-attach-actions { display: flex; - justify-content: flex-end; + align-self: stretch; } .item-add-button { - min-width: 2.75rem; - min-height: 2.75rem; + min-width: 3rem; + min-height: 100%; + height: 100%; padding: 0.4rem; font-size: 1.55rem; line-height: 1; font-weight: 700; + display: inline-flex; + align-items: center; + justify-content: center; } .search-results-summary { @@ -2415,6 +2644,7 @@ if ($selected_character) { justify-content: flex-start; } + .item-attach-form, .item-attach-meta, .search-controls-row { grid-template-columns: 1fr; @@ -2426,6 +2656,8 @@ if ($selected_character) { .item-add-button { width: 100%; + min-height: 2.75rem; + height: auto; } } @@ -2803,6 +3035,8 @@ if ($selected_character) { $item_type, $item_subtype ); + $item_quantity = sccharacters_normalize_item_quantity($character_item_row['cl_sccharacteritem_quantity'] ?? null); + $item_title = $item_quantity !== null ? $item_quantity . 'x ' . $item_name : $item_name; $item_note = trim((string) ($character_item_row['cl_sccharacteritem_note'] ?? '')); $item_stats = []; if ($is_custom) { @@ -2818,7 +3052,7 @@ if ($selected_character) {
-

+

@@ -2850,18 +3084,48 @@ if ($selected_character) {
Glisser-déposer pour réordonner dans cette catégorie -
- - - - - - - - - - -
+
+
+ +
+ + + + + + + + + +
+ + + +
+
+ +
+
+
+
+ + + + + + + + + + +
+
@@ -3008,6 +3272,7 @@ if ($selected_character) {
+ - +