409 lines
20 KiB
HTML
409 lines
20 KiB
HTML
<!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>
|