bmc_hub/app/modules/hardware/templates/index.html

516 lines
19 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.

{% extends "shared/frontend/base.html" %}
{% block title %}Hardware - BMC Hub{% endblock %}
{% block extra_css %}
<style>
.page-header {
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.page-header h1 {
font-size: 2rem;
font-weight: 700;
margin: 0;
}
.btn-new-hardware {
background-color: var(--accent);
color: white;
border: none;
padding: 0.6rem 1.5rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-new-hardware:hover {
background-color: #0056b3;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(15, 76, 117, 0.3);
}
.filter-section {
background: var(--bg-card);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 2rem;
border: 1px solid rgba(0,0,0,0.1);
}
.filter-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
align-items: end;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-group label {
font-weight: 500;
font-size: 0.9rem;
color: var(--text-secondary);
}
.filter-group select,
.filter-group input {
padding: 0.5rem;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 6px;
background: var(--bg-body);
color: var(--text-primary);
}
.btn-filter {
padding: 0.5rem 1rem;
background-color: var(--accent);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-filter:hover {
background-color: #0056b3;
}
.hardware-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.hardware-card {
background: var(--bg-card);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid rgba(0,0,0,0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.hardware-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.hardware-header {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
align-items: flex-start;
}
.hardware-icon {
font-size: 2.5rem;
flex-shrink: 0;
}
.hardware-info {
flex: 1;
min-width: 0;
}
.hardware-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.25rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-subtitle {
font-size: 0.9rem;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.hardware-detail-row {
display: flex;
justify-content: space-between;
gap: 0.5rem;
}
.hardware-detail-label {
color: var(--text-secondary);
font-weight: 500;
}
.hardware-detail-value {
color: var(--text-primary);
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid rgba(0,0,0,0.1);
}
.status-badge {
padding: 0.3rem 0.8rem;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
display: inline-block;
}
.status-active { background-color: #28a745; color: white; }
.status-faulty_reported { background-color: #ffc107; color: #000; }
.status-in_repair { background-color: #007bff; color: white; }
.status-replaced { background-color: #6f42c1; color: white; }
.status-retired { background-color: #6c757d; color: white; }
.status-unsupported { background-color: #dc3545; color: white; }
.hardware-actions {
display: flex;
gap: 0.5rem;
}
.btn-action {
padding: 0.4rem 0.8rem;
border-radius: 6px;
font-size: 0.85rem;
text-decoration: none;
transition: all 0.3s ease;
border: 1px solid rgba(0,0,0,0.2);
background: var(--bg-body);
color: var(--text-primary);
}
.btn-action:hover {
background-color: var(--accent);
color: white;
border-color: var(--accent);
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: var(--text-secondary);
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.3;
}
@media (max-width: 768px) {
.hardware-grid {
grid-template-columns: 1fr;
}
.filter-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>🗂️ BMC Assets Oversigt (Kun vores egne)</h1>
<div class="d-flex gap-2">
<a href="/hardware/customers" class="btn-new-hardware" style="background-color: #6c757d;">
<i class="bi bi-building"></i>
Kundehardware
</a>
<a href="/hardware/eset" class="btn-new-hardware" style="background-color: #0f4c75;">
<i class="bi bi-shield-check"></i>
ESET Oversigt
</a>
<a href="/hardware/new" class="btn-new-hardware">
➕ Nyt Hardware
</a>
</div>
</div>
<div class="filter-section">
<form method="get" action="/hardware">
<div class="filter-grid">
<div class="filter-group">
<label for="asset_type">Type</label>
<select name="asset_type" id="asset_type">
<option value="">Alle typer</option>
<option value="pc" {% if current_asset_type == 'pc' %}selected{% endif %}>🖥️ PC</option>
<option value="laptop" {% if current_asset_type == 'laptop' %}selected{% endif %}>💻 Laptop</option>
<option value="printer" {% if current_asset_type == 'printer' %}selected{% endif %}>🖨️ Printer</option>
<option value="skærm" {% if current_asset_type == 'skærm' %}selected{% endif %}>🖥️ Skærm</option>
<option value="telefon" {% if current_asset_type == 'telefon' %}selected{% endif %}>📱 Telefon</option>
<option value="server" {% if current_asset_type == 'server' %}selected{% endif %}>🗄️ Server</option>
<option value="netværk" {% if current_asset_type == 'netværk' %}selected{% endif %}>🌐 Netværk</option>
<option value="andet" {% if current_asset_type == 'andet' %}selected{% endif %}>📦 Andet</option>
</select>
</div>
<div class="filter-group">
<label for="status">Status</label>
<select name="status" id="status">
<option value="">Alle status</option>
<option value="active" {% if current_status == 'active' %}selected{% endif %}>✅ Aktiv</option>
<option value="faulty_reported" {% if current_status == 'faulty_reported' %}selected{% endif %}>⚠️ Fejl rapporteret</option>
<option value="in_repair" {% if current_status == 'in_repair' %}selected{% endif %}>🔧 Under reparation</option>
<option value="replaced" {% if current_status == 'replaced' %}selected{% endif %}>🔄 Udskiftet</option>
<option value="retired" {% if current_status == 'retired' %}selected{% endif %}>📦 Udtjent</option>
<option value="unsupported" {% if current_status == 'unsupported' %}selected{% endif %}>❌ Ikke supporteret</option>
</select>
</div>
<div class="filter-group">
<label for="bmc_asset_category">Asset-type</label>
<select name="bmc_asset_category" id="bmc_asset_category">
<option value="">Alle asset-typer</option>
<option value="rental" {% if current_bmc_asset_category == 'rental' %}selected{% endif %}>Udlejningsvare</option>
<option value="internal" {% if current_bmc_asset_category == 'internal' %}selected{% endif %}>Internt udstyr</option>
<option value="unassigned" {% if current_bmc_asset_category == 'unassigned' %}selected{% endif %}>Ikke valgt</option>
</select>
</div>
<div class="filter-group">
<label for="rental_scope">Udlejning</label>
<select name="rental_scope" id="rental_scope">
<option value="" {% if not current_rental_scope %}selected{% endif %}>Alle assets</option>
<option value="rented" {% if current_rental_scope == 'rented' %}selected{% endif %}>Kun udlejede</option>
<option value="not_rented" {% if current_rental_scope == 'not_rented' %}selected{% endif %}>Kun ikke-udlejede</option>
</select>
</div>
<div class="filter-group">
<label for="q">Søg</label>
<input type="text" name="q" id="q" placeholder="Serial, model, mærke..." value="{{ search_query or '' }}">
</div>
<div class="filter-group">
<label>&nbsp;</label>
<button type="submit" class="btn-filter">🔍 Filtrer</button>
</div>
</div>
</form>
</div>
{% if hardware and hardware|length > 0 %}
<div class="d-flex justify-content-end mb-3">
<div class="btn-group btn-group-sm" role="group" aria-label="Visning">
<button type="button" class="btn btn-outline-secondary active" id="viewCardsBtn" onclick="setHardwareView('cards')">Kort</button>
<button type="button" class="btn btn-outline-secondary" id="viewTableBtn" onclick="setHardwareView('table')">Tabel</button>
</div>
</div>
<div id="hardwareCardsView">
<div class="hardware-grid">
{% for item in hardware %}
<div class="hardware-card" onclick="window.location.href='/hardware/{{ item.id }}'">
<div class="hardware-header">
<div class="hardware-icon">
{% if item.asset_type == 'pc' %}🖥️
{% elif item.asset_type == 'laptop' %}💻
{% elif item.asset_type == 'printer' %}🖨️
{% elif item.asset_type == 'skærm' %}🖥️
{% elif item.asset_type == 'telefon' %}📱
{% elif item.asset_type == 'server' %}🗄️
{% elif item.asset_type == 'netværk' %}🌐
{% else %}📦
{% endif %}
</div>
<div class="hardware-info">
<div class="hardware-title">{{ item.brand or 'Unknown' }} {{ item.model or '' }}</div>
<div class="hardware-subtitle">{{ item.serial_number or 'Ingen serienummer' }}</div>
</div>
</div>
<div class="hardware-details">
<div class="hardware-detail-row">
<span class="hardware-detail-label">Type:</span>
<span class="hardware-detail-value">{{ item.asset_type|title }}</span>
</div>
<div class="hardware-detail-row">
<span class="hardware-detail-label">Asset-type:</span>
<span class="hardware-detail-value">{{ {'rental': 'Udlejningsvare', 'internal': 'Internt udstyr'}.get(item.bmc_asset_category, 'Ikke valgt') }}</span>
</div>
{% if item.anydesk_id or item.anydesk_link %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">AnyDesk:</span>
<span class="hardware-detail-value">
{% if item.anydesk_link %}
<a href="{{ item.anydesk_link }}" target="_blank">{{ item.anydesk_id or 'Åbn' }}</a>
{% elif item.anydesk_id %}
<a href="anydesk://{{ item.anydesk_id }}" target="_blank">{{ item.anydesk_id }}</a>
{% endif %}
</span>
</div>
{% endif %}
{% if item.customer_name %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Ejer:</span>
<span class="hardware-detail-value">{{ item.customer_name }}</span>
</div>
{% elif item.current_owner_type %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Ejer:</span>
<span class="hardware-detail-value">{{ item.current_owner_type|title }}</span>
</div>
{% endif %}
{% if item.internal_asset_id %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Asset ID:</span>
<span class="hardware-detail-value">{{ item.internal_asset_id }}</span>
</div>
{% endif %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Udlejning:</span>
<span class="hardware-detail-value">
{% if item.is_currently_rented %}
Udlejet
{% else %}
Ikke udlejet
{% endif %}
</span>
</div>
</div>
<div class="hardware-footer">
<span class="status-badge status-{{ item.status }}">
{{ item.status|replace('_', ' ')|title }}
</span>
<div class="hardware-actions" onclick="event.stopPropagation()">
<a href="/hardware/{{ item.id }}" class="btn-action">👁️ Se</a>
<a href="/hardware/{{ item.id }}/edit" class="btn-action">✏️ Rediger</a>
<a href="/hardware/{{ item.id }}/label" class="btn-action" target="_blank" rel="noopener"><i class="bi bi-qr-code"></i> Label</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div id="hardwareTableView" class="d-none">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Hardware</th>
<th>Hardwaretype</th>
<th>Asset-type</th>
<th>Serienr.</th>
<th>Ejer</th>
<th>Udlejning</th>
<th>Status</th>
<th>AnyDesk</th>
<th class="text-end">Handling</th>
</tr>
</thead>
<tbody>
{% for item in hardware %}
<tr>
<td class="fw-semibold">{{ item.brand or 'Unknown' }} {{ item.model or '' }}</td>
<td>{{ item.asset_type|title }}</td>
<td>{{ {'rental': 'Udlejningsvare', 'internal': 'Internt udstyr'}.get(item.bmc_asset_category, 'Ikke valgt') }}</td>
<td>{{ item.serial_number or 'Ingen serienummer' }}</td>
<td>{{ item.customer_name or (item.current_owner_type|title if item.current_owner_type else '—') }}</td>
<td>
{% if item.is_currently_rented %}
<span class="status-badge" style="background-color:#0f4c75;color:#fff;">Udlejet</span>
{% else %}
<span class="status-badge" style="background-color:#6c757d;color:#fff;">Ikke udlejet</span>
{% endif %}
</td>
<td>
<span class="status-badge status-{{ item.status }}">
{{ item.status|replace('_', ' ')|title }}
</span>
</td>
<td>
{% if item.anydesk_link %}
<a href="{{ item.anydesk_link }}" target="_blank">{{ item.anydesk_id or 'Åbn' }}</a>
{% elif item.anydesk_id %}
<a href="anydesk://{{ item.anydesk_id }}" target="_blank">{{ item.anydesk_id }}</a>
{% else %}
—
{% endif %}
</td>
<td class="text-end">
<a href="/hardware/{{ item.id }}" class="btn-action">👁️ Se</a>
<a href="/hardware/{{ item.id }}/edit" class="btn-action">✏️ Rediger</a>
<a href="/hardware/{{ item.id }}/label" class="btn-action" target="_blank" rel="noopener"><i class="bi bi-qr-code"></i> Label</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% else %}
<div class="empty-state">
<div class="empty-state-icon">🖥️</div>
<h3>Ingen BMC assets fundet</h3>
<p>Opret dit første interne asset for at komme i gang.</p>
<a href="/hardware/new" class="btn-new-hardware" style="margin-top: 1rem;">➕ Opret Hardware</a>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script>
// Auto-submit filter form on change
document.querySelectorAll('#asset_type, #status, #rental_scope, #bmc_asset_category').forEach(select => {
select.addEventListener('change', () => {
select.form.submit();
});
});
function setHardwareView(view) {
const cards = document.getElementById('hardwareCardsView');
const table = document.getElementById('hardwareTableView');
const cardsBtn = document.getElementById('viewCardsBtn');
const tableBtn = document.getElementById('viewTableBtn');
if (!cards || !table || !cardsBtn || !tableBtn) return;
const showTable = view === 'table';
cards.classList.toggle('d-none', showTable);
table.classList.toggle('d-none', !showTable);
cardsBtn.classList.toggle('active', !showTable);
tableBtn.classList.toggle('active', showTable);
localStorage.setItem('hardwareViewMode', showTable ? 'table' : 'cards');
}
const savedView = localStorage.getItem('hardwareViewMode');
if (savedView === 'table') {
setHardwareView('table');
}
</script>
{% endblock %}