From 627cdc8b40f025b5ab5597bcf1835807e802b301 Mon Sep 17 00:00:00 2001 From: MaksSlyzar Date: Sun, 29 Mar 2026 08:41:19 +0300 Subject: [PATCH] added ItemListTab and fix lang --- client/src/services/GameDataManager.js | 2 +- .../src/views/GameInterface/GameInterface.jsx | 16 +- .../GameInterface/components/Navigation.jsx | 2 +- .../views/GameInterface/tabs/ItemListTab.jsx | 170 +++++++++++++++ .../GameInterface/tabs/styles/ItemListTab.css | 203 ++++++++++++++++++ 5 files changed, 377 insertions(+), 16 deletions(-) create mode 100644 client/src/views/GameInterface/tabs/ItemListTab.jsx create mode 100644 client/src/views/GameInterface/tabs/styles/ItemListTab.css diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js index 827bb5d..333743a 100644 --- a/client/src/services/GameDataManager.js +++ b/client/src/services/GameDataManager.js @@ -9,7 +9,7 @@ class GameDataManager { this.enemies = new Map(); this.translations = {}; this.manifest = {}; - this.currentLang = "en_us"; + this.currentLang = "en_US"; this.isLoaded = false; } diff --git a/client/src/views/GameInterface/GameInterface.jsx b/client/src/views/GameInterface/GameInterface.jsx index 998315f..39222a8 100644 --- a/client/src/views/GameInterface/GameInterface.jsx +++ b/client/src/views/GameInterface/GameInterface.jsx @@ -4,7 +4,6 @@ import GameHeader from "./components/GameHeader"; import Navigation from "./components/Navigation"; import Console from "../../components/Console/Console.jsx"; -// Вкладки import DashboardTab from "./tabs/DashboardTab"; import InventoryTab from "./tabs/InventoryTab"; import DungeonsTab from "./tabs/DungeonsTab"; @@ -14,9 +13,9 @@ import QuestsTab from "./tabs/QuestsTab"; import ShopTab from "./tabs/ShopTab"; import CraftingTab from "./tabs/CraftingTab"; -// Бойовий екран (Екран данжу) import DungeonScreen from "./components/DungeonScreen"; import { useSocket } from "../../hooks/useSocket.js"; +import ItemListTab from "./tabs/ItemListTab.jsx"; const GameInterface = ({ onExit }) => { const [activeTab, setActiveTab] = useState("dashboard"); @@ -24,24 +23,19 @@ const GameInterface = ({ onExit }) => { const [onlinePlayers, setOnlinePlayers] = useState([]); const { socket } = useSocket(); - // --- SOCKET LOGIC --- useEffect(() => { if (!socket) return; - // Слухаємо старт данжу від сервера socket.on("dungeon:started", (sessionData) => { console.log("Deployment initiated:", sessionData); setActiveDungeonSession(sessionData); }); - // Слухаємо повне завершення данжу socket.on("dungeon:completed", (results) => { console.log("Mission accomplished:", results); - // Можна додати затримку або вікно результатів перед виходом setActiveDungeonSession(null); }); - // Обробка помилок (наприклад, не вистачило енергії) socket.on("error", (err) => { alert(`SYSTEM_ERROR: ${err.message}`); }); @@ -53,9 +47,7 @@ const GameInterface = ({ onExit }) => { }; }, [socket]); - // --- HANDLERS --- const handleStartDungeon = (dungeonId) => { - // Відправляємо сигнал на сервер про початок місії socket.emit("dungeon:start", { dungeonId }); }; @@ -66,13 +58,9 @@ const GameInterface = ({ onExit }) => { ) ) { setActiveDungeonSession(null); - // Можна додати socket.emit("dungeon:abort"); } }; - // --- RENDER LOGIC --- - - // 1. Якщо гравець у данжі - рендеримо тільки DungeonScreen if (activeDungeonSession) { return ( { ); } - // 2. Якщо гравець в хабі/орбіті - рендеримо стандартний інтерфейс з вкладками const tabs = { dashboard: , dungeons: , @@ -93,6 +80,7 @@ const GameInterface = ({ onExit }) => { inventory: , shop: , crafting: , + itemlist: , }; return ( diff --git a/client/src/views/GameInterface/components/Navigation.jsx b/client/src/views/GameInterface/components/Navigation.jsx index 6ee2ae3..0d26490 100644 --- a/client/src/views/GameInterface/components/Navigation.jsx +++ b/client/src/views/GameInterface/components/Navigation.jsx @@ -9,6 +9,7 @@ const Navigation = ({ activeTab, onTabChange }) => { { id: "inventory", icon: "fa-archive", label: "Inventory" }, { id: "shop", icon: "fa-store", label: "Shop" }, { id: "crafting", icon: "fa-hammer", label: "Crafting" }, + { id: "itemlist", iocon: "fa-store", label: "Item List" }, ]; return ( @@ -24,7 +25,6 @@ const Navigation = ({ activeTab, onTabChange }) => { {tab.label} - {/* Декоративна лінія для активного стану */}
))} diff --git a/client/src/views/GameInterface/tabs/ItemListTab.jsx b/client/src/views/GameInterface/tabs/ItemListTab.jsx new file mode 100644 index 0000000..b7d5883 --- /dev/null +++ b/client/src/views/GameInterface/tabs/ItemListTab.jsx @@ -0,0 +1,170 @@ +import React, { useState, useEffect } from "react"; +import GameDataManager from "../../../services/GameDataManager.js"; +import "./styles/ItemListTab.css"; + +const ItemListTab = () => { + const [allItems, setAllItems] = useState([]); + const [filteredItems, setFilteredItems] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedItem, setSelectedItem] = useState(null); + + const ASSET_BASE_URL = "http://localhost:5003/static/"; + + useEffect(() => { + const itemsArray = Array.from(GameDataManager.items.keys()).map((id) => + GameDataManager.getItem(id), + ); + setAllItems(itemsArray); + setFilteredItems(itemsArray); + }, []); + + useEffect(() => { + let result = allItems; + + if (selectedCategory !== "all") { + result = result.filter( + (item) => item.meta?.category === selectedCategory, + ); + } + + if (searchQuery) { + const q = searchQuery.toLowerCase(); + result = result.filter( + (item) => + item.displayName.toLowerCase().includes(q) || + item.id.toLowerCase().includes(q), + ); + } + + setFilteredItems(result); + }, [searchQuery, selectedCategory, allItems]); + + const getFullTextureUrl = (path) => { + if (!path) return "/assets/no-image.png"; + return path.startsWith("http") ? path : `${ASSET_BASE_URL}${path}`; + }; + + const categories = [ + "all", + ...new Set(allItems.map((i) => i.meta?.category).filter(Boolean)), + ]; + + return ( +
+
+
+ + setSearchQuery(e.target.value)} + /> +
+ +
+ {categories.map((cat) => ( + + ))} +
+ +
+ {filteredItems.map((item) => ( +
setSelectedItem(item)} + > +
+ +
+
+
{item.displayName}
+
{item.id}
+
+
+
+ ))} + {filteredItems.length === 0 && ( +
NO_RECORDS_FOUND
+ )} +
+
+ +
+ {selectedItem ? ( +
+
+
+ +
+
+
{selectedItem.id}
+

{selectedItem.displayName}

+
+ {selectedItem.meta?.rarity?.toUpperCase()} +
+
+
+ +
+
+
DATA_DESCRIPTION
+

{selectedItem.description}

+
+ + {selectedItem.stats && + Object.keys(selectedItem.stats).length > 0 && ( +
+
PARAMETER_READOUT
+
+ {Object.entries(selectedItem.stats).map(([k, v]) => ( +
+ + {GameDataManager.getStatName(k)} + + +{v} +
+ ))} +
+
+ )} + +
+
OBJECT_METADATA
+
+
+ CATEGORY + {selectedItem.meta?.category || "general"} +
+
+ EQUIP_SLOT + {selectedItem.meta?.equipmentSlot || "none"} +
+
+ STACK_LIMIT + {selectedItem.meta?.stackable ? "64" : "1"} +
+
+
+
+
+ ) : ( +
+
+

AWAITING_OBJECT_SELECTION...

+
+ )} +
+
+ ); +}; + +export default ItemListTab; diff --git a/client/src/views/GameInterface/tabs/styles/ItemListTab.css b/client/src/views/GameInterface/tabs/styles/ItemListTab.css new file mode 100644 index 0000000..032adb4 --- /dev/null +++ b/client/src/views/GameInterface/tabs/styles/ItemListTab.css @@ -0,0 +1,203 @@ +.item-list-container { + display: flex; + height: calc(100vh - 120px); + background: rgba(10, 15, 25, 0.95); + color: #e0e6ed; + font-family: "Rajdhani", "Segoe UI", sans-serif; + border-top: 1px solid rgba(0, 255, 255, 0.1); + overflow: hidden; +} + +/* SIDEBAR & SEARCH */ +.item-list-sidebar { + width: 380px; + border-right: 1px solid rgba(0, 255, 255, 0.1); + display: flex; + flex-direction: column; + background: rgba(5, 10, 20, 0.5); +} + +.search-box { + padding: 20px; + position: relative; +} + +.search-box input { + width: 100%; + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(0, 255, 255, 0.2); + padding: 12px 15px; + color: #00ffff; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 14px; + transition: all 0.3s; +} + +.search-box input:focus { + outline: none; + border-color: #00ffff; + box-shadow: 0 0 10px rgba(0, 255, 255, 0.2); +} + +/* CATEGORY FILTERS */ +.category-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 0 20px 15px; +} + +.filter-btn { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #aaa; + padding: 4px 10px; + font-size: 11px; + cursor: pointer; + transition: 0.2s; +} + +.filter-btn.active, +.filter-btn:hover { + background: rgba(0, 255, 255, 0.1); + color: #00ffff; + border-color: #00ffff; +} + +/* ITEM LIST ROWS */ +.items-list-scroll { + flex: 1; + overflow-y: auto; + padding: 10px 20px; +} + +.item-row { + display: flex; + align-items: center; + padding: 10px; + margin-bottom: 8px; + background: rgba(255, 255, 255, 0.02); + border-left: 3px solid transparent; + cursor: pointer; + transition: all 0.2s; + position: relative; +} + +.item-row:hover { + background: rgba(255, 255, 255, 0.05); + transform: translateX(5px); +} + +.item-row.selected { + background: rgba(0, 255, 255, 0.08); + border-left-color: #00ffff; +} + +.item-row-icon { + width: 44px; + height: 44px; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.1); + display: flex; + align-items: center; + justify-content: center; + margin-right: 15px; +} + +.item-row-icon img { + width: 32px; + height: 32px; + object-fit: contain; + image-rendering: pixelated; /* Щоб іконки були чіткими */ +} + +.item-row-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.item-row-title { + font-size: 15px; + font-weight: 600; + color: #fff; + text-transform: uppercase; +} + +.item-row-subtitle { + font-size: 11px; + color: #666; + font-family: "Courier New", monospace; +} + +/* RARITY COLORS */ +.item-row.legendary { + border-left-color: #ff8000; +} +.item-row.epic { + border-left-color: #a335ee; +} +.item-row.rare { + border-left-color: #0070dd; +} +.item-row.common { + border-left-color: #9d9d9d; +} + +/* INSPECTOR PANEL */ +.item-inspector { + flex: 1; + padding: 40px; + background: radial-gradient( + circle at top right, + rgba(0, 255, 255, 0.03), + transparent + ); + overflow-y: auto; +} + +.inspector-header { + display: flex; + gap: 30px; + margin-bottom: 40px; + align-items: center; +} + +.header-visual { + width: 120px; + height: 120px; + background: rgba(0, 0, 0, 0.3); + border: 2px solid rgba(0, 255, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); +} + +.header-visual img { + width: 80px; + height: 80px; +} + +.header-info h1 { + font-size: 32px; + margin: 5px 0; + text-transform: uppercase; + letter-spacing: 2px; +} + +.id-tag { + font-family: monospace; + color: #00ffff; + opacity: 0.6; + font-size: 14px; +} + +/* SCROLLBAR CUSTOMIZATION */ +.items-list-scroll::-webkit-scrollbar { + width: 4px; +} +.items-list-scroll::-webkit-scrollbar-thumb { + background: rgba(0, 255, 255, 0.2); +}