Fix: Task detalj kao overlay modal umesto side panela

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-20 13:43:31 +00:00
parent b3645beea0
commit 5bf7375b50
4 changed files with 86 additions and 61 deletions

View File

@ -1563,7 +1563,7 @@ func TestDashboard_HasPrijavaNav(t *testing.T) {
// --- T21: UI tests --- // --- T21: UI tests ---
func TestDashboard_DetailOpenClassInJS(t *testing.T) { func TestDashboard_EscapeClosesOverlay(t *testing.T) {
srv := setupTestServer(t) srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
@ -1571,13 +1571,12 @@ func TestDashboard_DetailOpenClassInJS(t *testing.T) {
srv.Router.ServeHTTP(w, req) srv.Router.ServeHTTP(w, req)
body := w.Body.String() body := w.Body.String()
// JS should add detail-open class when detail panel opens if !containsStr(body, "Escape") {
if !containsStr(body, "detail-open") { t.Error("expected Escape key handler for overlay")
t.Error("expected 'detail-open' body class in dashboard JS")
} }
} }
func TestDashboard_ClickOutsideClosesDetail(t *testing.T) { func TestDashboard_BackdropClickClosesOverlay(t *testing.T) {
srv := setupTestServer(t) srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
@ -1585,9 +1584,21 @@ func TestDashboard_ClickOutsideClosesDetail(t *testing.T) {
srv.Router.ServeHTTP(w, req) srv.Router.ServeHTTP(w, req)
body := w.Body.String() body := w.Body.String()
// Should have click-outside handler for detail panel if !containsStr(body, "e.target === this") {
if !containsStr(body, "closest('.task-card')") { t.Error("expected backdrop click handler for overlay")
t.Error("expected click-outside handler for detail panel") }
}
func TestTaskDetail_HasInnerWrapper(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/task/T01", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "detail-inner") {
t.Error("expected detail-inner wrapper in task detail")
} }
} }

View File

@ -86,40 +86,45 @@ body {
margin-top: 4px; margin-top: 4px;
} }
/* Task detail panel — 50% of screen */ /* Task detail overlay (modal) */
#task-detail { #task-detail {
display: none;
position: fixed; position: fixed;
top: 0; top: 0;
right: -50%; left: 0;
width: 50%; width: 100%;
height: 100vh; height: 100vh;
background: #16213e; z-index: 50;
border-left: 2px solid #0f3460; background: rgba(0, 0, 0, 0.6);
padding: 20px; justify-content: center;
overflow-y: auto; align-items: center;
z-index: 10;
transition: right 0.3s ease;
} }
#task-detail.active { #task-detail.active {
right: 0; display: flex;
} }
/* Board compresses when detail is open */ .detail-inner {
.board { background: #16213e;
transition: margin-right 0.3s ease; border: 1px solid #0f3460;
} border-radius: 10px;
padding: 24px;
body.detail-open .board { width: 700px;
margin-right: 50%; max-width: 90%;
max-height: 85vh;
overflow-y: auto;
position: relative;
} }
.detail-close { .detail-close {
cursor: pointer; cursor: pointer;
float: right; position: absolute;
font-size: 1.2em; top: 12px;
right: 16px;
font-size: 1.4em;
color: #888; color: #888;
padding: 4px 8px; padding: 4px 8px;
line-height: 1;
} }
.detail-close:hover { color: #e94560; } .detail-close:hover { color: #e94560; }
@ -822,8 +827,6 @@ textarea.form-input {
@media (max-width: 700px) { @media (max-width: 700px) {
.board { grid-template-columns: repeat(2, 1fr); } .board { grid-template-columns: repeat(2, 1fr); }
#task-detail { width: 100%; right: -100%; }
body.detail-open .board { margin-right: 0; }
.docs-layout { grid-template-columns: 1fr; } .docs-layout { grid-template-columns: 1fr; }
} }

View File

@ -37,7 +37,6 @@
document.body.addEventListener('htmx:afterSwap', function(e) { document.body.addEventListener('htmx:afterSwap', function(e) {
if (e.detail.target.id === 'task-detail') { if (e.detail.target.id === 'task-detail') {
e.detail.target.classList.add('active'); e.detail.target.classList.add('active');
document.body.classList.add('detail-open');
} }
}); });
@ -45,9 +44,25 @@ function closeDetail() {
var el = document.getElementById('task-detail'); var el = document.getElementById('task-detail');
el.classList.remove('active'); el.classList.remove('active');
el.innerHTML = ''; el.innerHTML = '';
document.body.classList.remove('detail-open');
} }
// Close overlay on Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
var detail = document.getElementById('task-detail');
if (detail && detail.classList.contains('active')) {
closeDetail();
}
}
});
// Close overlay on backdrop click
document.getElementById('task-detail').addEventListener('click', function(e) {
if (e.target === this) {
closeDetail();
}
});
function showToast(msg, type) { function showToast(msg, type) {
var toast = document.getElementById('toast'); var toast = document.getElementById('toast');
toast.textContent = msg; toast.textContent = msg;
@ -85,12 +100,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (wrapper && !wrapper.contains(e.target)) { if (wrapper && !wrapper.contains(e.target)) {
results.innerHTML = ''; results.innerHTML = '';
} }
// Close detail panel on click outside
var detail = document.getElementById('task-detail');
if (detail && detail.classList.contains('active') && !detail.contains(e.target) && !e.target.closest('.task-card')) {
closeDetail();
}
}); });
}); });

View File

@ -1,28 +1,30 @@
{{define "task-detail"}} {{define "task-detail"}}
<span class="detail-close" onclick="closeDetail()"></span> <div class="detail-inner">
<h2>{{.Task.ID}}: {{.Task.Title}}</h2> <span class="detail-close" onclick="closeDetail()"></span>
<div class="detail-meta"> <h2>{{.Task.ID}}: {{.Task.Title}}</h2>
<p><strong>Agent:</strong> {{.Task.Agent}} · <strong>Model:</strong> {{.Task.Model}} · <strong>Status:</strong> {{.Task.Status}}</p> <div class="detail-meta">
{{if .Task.DependsOn}} <p><strong>Agent:</strong> {{.Task.Agent}} · <strong>Model:</strong> {{.Task.Model}} · <strong>Status:</strong> {{.Task.Status}}</p>
<p><strong>Zavisi od:</strong> {{joinDeps .Task.DependsOn}}</p> {{if .Task.DependsOn}}
<p><strong>Zavisi od:</strong> {{joinDeps .Task.DependsOn}}</p>
{{end}}
</div>
{{if .HasReport}}
<div class="detail-actions">
<a href="/report/{{.Task.ID}}" class="btn" target="_blank">Izvestaj</a>
</div>
{{end}} {{end}}
</div> <div class="detail-actions">
{{if .HasReport}} {{if eq .Task.Status "backlog"}}
<div class="detail-actions"> <button class="btn btn-success" hx-post="/task/{{.Task.ID}}/move?to=ready" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Odobri</button>
<a href="/report/{{.Task.ID}}" class="btn" target="_blank">Izvestaj</a> {{end}}
{{if eq .Task.Status "ready"}}
<button class="btn btn-run" hx-post="/task/{{.Task.ID}}/run" hx-swap="none" onclick="closeDetail()">Pusti</button>
{{end}}
{{if eq .Task.Status "review"}}
<button class="btn btn-success" hx-post="/task/{{.Task.ID}}/move?to=done" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Odobri</button>
<button class="btn btn-move" hx-post="/task/{{.Task.ID}}/move?to=ready" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Vrati</button>
{{end}}
</div>
<div class="detail-content">{{.Content}}</div>
</div> </div>
{{end}} {{end}}
<div class="detail-actions">
{{if eq .Task.Status "backlog"}}
<button class="btn btn-success" hx-post="/task/{{.Task.ID}}/move?to=ready" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Odobri</button>
{{end}}
{{if eq .Task.Status "ready"}}
<button class="btn btn-run" hx-post="/task/{{.Task.ID}}/run" hx-swap="none" onclick="closeDetail()">Pusti</button>
{{end}}
{{if eq .Task.Status "review"}}
<button class="btn btn-success" hx-post="/task/{{.Task.ID}}/move?to=done" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Odobri</button>
<button class="btn btn-move" hx-post="/task/{{.Task.ID}}/move?to=ready" hx-target="#board" hx-swap="outerHTML" onclick="closeDetail()">Vrati</button>
{{end}}
</div>
<div class="detail-content">{{.Content}}</div>
{{end}}