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:
parent
500899121b
commit
f137703f1b
56
TASKS/reports/T21-report.md
Normal file
56
TASKS/reports/T21-report.md
Normal 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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user