import { useState, useRef, useEffect } from "react"; import { Upload, Trash2, Plus, X, Save } from "lucide-react"; import { useServerMissions, useServerMissionRotation, useUpdateMissionRotation, useUploadMission, useDeleteMission, useServerConfigSection, } from "@/hooks/useServerDetail"; import type { MissionRotationEntry } from "@/hooks/useServerDetail"; import { useAuthStore } from "@/store/auth.store"; import { useUIStore } from "@/store/ui.store"; import { logger } from "@/lib/logger"; interface MissionListProps { serverId: number; } const DIFFICULTY_OPTIONS = ["", "Recruit", "Regular", "Veteran", "Custom"]; interface UploadProgress { filename: string; done: boolean; } export function MissionList({ serverId }: MissionListProps) { const isAdmin = useAuthStore((s) => s.user?.role === "admin"); const addNotification = useUIStore((s) => s.addNotification); const { data: missionsData, isLoading: missionsLoading } = useServerMissions(serverId); const { data: rotationData, isLoading: rotationLoading } = useServerMissionRotation(serverId); const { data: serverSection } = useServerConfigSection(serverId, "server"); const updateRotation = useUpdateMissionRotation(serverId); const uploadMission = useUploadMission(serverId); const deleteMission = useDeleteMission(serverId); const fileInputRef = useRef(null); const [rotation, setRotation] = useState([]); const [uploadProgress, setUploadProgress] = useState([]); // Sync rotation from query on load useEffect(() => { if (rotationData) setRotation(rotationData); }, [rotationData]); const configVersion = (serverSection?._meta as { config_version?: number })?.config_version ?? 0; const handleUpload = async (e: React.ChangeEvent) => { const files = Array.from(e.target.files ?? []); if (files.length === 0) return; const progress: UploadProgress[] = files.map((f) => ({ filename: f.name, done: false })); setUploadProgress(progress); try { for (let i = 0; i < files.length; i++) { await uploadMission.mutateAsync([files[i]]); setUploadProgress((prev) => prev.map((p, idx) => (idx === i ? { ...p, done: true } : p))); } addNotification({ type: "success", message: `${files.length} mission(s) uploaded` }); } catch (err) { logger.error("MissionList", "Failed to upload missions: %s", err); addNotification({ type: "error", message: "Failed to upload one or more missions" }); } setUploadProgress([]); if (fileInputRef.current) fileInputRef.current.value = ""; }; const handleDelete = async (filename: string) => { try { await deleteMission.mutateAsync(filename); addNotification({ type: "info", message: `Mission ${filename} deleted` }); } catch (err) { logger.error("MissionList", "Failed to delete mission %s: %s", filename, err); addNotification({ type: "error", message: `Failed to delete ${filename}` }); } }; const addToRotation = (missionName: string) => { if (rotation.some((r) => r.name === missionName)) return; setRotation([...rotation, { name: missionName, difficulty: "" }]); }; const removeFromRotation = (idx: number) => { setRotation(rotation.filter((_, i) => i !== idx)); }; const updateDifficulty = (idx: number, difficulty: string) => { setRotation(rotation.map((r, i) => (i === idx ? { ...r, difficulty } : r))); }; const handleSaveRotation = async () => { try { await updateRotation.mutateAsync({ missions: rotation, config_version: configVersion }); addNotification({ type: "success", message: "Mission rotation saved" }); } catch (err) { logger.error("MissionList", "Failed to save rotation: %s", err); addNotification({ type: "error", message: "Failed to save mission rotation" }); } }; const handleClearRotation = () => setRotation([]); if (missionsLoading || rotationLoading) { return
Loading missions...
; } const missions = missionsData?.missions ?? []; return (
{/* Section A: Available Missions */}

Available Missions ({missions.length})

{isAdmin && ( )}
{uploadProgress.length > 0 && (
{uploadProgress.map((p) => (
{p.done ? ( ) : ( )} {p.filename}
))}
)}
{isAdmin && ( )} {missions.length === 0 ? ( ) : ( missions.map((mission) => ( {isAdmin && ( )} )) )}
Mission Name Terrain SizeActions
No missions uploaded
{mission.name} {mission.terrain ? ( {mission.terrain} ) : ( )} {formatSize(mission.size_bytes)}
{/* Section B: Mission Rotation */}

Mission Rotation ({rotation.length})

{isAdmin && (
)}
{isAdmin && ( )} {rotation.length === 0 ? ( ) : ( rotation.map((entry, idx) => { const missionFile = missions.find((m) => m.name === entry.name); return ( {isAdmin && ( )} ); }) )}
# Mission Name Terrain DifficultyRemove
No missions in rotation. Add from Available above.
{idx + 1} {entry.name} {missionFile?.terrain ? ( {missionFile.terrain} ) : ( )} {isAdmin ? ( ) : ( {entry.difficulty || "Default"} )}
); } function formatSize(bytes: number): string { if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${bytes} B`; }