Files
k-boris-website/backlogger/templates/backlogger/item_form.html
Boris b289e7c7fe
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Refactor item form visibility to data-attribute driven JS
Replace hardcoded id-based section lookup with declarative rules:
- data-show-category="games|books|films" on sections
- data-hide-status="unending" on individual fields

JS now has a single updateVisibility() that evaluates attributes.
Adding new conditions only requires touching HTML, not JS.

Also hides Progress and Total Hours for unending items.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:55:47 +03:00

278 lines
8.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ action }} Item — Backlogger</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
}
a { text-decoration: none; color: inherit; }
.site-header {
background: #0a0f1e;
border-bottom: 1px solid #1e293b;
padding: 0.75rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.site-header .brand { color: #38bdf8; font-weight: 600; font-size: 0.95rem; }
.site-header nav a { color: #64748b; font-size: 0.85rem; }
.site-header nav a:hover { color: #e2e8f0; }
.container {
max-width: 520px;
margin: 3rem auto;
padding: 0 1.5rem;
}
.card {
background: #0a0f1e;
border: 1px solid #1e293b;
border-radius: 12px;
padding: 2rem;
}
h1 { font-size: 1.35rem; margin-bottom: 1.75rem; letter-spacing: -0.02em; }
.field { margin-bottom: 1.25rem; }
.field label {
display: block;
font-size: 0.8rem;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.07em;
margin-bottom: 0.4rem;
}
.field input[type="text"],
.field input[type="number"],
.field select {
width: 100%;
background: #1e293b;
border: 1px solid #334155;
border-radius: 6px;
color: #e2e8f0;
padding: 0.55rem 0.75rem;
font-size: 0.9rem;
}
.field input:focus,
.field select:focus { outline: none; border-color: #38bdf8; }
.field input[type="range"] {
width: 100%;
accent-color: #38bdf8;
cursor: pointer;
margin-top: 0.2rem;
}
.progress-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.2rem;
}
.progress-val { color: #38bdf8; font-variant-numeric: tabular-nums; font-size: 0.9rem; }
.checkbox-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.checkbox-row input[type="checkbox"] { accent-color: #38bdf8; width: 1rem; height: 1rem; cursor: pointer; }
.checkbox-row label {
font-size: 0.9rem;
color: #e2e8f0;
text-transform: none;
letter-spacing: 0;
margin-bottom: 0;
}
.section-divider {
border: none;
border-top: 1px solid #1e293b;
margin: 1.5rem 0 1.25rem;
}
.section-title {
font-size: 0.72rem;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 1rem;
}
.optional { color: #475569; font-size: 0.7rem; margin-left: 0.3rem; text-transform: none; letter-spacing: 0; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 2rem;
}
.btn {
display: inline-block;
padding: 0.5rem 1.25rem;
border-radius: 6px;
font-size: 0.875rem;
cursor: pointer;
border: none;
}
.btn-primary { background: #38bdf8; color: #0f172a; font-weight: 600; }
.btn-primary:hover { opacity: 0.88; }
.btn-ghost { background: transparent; color: #64748b; border: 1px solid #334155; text-decoration: none; padding: 0.5rem 1.25rem; }
.btn-ghost:hover { color: #e2e8f0; }
.errorlist { list-style: none; color: #f87171; font-size: 0.8rem; margin-top: 0.3rem; }
</style>
</head>
<body>
<header class="site-header">
<a class="brand" href="/">k-boris.tech</a>
<nav>
<a href="{% url 'backlogger:list' %}">&larr; Back to backlogger</a>
</nav>
</header>
<div class="container">
<div class="card">
<h1>{{ action }} item</h1>
<form method="post">
{% csrf_token %}
<div class="field">
<label for="{{ form.category.id_for_label }}">Category</label>
{{ form.category }}
{{ form.category.errors }}
</div>
<div class="field">
<label for="{{ form.name.id_for_label }}">Name</label>
{{ form.name }}
{{ form.name.errors }}
</div>
<div class="field" data-hide-status="unending">
<div class="progress-label">
<label for="{{ form.progress_percent.id_for_label }}">Progress</label>
<span class="progress-val" id="progress-display">{{ form.progress_percent.value|default:0|floatformat:0 }}%</span>
</div>
{{ form.progress_percent }}
{{ form.progress_percent.errors }}
</div>
<div class="field">
<div class="checkbox-row">
{{ form.favorite }}
<label for="{{ form.favorite.id_for_label }}">Mark as favorite</label>
</div>
</div>
<!-- Games fields -->
<div class="cat-section" data-show-category="games">
<hr class="section-divider">
<div class="section-title">Games</div>
<div class="two-col">
<div class="field">
<label>Hours played</label>
{{ form.hours_played }}
{{ form.hours_played.errors }}
</div>
<div class="field" data-hide-status="unending">
<label>Total hours <span class="optional">optional</span></label>
{{ form.total_hours }}
{{ form.total_hours.errors }}
{% if item.hltb_main or item.hltb_extra or item.hltb_complete %}
<div style="margin-top:0.4rem;font-size:0.72rem;color:#475569;line-height:1.6">
HowLongToBeat:
{% if item.hltb_main %}<span style="color:#64748b">Main {{ item.hltb_main|floatformat:0 }}h</span>{% endif %}
{% if item.hltb_extra %}<span style="color:#64748b"> · +Extra {{ item.hltb_extra|floatformat:0 }}h</span>{% endif %}
{% if item.hltb_complete %}<span style="color:#64748b"> · 100% {{ item.hltb_complete|floatformat:0 }}h</span>{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
<!-- Books fields -->
<div class="cat-section" data-show-category="books">
<hr class="section-divider">
<div class="section-title">Books</div>
<div class="two-col">
<div class="field">
<label>Pages read</label>
{{ form.pages_read }}
{{ form.pages_read.errors }}
</div>
<div class="field">
<label>Total pages <span class="optional">optional</span></label>
{{ form.total_pages }}
{{ form.total_pages.errors }}
</div>
</div>
</div>
<!-- Films fields -->
<div class="cat-section" data-show-category="films">
<hr class="section-divider">
<div class="section-title">Films</div>
<div class="two-col">
<div class="field" style="display:flex; align-items:center; padding-top:1.5rem;">
<div class="checkbox-row">
{{ form.watched }}
<label for="{{ form.watched.id_for_label }}">Watched</label>
</div>
</div>
<div class="field">
<label>Duration (min) <span class="optional">optional</span></label>
{{ form.duration_minutes }}
{{ form.duration_minutes.errors }}
</div>
</div>
</div>
<div class="actions">
<a href="{% url 'backlogger:list' %}" class="btn btn-ghost">Cancel</a>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</form>
</div>
</div>
<script>
const catSelect = document.getElementById('{{ form.category.id_for_label }}');
const progressRange = document.getElementById('{{ form.progress_percent.id_for_label }}');
const progressDisplay = document.getElementById('progress-display');
const itemStatus = '{{ item.status|default:"active" }}';
function updateVisibility() {
const cat = catSelect.value;
// Show only the section matching the selected category
document.querySelectorAll('[data-show-category]').forEach(el => {
el.style.display = el.dataset.showCategory === cat ? 'block' : 'none';
});
// Hide fields that are not applicable for the current status
document.querySelectorAll('[data-hide-status]').forEach(el => {
el.style.display = el.dataset.hideStatus === itemStatus ? 'none' : '';
});
}
catSelect.addEventListener('change', updateVisibility);
updateVisibility();
progressRange.addEventListener('input', function() {
progressDisplay.textContent = Math.round(this.value) + '%';
});
</script>
</body>
</html>