asset_browser/public/index.html
setonc a558804026 Initial commit — asset browser web app
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 09:06:25 -04:00

480 lines
21 KiB
HTML
Executable file
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.0">
<title>Asset Browser — deRenzy BT</title>
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
<link rel="icon" href="/assets/favicon-192.png" type="image/png" sizes="192x192">
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
<link rel="stylesheet" href="/styles/variables.css">
<link rel="stylesheet" href="/styles/main.css">
<link rel="stylesheet" href="/styles/card.css">
<link rel="stylesheet" href="/styles/sidebar.css">
<link rel="stylesheet" href="/styles/label.css">
<link rel="stylesheet" href="/styles/labelCenter.css">
<!-- JsBarcode vendored for offline use -->
<script src="/vendor/JsBarcode.all.min.js"></script>
</head>
<body>
<!-- ── Header ── -->
<header id="app-header">
<div class="header-left">
<a href="https://derenzy.com" target="_blank" rel="noopener noreferrer" class="header-logo-link">
<img src="/assets/logo-swirl.png" alt="deRenzy BT" class="header-logo">
</a>
<div class="header-title">
<button type="button" class="title-main" id="btn-home">Asset Browser</button>
<a href="https://derenzy.com" target="_blank" rel="noopener noreferrer" class="title-sub">deRenzy Business Technologies</a>
</div>
</div>
<div class="header-center">
<div class="scan-input-wrap">
<span class="scan-input-icon">
<!-- Barcode icon — shown for staff roles -->
<svg class="icon-barcode" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 5v14M7 5v14M11 5v14M17 5v14M21 5v14"/>
<path d="M3 5h2M3 19h2M19 5h2M19 19h2"/>
</svg>
<!-- Magnifying glass — shown for client role -->
<svg class="icon-search" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
</span>
<input
type="text"
id="scan-input"
placeholder="Search by name, serial, user… or scan a barcode"
autocomplete="off"
autocorrect="off"
spellcheck="false"
inputmode="numeric"
data-lpignore="true"
data-1p-ignore
data-bwignore
data-form-type="other"
>
<button id="scan-clear" class="scan-clear-btn" hidden aria-label="Clear"></button>
</div>
</div>
<div class="header-right">
<nav class="mode-nav" aria-label="App mode">
<button class="mode-btn" data-mode="scan" id="btn-scan-mode">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 14h7v7h-7z" fill="currentColor" opacity=".3"/>
<path d="M14 14h7v7h-7z"/>
</svg>
Scan Mode
</button>
<button class="mode-btn" id="btn-quick-view">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="8" rx="1"/><rect x="14" y="3" width="7" height="8" rx="1"/>
<rect x="3" y="13" width="7" height="8" rx="1"/><rect x="14" y="13" width="7" height="8" rx="1"/>
</svg>
Quick View
</button>
</nav>
<span class="header-btn-divider" aria-hidden="true"></span>
<!-- App menu -->
<div class="app-menu-wrap">
<button class="mode-btn" id="btn-menu-toggle" aria-haspopup="true" aria-expanded="false">
Menu
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
<span class="lc-menu-badge" id="lc-badge" hidden>0</span>
</button>
<div id="app-menu" class="app-menu" hidden>
<!-- User info -->
<div class="app-menu-user">
<span class="user-name" id="user-name"></span>
<span class="user-role-badge" id="user-role"></span>
</div>
<!-- Admin portal (shown only for admin+) -->
<a href="/admin" class="app-menu-item" id="menu-admin-link" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
Admin Portal
</a>
<div class="app-menu-divider"></div>
<!-- Label Center — batch sheet printing -->
<button class="app-menu-item" id="btn-label-center">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
Label Center
</button>
<!-- Timer toggle -->
<button class="app-menu-item" id="btn-timer-toggle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<polyline points="12 6 12 12 16 14"/>
</svg>
<span id="timer-toggle-label">Timer: ON</span>
</button>
<!-- Scan mode toggle (USB keyboard wedge ↔ camera) -->
<button class="app-menu-item" id="btn-scan-mode-toggle">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
Camera Scan
</button>
<div class="app-menu-divider"></div>
<!-- Sign out -->
<button class="app-menu-item app-menu-item-danger" id="btn-logout">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
</svg>
Sign Out
</button>
</div>
</div>
</div>
</header>
<!-- ── App body (sidebar + main) ── -->
<div id="app-body">
<!-- ── Asset Browser Sidebar ── -->
<aside id="asset-sidebar" class="sidebar" data-no-refocus>
<div class="sidebar-header">
<span class="sidebar-title">Assets</span>
<button id="sidebar-refresh" class="sidebar-refresh-btn" aria-label="Refresh assets" title="Refresh asset list">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="23 4 23 10 17 10"/>
<path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/>
</svg>
</button>
<button id="sidebar-filter-btn" class="sidebar-filter-btn" aria-label="Filter assets" title="Filter assets">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>
</svg>
<span class="sidebar-filter-badge" id="sidebar-filter-badge" hidden>0</span>
</button>
<button id="sidebar-menu-btn" class="sidebar-menu-btn" aria-label="Display options" title="Display options">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="4" y1="6" x2="20" y2="6"/>
<line x1="4" y1="12" x2="20" y2="12"/>
<line x1="4" y1="18" x2="20" y2="18"/>
<circle cx="9" cy="6" r="2.5" fill="currentColor" stroke="none"/>
<circle cx="15" cy="12" r="2.5" fill="currentColor" stroke="none"/>
<circle cx="9" cy="18" r="2.5" fill="currentColor" stroke="none"/>
</svg>
</button>
</div>
<div class="sidebar-search">
<input type="text" id="sidebar-search" placeholder="Search name, serial, user…" autocomplete="off">
<button id="sidebar-search-clear" class="sidebar-search-clear" hidden aria-label="Clear search"></button>
</div>
<!-- Filter panel — shown when filter button is active -->
<div id="sidebar-filter-panel" class="sidebar-filter-panel">
<div class="sf-section" data-filter-type="lifecycle">
<div class="sf-label">Lifecycle</div>
<div class="sf-chips">
<button class="sf-chip" data-value="Active">Active</button>
<button class="sf-chip" data-value="Inventory">Inventory</button>
<button class="sf-chip" data-value="Pre-Deployment">Pre-Deploy</button>
<button class="sf-chip" data-value="For Repair">Repair</button>
<button class="sf-chip" data-value="For Upgrade">Upgrade</button>
<button class="sf-chip" data-value="For Parts">Parts</button>
<button class="sf-chip" data-value="Decommissioned">Decomm.</button>
<button class="sf-chip" data-value="Disposed of">Disposed</button>
</div>
</div>
<div class="sf-section" data-filter-type="possession">
<div class="sf-label">Possession</div>
<div class="sf-chips">
<button class="sf-chip active" data-value="">All</button>
<button class="sf-chip" data-value="IT">IT Possession</button>
<button class="sf-chip" data-value="Deployed">Deployed</button>
</div>
</div>
<div class="sf-section" data-filter-type="infra">
<div class="sf-label">Type</div>
<div class="sf-chips">
<button class="sf-chip active" data-value="">All</button>
<button class="sf-chip" data-value="false">Devices</button>
<button class="sf-chip" data-value="true">Infrastructure</button>
</div>
</div>
<div class="sf-footer">
<label class="sf-remember-label">
<input type="checkbox" id="filter-remember">
<span>Remember</span>
</label>
<button id="sf-clear-btn" class="sf-clear-btn">Clear Filters</button>
</div>
</div>
<!-- Display/sort options menu — shown when menu button is active -->
<div id="sidebar-menu-panel" class="sidebar-menu-panel">
<!-- ▼ Badges -->
<div class="sidebar-menu-subsection open">
<button class="sidebar-menu-subsection-header" type="button">
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
<span>Badges</span>
</button>
<div class="sidebar-menu-subsection-body">
<div class="sidebar-menu-checkbox-row">
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-possession"><span>Possession</span></label>
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-user"><span>User</span></label>
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-lifecycle"><span>Lifecycle</span></label>
<label class="sidebar-menu-item"><input type="checkbox" id="badge-vis-infra"><span>Infra</span></label>
</div>
</div>
</div>
<!-- ▼ Clients -->
<div class="sidebar-menu-subsection open">
<button class="sidebar-menu-subsection-header" type="button">
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
<span>Clients</span>
</button>
<div class="sidebar-menu-subsection-body">
<div class="sidebar-menu-checkbox-row">
<label class="sidebar-menu-item"><input type="checkbox" id="disp-show-count"><span>Show count</span></label>
<label class="sidebar-menu-item"><input type="checkbox" id="disp-show-billable"><span>Show billable</span></label>
</div>
<div class="sidebar-menu-row">
<span class="sidebar-menu-row-label">Hide empty:</span>
<select id="disp-hide-empty" class="sidebar-menu-select">
<option value="none">Show all</option>
<option value="zero-assets">No assets</option>
<option value="zero-billable">No billable</option>
</select>
</div>
<div class="sidebar-menu-row"><span class="sidebar-menu-row-label">Sort:</span></div>
<div class="sidebar-sort-chips">
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="alpha">AZ</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-assets">Most Assets</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-billable">Billable</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="clients" data-value="most-users">Users</button>
</div>
</div>
</div>
<!-- ▼ Assets -->
<div class="sidebar-menu-subsection open">
<button class="sidebar-menu-subsection-header" type="button">
<svg class="subsection-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"/></svg>
<span>Assets</span>
</button>
<div class="sidebar-menu-subsection-body">
<div class="sidebar-menu-row"><span class="sidebar-menu-row-label">Sort:</span></div>
<div class="sidebar-sort-chips">
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="default">Default</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="alpha">AZ</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="user">By User</button>
<button class="sidebar-sort-chip" type="button" data-menu-sort="assets" data-value="last-sync">Last Sync</button>
</div>
</div>
</div>
<div class="sidebar-menu-footer">
<label class="sidebar-menu-item sidebar-menu-remember">
<input type="checkbox" id="menu-remember">
<span>Remember settings</span>
</label>
</div>
</div>
<div id="sidebar-tree" class="sidebar-tree"></div>
</aside>
<!-- ── Sidebar resize handle ── -->
<div id="sidebar-resize-handle" class="sidebar-resize-handle" aria-hidden="true"></div>
<!-- ── Main ── -->
<main id="app-main">
<!-- Neutral / no mode selected -->
<section id="view-neutral" class="view active" aria-live="polite"></section>
<!-- Idle / Ready to Scan -->
<section id="view-idle" class="view" aria-live="polite">
<div class="idle-content">
<div class="scan-indicator">
<div class="scan-pulse"></div>
<svg class="scan-icon-large" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.5">
<rect x="8" y="14" width="5" height="36" rx="1"/>
<rect x="16" y="14" width="3" height="36" rx="1"/>
<rect x="22" y="14" width="7" height="36" rx="1"/>
<rect x="32" y="14" width="3" height="36" rx="1"/>
<rect x="38" y="14" width="5" height="36" rx="1"/>
<rect x="46" y="14" width="3" height="36" rx="1"/>
<rect x="52" y="14" width="4" height="36" rx="1"/>
<line x1="4" y1="32" x2="60" y2="32" stroke="#C4622A" stroke-width="2" stroke-dasharray="4 2" opacity="0.8"/>
</svg>
</div>
<h2>Ready to Scan</h2>
<p>Aim your scanner at an asset label, or type an asset ID above</p>
</div>
</section>
<!-- Loading -->
<section id="view-loading" class="view" aria-live="polite">
<div class="loading-content">
<div class="spinner"></div>
<p>Looking up asset…</p>
</div>
</section>
<!-- Asset Card -->
<section id="view-asset" class="view" aria-live="polite">
<div id="asset-card-container"></div>
</section>
<!-- Search Results -->
<section id="view-search-results" class="view" aria-live="polite">
<div id="search-results-container"></div>
</section>
<!-- Label Generation -->
<section id="view-label" class="view" aria-live="polite">
<div id="label-gen-container"></div>
</section>
<!-- Quick View Dashboard (client role) -->
<section id="view-quick-view" class="view" aria-live="polite">
<div id="quick-view-container"></div>
</section>
<!-- Error -->
<section id="view-error" class="view" aria-live="assertive">
<div id="error-container"></div>
</section>
</main>
</div><!-- /#app-body -->
<!-- Toast container -->
<div id="toast-container" aria-live="polite"></div>
<!-- Label Center overlay -->
<div id="label-center-overlay" class="lc-overlay" hidden aria-modal="true" role="dialog" aria-label="Label Center">
<div class="lc-header">
<div class="lc-header-left">
<svg class="lc-header-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 6 2 18 2 18 9"/>
<path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/>
<rect x="6" y="14" width="12" height="8"/>
</svg>
<span class="lc-header-title">Label Center</span>
<span class="lc-header-sub" id="lc-queue-count">0 items queued</span>
</div>
<div class="lc-header-actions">
<button class="btn btn-accent" id="lc-print-btn" disabled>Print All</button>
<button class="btn btn-ghost" id="lc-close-btn">Close</button>
</div>
</div>
<div class="lc-body">
<!-- Left: queue list -->
<div class="lc-queue-panel">
<div class="lc-queue-panel-header">
<span class="lc-panel-title">Queued Labels</span>
<button class="btn btn-ghost" id="lc-clear-all-btn" style="font-size:.78rem;padding:4px 10px">Clear All</button>
</div>
<div class="lc-queue-list" id="lc-queue-list"></div>
</div>
<!-- Right: sheet pages -->
<div class="lc-sheet-panel">
<div class="lc-sheet-panel-header">
<select id="lc-sheet-type" class="lc-sheet-select">
<option value="OL875LP">OL875LP &mdash; 2.625&quot; &times; 1&quot; (30-up)</option>
<option value="OL25LP">OL25LP &mdash; 1.75&quot; &times; 0.5&quot; (80-up)</option>
</select>
<button class="btn btn-ghost" id="lc-autofill-btn" style="font-size:.78rem;padding:4px 10px">Auto-fill all</button>
<button class="btn btn-ghost btn-danger" id="lc-reset-all-btn" style="font-size:.78rem;padding:4px 10px">Reset all</button>
</div>
<div class="lc-sheet-scroll">
<div class="lc-pages-container" id="lc-pages-container"></div>
</div>
</div>
</div>
<!-- Assignment picker flyout (positioned by JS) -->
<div class="lc-picker" id="lc-picker" hidden>
<div class="lc-picker-title">Assign to Position <span id="lc-picker-pos-label"></span></div>
<!-- Asset search -->
<div class="lc-picker-search-wrap">
<svg class="lc-picker-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input type="text" id="lc-picker-search" class="lc-picker-search-input"
placeholder="Search assets…" autocomplete="off" spellcheck="false">
</div>
<div id="lc-picker-search-results" class="lc-picker-search-results" hidden></div>
<div class="lc-picker-section-label">Queued labels</div>
<div class="lc-picker-list" id="lc-picker-list"></div>
<button class="btn btn-ghost" id="lc-picker-cancel" style="width:100%;margin-top:8px;font-size:.8rem">Cancel</button>
</div>
</div>
<!-- Camera scan overlay -->
<div id="camera-overlay" class="camera-overlay" hidden aria-modal="true" role="dialog" aria-label="Camera Scanner">
<div class="camera-overlay-inner">
<div class="camera-header">
<span class="camera-title">Camera Scanner</span>
<button class="camera-close-btn" id="btn-camera-close" aria-label="Close camera scanner">&times;</button>
</div>
<div class="camera-viewport">
<video id="camera-video" autoplay playsinline muted></video>
<div class="camera-reticle" aria-hidden="true">
<div class="camera-scan-line"></div>
</div>
</div>
<p class="camera-status" id="camera-status">Initializing camera…</p>
</div>
</div>
<script type="module" src="/app.js"></script>
</body>
</html>