T21: UI poboljšanja — detalj panel 50%, konzola fullscreen, docs full height

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-20 13:28:29 +00:00
parent 500899121b
commit f137703f1b
5 changed files with 179 additions and 11 deletions

View File

@ -0,0 +1,56 @@
# T21 Izveštaj: UI poboljšanja (konzola, task detalj, layout)
**Agent:** coder
**Model:** Opus
**Datum:** 2026-02-20
---
## Šta je urađeno
Tri UI poboljšanja: task detalj panel 50% ekrana, konzola fullscreen, dokumenti full height.
### Izmenjeni fajlovi
| Fajl | Izmena |
|------|--------|
| `web/static/style.css` | #task-detail 50% width, body.detail-open board compression, console fullscreen, docs full height, sidebar/main scroll |
| `web/templates/layout.html` | detail-open body class toggle, click-outside-to-close za detalj panel |
| `web/templates/console.html` | Toolbar premešten iznad panela |
| `internal/server/server_test.go` | 5 novih testova |
### 1. Task detalj panel — 50% ekrana
- Panel se otvara na 50% širine ekrana (umesto fiksnih 420px)
- Board se kompresuje na 50% kad je panel otvoren (`body.detail-open .board { margin-right: 50% }`)
- Klik van panela zatvara panel (osim klika na task karticu)
- Na mobilnom (<700px) panel je 100% kao overlay
- Tranzicija: slide sa desne strane (0.3s ease)
### 2. Konzola — fullscreen layout
- Toolbar (+ Sesija 2) premešten iznad panela za bolji UX
- `min-height: 80vh` na kontejneru
- Input red: `flex-shrink: 0` za uvek vidljiv input
- Toolbar: `flex-shrink: 0` za stabilnu poziciju
### 3. Dokumenti — full height
- `docs-container`: `height: calc(100vh - 60px)` umesto `min-height: 80vh`
- `docs-layout`: `flex: 1; min-height: 0` za ispravno popunjavanje prostora
- `docs-sidebar` i `docs-main`: `overflow-y: auto` za nezavisni scroll
### Novi testovi — 5 PASS
```
TestDashboard_DetailOpenClassInJS PASS
TestDashboard_ClickOutsideClosesDetail PASS
TestConsolePage_ToolbarAbovePanels PASS
TestConsolePage_HasSessionToggle PASS
TestDocsPage_HasFullHeightLayout PASS
```
### Ukupno projekat: 142 testova, svi prolaze
- `go vet ./...` — čist
- `go build ./...` — prolazi

View File

@ -1314,6 +1314,89 @@ func TestRewriteLinksSimple_NestedDir(t *testing.T) {
}
}
func TestDashboard_DetailOpenClassInJS(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// JS should add detail-open class when detail panel opens
if !containsStr(body, "detail-open") {
t.Error("expected 'detail-open' body class in dashboard JS")
}
}
func TestDashboard_ClickOutsideClosesDetail(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// Should have click-outside handler for detail panel
if !containsStr(body, "closest('.task-card')") {
t.Error("expected click-outside handler for detail panel")
}
}
func TestConsolePage_ToolbarAbovePanels(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
// Toolbar should appear before console-panels in the HTML
toolbarIdx := strings.Index(body, "console-toolbar")
panelsIdx := strings.Index(body, "console-panels")
if toolbarIdx == -1 {
t.Fatal("expected console-toolbar in console page")
}
if panelsIdx == -1 {
t.Fatal("expected console-panels in console page")
}
if toolbarIdx > panelsIdx {
t.Error("expected toolbar before panels in console HTML")
}
}
func TestConsolePage_HasSessionToggle(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/console", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "togglePanel2") {
t.Error("expected togglePanel2 button in console page")
}
if !containsStr(body, `+ Sesija 2`) {
t.Error("expected '+ Sesija 2' toggle button")
}
}
func TestDocsPage_HasFullHeightLayout(t *testing.T) {
srv := setupTestServer(t)
req := httptest.NewRequest(http.MethodGet, "/docs", nil)
w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req)
body := w.Body.String()
if !containsStr(body, "docs-container") {
t.Error("expected docs-container class")
}
if !containsStr(body, "docs-layout") {
t.Error("expected docs-layout class for grid layout")
}
}
func containsStr(s, substr string) bool {
return len(s) >= len(substr) && findStr(s, substr)
}

View File

@ -86,12 +86,12 @@ body {
margin-top: 4px;
}
/* Task detail panel */
/* Task detail panel — 50% of screen */
#task-detail {
position: fixed;
top: 0;
right: -420px;
width: 420px;
right: -50%;
width: 50%;
height: 100vh;
background: #16213e;
border-left: 2px solid #0f3460;
@ -105,6 +105,15 @@ body {
right: 0;
}
/* Board compresses when detail is open */
.board {
transition: margin-right 0.3s ease;
}
body.detail-open .board {
margin-right: 50%;
}
.detail-close {
cursor: pointer;
float: right;
@ -386,17 +395,20 @@ body {
border-color: #e94560;
}
/* Docs */
/* Docs — full height */
.docs-container {
padding: 16px 24px;
min-height: 80vh;
height: calc(100vh - 60px);
display: flex;
flex-direction: column;
}
.docs-layout {
display: grid;
grid-template-columns: 25% 1fr;
gap: 16px;
min-height: 80vh;
flex: 1;
min-height: 0;
}
.docs-sidebar h2 {
@ -450,8 +462,13 @@ body {
margin: 0 4px;
}
.docs-sidebar {
overflow-y: auto;
}
.docs-main {
min-width: 0;
overflow-y: auto;
}
.docs-content {
@ -536,12 +553,13 @@ body {
margin: 16px 0;
}
/* Console */
/* Console — fullscreen */
.console-container {
padding: 16px;
height: calc(100vh - 60px);
display: flex;
flex-direction: column;
min-height: 80vh;
}
.console-panels {
@ -624,6 +642,7 @@ body {
gap: 4px;
padding: 8px;
border-top: 1px solid #0f3460;
flex-shrink: 0;
}
.console-input {
@ -650,6 +669,7 @@ body {
padding: 8px 0;
display: flex;
gap: 8px;
flex-shrink: 0;
}
/* Responsive */
@ -660,6 +680,7 @@ body {
@media (max-width: 700px) {
.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; }
}

View File

@ -21,6 +21,10 @@
</div>
<div class="console-container">
<div class="console-toolbar">
<button class="btn" id="toggle-panel" onclick="togglePanel2()">+ Sesija 2</button>
</div>
<div class="console-panels">
<div class="console-panel" id="panel-1">
<div class="console-panel-header">
@ -48,10 +52,6 @@
</div>
</div>
</div>
<div class="console-toolbar">
<button class="btn" id="toggle-panel" onclick="togglePanel2()">+ Sesija 2</button>
</div>
</div>
<script>

View File

@ -36,6 +36,7 @@
document.body.addEventListener('htmx:afterSwap', function(e) {
if (e.detail.target.id === 'task-detail') {
e.detail.target.classList.add('active');
document.body.classList.add('detail-open');
}
});
@ -43,6 +44,7 @@ function closeDetail() {
var el = document.getElementById('task-detail');
el.classList.remove('active');
el.innerHTML = '';
document.body.classList.remove('detail-open');
}
function showToast(msg, type) {
@ -82,6 +84,12 @@ document.addEventListener('DOMContentLoaded', function() {
if (wrapper && !wrapper.contains(e.target)) {
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();
}
});
});