Files
ShooterHub/frontend/admin.html
2026-04-02 11:24:30 +02:00

409 lines
20 KiB
HTML
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.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin ShooterHub</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<div id="navbar"></div>
<div class="container-fluid py-4">
<div class="d-flex align-items-center gap-2 mb-4 container">
<i class="bi bi-shield-lock fs-3 text-warning"></i>
<h2 class="fw-bold mb-0" data-i18n="admin.title">Administration</h2>
</div>
<ul class="nav nav-tabs mb-4 container" id="adminTabs">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#usersTab">
<i class="bi bi-people me-1"></i><span data-i18n="admin.tab.users">Users</span>
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#catalogTab">
<i class="bi bi-archive me-1"></i><span data-i18n="admin.tab.catalog">Gear catalog</span>
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#calibersTab">
<i class="bi bi-crosshair me-1"></i><span data-i18n="admin.tab.calibers">Calibers</span>
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#componentsTab">
<i class="bi bi-gear-wide-connected me-1"></i><span data-i18n="admin.tab.comps">Reload components</span>
</button>
</li>
</ul>
<div class="tab-content container">
<!-- ══ USERS TAB ══════════════════════════════════════════════════════ -->
<div class="tab-pane fade show active" id="usersTab">
<!-- Create user panel (collapsed by default) -->
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-primary btn-sm" id="createUserBtn">
<i class="bi bi-person-plus me-1"></i><span data-i18n="admin.users.new">New user</span>
</button>
</div>
<div id="createUserPanel" class="card border-0 shadow-sm mb-4 d-none">
<div class="card-body">
<h6 class="fw-semibold mb-3" data-i18n="admin.users.create.title">Create new user</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label"><span data-i18n="admin.users.username">Username</span> <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="newUsername" autocomplete="off">
</div>
<div class="col-md-6">
<label class="form-label"><span data-i18n="admin.users.email">Email</span> <span class="text-danger">*</span></label>
<input type="email" class="form-control" id="newEmail" autocomplete="off">
</div>
<div class="col-md-6">
<label class="form-label"><span data-i18n="admin.users.password">Password</span> <span class="text-danger">*</span></label>
<input type="password" class="form-control" id="newPassword" autocomplete="new-password">
</div>
<div class="col-md-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="newIsStaff">
<label class="form-check-label" for="newIsStaff" data-i18n="admin.users.staff">Staff / Admin</label>
</div>
</div>
<div class="col-md-6">
<label class="form-label" data-i18n="admin.users.first_name">First name</label>
<input type="text" class="form-control" id="newFirstName">
</div>
<div class="col-md-6">
<label class="form-label" data-i18n="admin.users.last_name">Last name</label>
<input type="text" class="form-control" id="newLastName">
</div>
</div>
<div id="createUserAlert" class="alert alert-danger d-none small mt-3"></div>
<div class="d-flex gap-2 justify-content-end mt-3">
<button class="btn btn-secondary btn-sm" id="cancelCreateUserBtn" data-i18n="btn.cancel">Cancel</button>
<button class="btn btn-primary btn-sm" id="saveCreateUserBtn">
<i class="bi bi-person-plus me-1"></i><span data-i18n="admin.users.create.btn">Create user</span>
</button>
</div>
</div>
</div>
<div id="usersSpinner" class="text-center py-4"><div class="spinner-border text-primary"></div></div>
<div class="table-responsive d-none" id="usersTableWrap">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th data-i18n="admin.table.username">Username</th><th data-i18n="admin.table.email">Email</th><th data-i18n="admin.table.name">Name</th>
<th data-i18n="admin.table.staff">Staff</th><th data-i18n="admin.table.active">Active</th><th data-i18n="admin.table.joined">Joined</th><th></th>
</tr>
</thead>
<tbody id="usersBody"></tbody>
</table>
</div>
</div>
<!-- ══ GEAR CATALOG TAB ════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="catalogTab">
<div class="row g-4">
<!-- Left: list -->
<div class="col-lg-8">
<div class="d-flex gap-2 mb-2 align-items-center flex-wrap">
<select class="form-select w-auto" id="catalogTypeFilter">
<option value="all" data-i18n="admin.filter.all_types">All types</option>
<option value="firearm" data-i18n="admin.filter.firearms">Firearms</option>
<option value="scope" data-i18n="admin.filter.scopes">Scopes</option>
<option value="suppressor" data-i18n="admin.filter.suppressors">Suppressors</option>
<option value="bipod" data-i18n="admin.filter.bipods">Bipods</option>
<option value="magazine" data-i18n="admin.filter.magazines">Magazines</option>
</select>
<select class="form-select w-auto" id="catalogStatusFilter">
<option value="PENDING" data-i18n="admin.filter.pending" selected>Pending</option>
<option value="all" data-i18n="admin.filter.all_statuses">All statuses</option>
<option value="VERIFIED" data-i18n="admin.filter.verified">Verified</option>
<option value="REJECTED" data-i18n="admin.filter.rejected">Rejected</option>
</select>
<input type="search" class="form-control w-auto" id="catalogSearch" placeholder="Search brand / model…" style="min-width:180px">
<button class="btn btn-outline-secondary btn-sm" onclick="catalogSearchTerm=document.getElementById('catalogSearch').value.trim();loadCatalog(undefined,1)">
<i class="bi bi-search"></i>
</button>
</div>
<p class="text-muted small mb-2" id="catalogCount"></p>
<div id="catalogSpinner" class="text-center py-4"><div class="spinner-border text-primary"></div></div>
<div class="table-responsive d-none" id="catalogTableWrap">
<table class="table table-sm table-hover align-middle">
<thead class="table-light">
<tr><th data-i18n="admin.catalog.col.type">Type</th><th data-i18n="admin.catalog.col.brand">Brand</th><th data-i18n="admin.catalog.col.model">Model</th><th data-i18n="admin.catalog.col.caliber">Caliber</th><th data-i18n="admin.catalog.col.status">Status</th><th></th></tr>
</thead>
<tbody id="catalogBody"></tbody>
</table>
<div id="catalogPagination"></div>
</div>
</div>
<!-- Right: add form -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="fw-semibold mb-3" data-i18n="admin.add.gear">Add gear item</h6>
<div class="mb-3">
<label class="form-label">Type <span class="text-danger">*</span></label>
<select class="form-select" id="addGearType">
<option value="">Select type…</option>
<option value="firearm">Firearm</option>
<option value="scope">Scope</option>
<option value="suppressor">Suppressor</option>
<option value="bipod">Bipod</option>
<option value="magazine">Magazine</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Brand <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="addGearBrand">
</div>
<div class="mb-3">
<label class="form-label">Model <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="addGearModel">
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<input type="text" class="form-control" id="addGearDesc">
</div>
<!-- Firearm-specific -->
<div id="fieldsFirearm" class="d-none">
<div class="mb-3">
<label class="form-label">Firearm type</label>
<select class="form-select" id="addFirearmType">
<option value="RIFLE">Rifle</option>
<option value="PISTOL">Pistol</option>
<option value="SHOTGUN">Shotgun</option>
<option value="REVOLVER">Revolver</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Caliber</label>
<select class="form-select" id="addFirearmCaliber"><option value="">Loading…</option></select>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Barrel (mm)</label>
<input type="number" class="form-control" id="addFirearmBarrel">
</div>
<div class="col">
<label class="form-label">Mag capacity</label>
<input type="number" class="form-control" id="addFirearmMag">
</div>
</div>
</div>
<!-- Scope-specific -->
<div id="fieldsScope" class="d-none">
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Mag min</label>
<input type="number" class="form-control" id="addScopeMagMin">
</div>
<div class="col">
<label class="form-label">Mag max</label>
<input type="number" class="form-control" id="addScopeMagMax">
</div>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Obj dia (mm)</label>
<input type="number" class="form-control" id="addScopeObj">
</div>
<div class="col">
<label class="form-label">Tube dia (mm)</label>
<input type="number" class="form-control" id="addScopeTube">
</div>
</div>
<div class="mb-3">
<label class="form-label">Reticle type</label>
<select class="form-select" id="addScopeReticle">
<option value="">— optional —</option>
<option value="DUPLEX">Duplex</option>
<option value="MILDOT">Mil-Dot</option>
<option value="BDC">BDC</option>
<option value="ILLUMINATED">Illuminated</option>
<option value="ETCHED">Etched Glass</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Adjustment</label>
<select class="form-select" id="addScopeAdj">
<option value="">— optional —</option>
<option value="MOA">MOA</option>
<option value="MRAD">MRAD</option>
</select>
</div>
<div class="col">
<label class="form-label">Focal plane</label>
<select class="form-select" id="addScopeFP">
<option value="">— optional —</option>
<option value="FFP">FFP</option>
<option value="SFP">SFP</option>
</select>
</div>
</div>
<div class="d-none"><!-- placeholder to close fieldsSsope -->
</div>
</div>
<!-- Suppressor-specific -->
<div id="fieldsSuppressor" class="d-none">
<div class="mb-3">
<label class="form-label">Max caliber</label>
<select class="form-select" id="addSuppCaliber"><option value="">Loading…</option></select>
</div>
<div class="mb-3">
<label class="form-label">Thread pitch</label>
<input type="text" class="form-control" id="addSuppThread" placeholder="1/2-28">
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Length (mm)</label>
<input type="number" class="form-control" id="addSuppLength">
</div>
<div class="col">
<label class="form-label">Weight (g)</label>
<input type="number" class="form-control" id="addSuppWeight">
</div>
</div>
</div>
<!-- Bipod-specific -->
<div id="fieldsBipod" class="d-none">
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Min h (mm)</label>
<input type="number" class="form-control" id="addBipodMin">
</div>
<div class="col">
<label class="form-label">Max h (mm)</label>
<input type="number" class="form-control" id="addBipodMax">
</div>
</div>
<div class="mb-3">
<label class="form-label">Attachment</label>
<input type="text" class="form-control" id="addBipodAttach" placeholder="Picatinny, Harris…">
</div>
</div>
<!-- Magazine-specific -->
<div id="fieldsMagazine" class="d-none">
<div class="mb-3">
<label class="form-label">Caliber</label>
<select class="form-select" id="addMagCaliber"><option value="">Loading…</option></select>
</div>
<div class="mb-3">
<label class="form-label">Capacity</label>
<input type="number" class="form-control" id="addMagCapacity">
</div>
</div>
<div id="addGearAlert" class="alert alert-danger d-none small"></div>
<button class="btn btn-primary w-100" id="addGearSubmit" disabled>
<i class="bi bi-plus-lg me-1"></i><span data-i18n="admin.add.btn">Add to catalog</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══ CALIBERS TAB ═══════════════════════════════════════════════════ -->
<div class="tab-pane fade" id="calibersTab">
<div class="row g-4">
<div class="col-lg-8">
<div id="calibersSpinner" class="text-center py-3"><div class="spinner-border text-primary"></div></div>
<div class="table-responsive d-none" id="calibersTableWrap">
<table class="table table-sm table-hover align-middle">
<thead class="table-light">
<tr><th>Name</th><th>Short</th><th>Case (mm)</th><th>Pressure (MPa)</th><th>Status</th><th></th></tr>
</thead>
<tbody id="calibersBody"></tbody>
</table>
</div>
</div>
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="fw-semibold mb-3">Add caliber</h6>
<div class="mb-2"><input type="text" class="form-control form-control-sm" id="calName" placeholder="Name *"></div>
<div class="mb-2"><input type="text" class="form-control form-control-sm" id="calShort" placeholder="Short name"></div>
<div class="row g-2 mb-2">
<div class="col"><input type="number" class="form-control form-control-sm" id="calCase" placeholder="Case length (mm)" step="0.01"></div>
<div class="col"><input type="number" class="form-control form-control-sm" id="calOAL" placeholder="OAL (mm)" step="0.01"></div>
</div>
<div class="row g-2 mb-2">
<div class="col"><input type="number" class="form-control form-control-sm" id="calBulletDia" placeholder="Bullet dia (mm)" step="0.001"></div>
<div class="col"><input type="number" class="form-control form-control-sm" id="calPressure" placeholder="Max pressure (MPa)" step="0.1"></div>
</div>
<div id="calibersAddAlert" class="alert alert-danger d-none small mb-2"></div>
<button class="btn btn-primary btn-sm w-100" id="calibersAddBtn">
<i class="bi bi-plus-lg me-1"></i>Add caliber (verified)
</button>
</div>
</div>
</div>
</div>
</div>
<!-- ══ RELOAD COMPONENTS TAB ═══════════════════════════════════════════ -->
<div class="tab-pane fade" id="componentsTab">
<ul class="nav nav-pills mb-3" id="compPills">
<li class="nav-item"><button class="nav-link active" data-comp="primers" data-i18n="admin.comp.primers">Primers</button></li>
<li class="nav-item"><button class="nav-link" data-comp="brass" data-i18n="admin.comp.brass">Brass</button></li>
<li class="nav-item"><button class="nav-link" data-comp="bullets" data-i18n="admin.comp.bullets">Bullets</button></li>
<li class="nav-item"><button class="nav-link" data-comp="powders" data-i18n="admin.comp.powders">Powders</button></li>
</ul>
<div class="row g-4">
<!-- Component list -->
<div class="col-lg-8">
<div id="compSpinner" class="text-center py-3"><div class="spinner-border text-primary"></div></div>
<div class="table-responsive d-none" id="compTableWrap">
<table class="table table-sm table-hover align-middle">
<thead class="table-light"><tr id="compTableHead"></tr></thead>
<tbody id="compBody"></tbody>
</table>
</div>
</div>
<!-- Add component form -->
<div class="col-lg-4">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="fw-semibold mb-3" id="addCompTitle">Add primer</h6>
<div id="addCompForm"></div>
<div id="addCompAlert" class="alert alert-danger d-none small mt-2"></div>
<button class="btn btn-primary w-100 mt-2" id="addCompSubmit">
<i class="bi bi-plus-lg me-1"></i>Add
</button>
</div>
</div>
</div>
</div>
</div>
</div><!-- /tab-content -->
</div>
<div id="toastContainer"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/js/api.js"></script>
<script src="/js/i18n.js"></script>
<script src="/js/calibers.js"></script>
<script src="/js/nav.js"></script>
<script src="/js/admin.js"></script>
</body>
</html>