bmc_hub/app/modules/sag/templates/index.html
Christian 29acdf3e01 Add tests for new SAG module endpoints and module deactivation
- Implement test script for new SAG module endpoints BE-003 (Tag State Management) and BE-004 (Bulk Operations).
- Create test cases for creating, updating, and bulk operations on cases and tags.
- Add a test for module deactivation to ensure data integrity is maintained.
- Include setup and teardown for tests to clear database state before and after each test.
2026-01-31 23:16:24 +01:00

388 lines
12 KiB
HTML

{% extends "shared/frontend/base.html" %}
{% block title %}Sager - 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-case {
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-case: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-section label {
font-weight: 600;
color: var(--accent);
margin-bottom: 0.5rem;
display: block;
}
.filter-section input,
.filter-section select {
border-radius: 8px;
border: 1px solid rgba(0,0,0,0.1);
}
.sag-card {
display: block;
background: var(--bg-card);
border: 1px solid rgba(0,0,0,0.1);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1rem;
text-decoration: none;
color: inherit;
transition: all 0.2s;
cursor: pointer;
}
.sag-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-color: var(--accent);
}
.sag-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--accent);
margin-bottom: 0.5rem;
}
.sag-meta {
display: flex;
gap: 1rem;
font-size: 0.85rem;
flex-wrap: wrap;
margin-top: 1rem;
}
.status-badge {
padding: 0.3rem 0.8rem;
border-radius: 20px;
font-weight: 500;
font-size: 0.8rem;
}
.status-åben {
background-color: #d1ecf1;
color: #0c5460;
}
.status-lukket {
background-color: #f8d7da;
color: #721c24;
}
[data-bs-theme="dark"] .status-åben {
background-color: #1a4d5c;
color: #66d9e8;
}
[data-bs-theme="dark"] .status-lukket {
background-color: #5c2b2f;
color: #f8a5ac;
}
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
}
.empty-state p {
font-size: 1.1rem;
margin: 0;
}
#bulkActions {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
background: var(--accent-light);
margin-bottom: 1rem;
border-radius: 8px;
border: 1px solid var(--accent);
}
.case-checkbox {
position: absolute;
top: 1rem;
left: 1rem;
width: 20px;
height: 20px;
cursor: pointer;
z-index: 5;
}
.sag-card-with-checkbox {
padding-left: 3rem;
}
.bulk-action-btn {
padding: 0.5rem 1rem;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 500;
transition: all 0.2s;
}
.bulk-action-btn:hover {
transform: translateY(-1px);
}
.btn-bulk-close {
background: #28a745;
color: white;
}
.btn-bulk-close:hover {
background: #218838;
}
.btn-bulk-tag {
background: var(--accent);
color: white;
}
.btn-bulk-tag:hover {
background: #0056b3;
}
.btn-bulk-clear {
background: #6c757d;
color: white;
}
.btn-bulk-clear:hover {
background: #5a6268;
}
</style>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 2rem; margin-bottom: 2rem;">
<!-- Page Header -->
<div class="page-header">
<h1>📋 Sager</h1>
<a href="/cases/new" class="btn-new-case">
<i class="bi bi-plus-lg"></i> Ny Sag
</a>
</div>
<!-- Bulk Actions Bar (hidden by default) -->
<div id="bulkActions" style="display: none;">
<span id="selectedCount" style="font-weight: 600; color: var(--accent);">0 sager valgt</span>
<div style="display: inline-flex; gap: 0.5rem;">
<button onclick="bulkClose()" class="bulk-action-btn btn-bulk-close">Luk alle</button>
<button onclick="bulkAddTag()" class="bulk-action-btn btn-bulk-tag">Tilføj tag</button>
<button onclick="clearSelection()" class="bulk-action-btn btn-bulk-clear">Ryd</button>
</div>
</div>
<!-- Filter Section -->
<div class="filter-section">
<div class="row g-3">
<div class="col-md-4">
<label>Søg</label>
<input type="text" id="searchInput" class="form-control" placeholder="Søg efter sag...">
</div>
<div class="col-md-4">
<label>Status</label>
<select id="statusFilter" class="form-select">
<option value="">Alle statuser</option>
{% for status in statuses %}
<option value="{{ status }}">{{ status }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label>Tags</label>
<select id="tagFilter" class="form-select">
<option value="">Alle tags</option>
{% for tag in all_tags %}
<option value="{{ tag }}">{{ tag }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<!-- Cases List -->
<div>
{% if sager %}
{% for sag in sager %}
<div class="sag-card sag-card-with-checkbox" data-status="{{ sag.status }}" style="cursor: default; position: relative;">
<input type="checkbox" class="case-checkbox" data-case-id="{{ sag.id }}">
<div style="cursor: pointer;" onclick="window.location.href='/cases/{{ sag.id }}'">
<div class="sag-title">{{ sag.titel }}</div>
{% if sag.beskrivelse %}
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 0.5rem;">{{ sag.beskrivelse[:150] }}{% if sag.beskrivelse|length > 150 %}...{% endif %}</div>
{% endif %}
<div class="sag-meta">
<span class="status-badge status-{{ sag.status }}">{{ sag.status }}</span>
<span style="color: var(--text-secondary);">{{ sag.created_at[:10] }}</span>
</div>
</div>
<div style="position: absolute; top: 1rem; right: 1rem; display: flex; gap: 0.5rem;">
<a href="/cases/{{ sag.id }}/edit" class="btn" style="background-color: var(--accent-color); color: white; padding: 0.4rem 0.8rem; border-radius: 6px; text-decoration: none; font-size: 0.85rem; display: flex; align-items: center; gap: 0.3rem;" title="Rediger">✏️</a>
<a href="/cases/{{ sag.id }}" class="btn" style="background-color: #6c757d; color: white; padding: 0.4rem 0.8rem; border-radius: 6px; text-decoration: none; font-size: 0.85rem; display: flex; align-items: center; gap: 0.3rem;" title="Detaljer"></a>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p><i class="bi bi-inbox" style="font-size: 2rem; display: block; margin-bottom: 1rem;"></i>Ingen sager fundet</p>
<a href="/cases/new" class="btn-new-case">Opret første sag</a>
</div>
{% endif %}
</div>
</div>
<script>
// Bulk selection state
let selectedCases = new Set();
// Bulk selection handler
document.addEventListener('change', function(e) {
if (e.target.classList.contains('case-checkbox')) {
const caseId = parseInt(e.target.dataset.caseId);
if (e.target.checked) {
selectedCases.add(caseId);
} else {
selectedCases.delete(caseId);
}
updateBulkBar();
}
});
function updateBulkBar() {
const count = selectedCases.size;
const bar = document.getElementById('bulkActions');
if (count > 0) {
bar.style.display = 'flex';
document.getElementById('selectedCount').textContent = `${count} sager valgt`;
} else {
bar.style.display = 'none';
}
}
async function bulkClose() {
const caseIds = Array.from(selectedCases);
if (!confirm(`Luk ${caseIds.length} sager?`)) return;
try {
const response = await fetch('/api/v1/cases/bulk', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
case_ids: caseIds,
action: 'close_all'
})
});
if (response.ok) {
window.location.reload();
} else {
const error = await response.json();
alert(`Fejl: ${error.detail}`);
}
} catch (err) {
alert('Fejl ved bulk lukning: ' + err.message);
}
}
async function bulkAddTag() {
const tagName = prompt('Indtast tag navn:');
if (!tagName) return;
const caseIds = Array.from(selectedCases);
try {
const response = await fetch('/api/v1/cases/bulk', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
case_ids: caseIds,
action: 'add_tag',
params: {tag_navn: tagName}
})
});
if (response.ok) {
window.location.reload();
} else {
const error = await response.json();
alert(`Fejl: ${error.detail}`);
}
} catch (err) {
alert('Fejl ved bulk tag tilføjelse: ' + err.message);
}
}
function clearSelection() {
selectedCases.clear();
document.querySelectorAll('.case-checkbox').forEach(cb => cb.checked = false);
updateBulkBar();
}
// Search functionality
document.getElementById('searchInput').addEventListener('keyup', function(e) {
const search = e.target.value.toLowerCase();
document.querySelectorAll('.sag-card').forEach(card => {
const text = card.textContent.toLowerCase();
const display = text.includes(search) ? 'block' : 'none';
card.style.display = display;
});
});
// Status filter
document.getElementById('statusFilter').addEventListener('change', function(e) {
const status = e.target.value;
document.querySelectorAll('.sag-card').forEach(card => {
const cardStatus = card.dataset.status;
const display = status === '' || cardStatus === status ? 'block' : 'none';
card.style.display = display;
});
});
</script>
{% endblock %}