Game-Server/client/src/views/GameInterface/tabs/QuestsTab.jsx
2026-04-21 08:48:52 +03:00

173 lines
5.5 KiB
JavaScript

import React, { useEffect, useState, useMemo } from "react";
import Card from "../../../components/ui/Card";
import { useSocket } from "../../../hooks/useSocket";
import gameDataManager from "../../../services/GameDataManager";
import "./styles/QuestsTab.css";
const QuestsTab = () => {
const { socket } = useSocket();
const [quests, setQuests] = useState([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState("ACTIVE");
useEffect(() => {
if (!socket) return;
socket.emit("quest:get_list");
const localize = (q) => {
const staticData = gameDataManager.getQuest(q.id);
return {
...q,
displayName: staticData?.displayName || q.id,
description: staticData?.description || "",
objectives: q.objectives.map((obj, idx) => ({
...obj,
description: staticData?.objectives[idx]?.description || obj.type,
})),
};
};
const handleQuestData = (data) => {
const uniqueQuests = new Map();
data.forEach((q) => {
uniqueQuests.set(q.id, localize(q));
});
setQuests(Array.from(uniqueQuests.values()));
setLoading(false);
};
const handleQuestUpdate = (updatedQuest) => {
setQuests((prev) => {
const localized = localize(updatedQuest);
const questMap = new Map(prev.map((q) => [q.id, q]));
questMap.set(localized.id, localized);
return Array.from(questMap.values());
});
};
socket.on("quest:list_data", handleQuestData);
socket.on("quest:update", handleQuestUpdate);
return () => {
socket.off("quest:list_data", handleQuestData);
socket.off("quest:update", handleQuestUpdate);
};
}, [socket]);
const filteredQuests = useMemo(() => {
return quests.filter((q) =>
activeTab === "ACTIVE"
? q.status === "active" || q.status === "ready"
: q.status === "completed",
);
}, [quests, activeTab]);
const renderObjective = (obj, index) => {
const progress = Math.min(
(obj.currentAmount / obj.requiredAmount) * 100,
100,
);
return (
<div key={index} className="objective-item">
<div className="objective-info">
<span className="objective-desc">{obj.description || obj.type}</span>
<span className="objective-count">
{obj.currentAmount} / {obj.requiredAmount}
</span>
</div>
<div className="objective-progress-track">
<div
className="objective-progress-fill"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
);
};
return (
<div className="quests-container">
<div className="dash-scanline"></div>
<div className="quests-header">
<h2 className="glitch-text" data-text="MISSION_LOG">
MISSION_LOG
</h2>
<div className="quest-tabs-nav">
<button
className={`nav-btn ${activeTab === "ACTIVE" ? "active" : ""}`}
onClick={() => setActiveTab("ACTIVE")}
>
ACTIVE_OPERATIONS
</button>
<button
className={`nav-btn ${activeTab === "COMPLETED" ? "active" : ""}`}
onClick={() => setActiveTab("COMPLETED")}
>
ARCHIVED_DATA
</button>
</div>
<div className="header-line"></div>
</div>
{loading ? (
<div className="loading-status">SCANNING_NEURAL_NETWORK...</div>
) : (
<div className="quests-grid">
{filteredQuests.length > 0 ? (
filteredQuests.map((quest) => (
<Card key={quest.id} className={`quest-card ${quest.status}`}>
<div className="card-tag">{quest.category || "MISSION"}</div>
<div className="quest-main">
<h3 className="quest-title">{quest.displayName}</h3>
<p className="quest-description">{quest.description}</p>
</div>
<div className="quest-objectives">
<div className="section-label">OBJECTIVES</div>
{quest.objectives.map((obj, idx) =>
renderObjective(obj, idx),
)}
</div>
<div className="quest-rewards">
<div className="section-label">REWARDS</div>
<div className="rewards-row">
{quest.rewards?.credits > 0 && (
<span className="reward-pill credits">
+{quest.rewards.credits} CR
</span>
)}
{quest.rewards?.xp > 0 && (
<span className="reward-pill xp">
+{quest.rewards.xp} XP
</span>
)}
</div>
</div>
{quest.status === "ready" && (
<button
className="claim-btn"
onClick={() =>
socket.emit("quest:claim_reward", { questId: quest.id })
}
>
COMPLETE_MISSION
</button>
)}
{quest.status === "completed" && (
<div className="completed-stamp">MISSION_ACCOMPLISHED</div>
)}
</Card>
))
) : (
<div className="no-quests">
<p>NO_{activeTab}_SIGNALS_FOUND</p>
</div>
)}
</div>
)}
</div>
);
};
export default QuestsTab;