feat(19): list view as proper table — Projet/Statut/Créé le/Progression/Actions
- Two separate containers: #tablos-grid (card grid) and #tablos-table (table), toggled via .hidden - TabloListRow: new templ for <tr> with icon+title, Actif/Archivé badge, date, progress bar, trash - JS: setTablosView toggles hidden on both containers; filterTablos targets both article and tr elements - Remove old .tablo-list-row / #tablos-list[data-view] CSS (no longer needed) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
90d43f98bc
commit
a575ab93c6
3 changed files with 96 additions and 77 deletions
|
|
@ -844,57 +844,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transparent grid-item wrapper — children participate in grid/flex directly */
|
/* Grid/table view is toggled via .hidden Tailwind class on #tablos-grid / #tablos-table */
|
||||||
.tablo-card-wrapper {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* List-view row (hidden by default) */
|
|
||||||
.tablo-list-row {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border: 1px solid var(--color-border-subtle);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
background: var(--color-surface-default);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title cell in list row */
|
|
||||||
.tablo-list-row-title {
|
|
||||||
flex: 1;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Metadata cells in list row */
|
|
||||||
.tablo-list-row-meta {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
display: flex;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* data-view="list" toggle rules */
|
|
||||||
#tablos-list[data-view="list"] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tablos-list[data-view="list"] .project-card {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tablos-list[data-view="list"] .tablo-list-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Single-column layout in list view */
|
|
||||||
#tablos-list[data-view="list"] {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── View toggle tabs ─────────────────────────────────────── */
|
/* ── View toggle tabs ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -49,8 +49,8 @@ templ TablosDashboard(user *auth.User, csrfToken string, activePath string, tabl
|
||||||
<button type="button" class="filter-tab" data-filter-btn="active" onclick="filterTablos('active')">Actif</button>
|
<button type="button" class="filter-tab" data-filter-btn="active" onclick="filterTablos('active')">Actif</button>
|
||||||
<button type="button" class="filter-tab" data-filter-btn="archived" onclick="filterTablos('archived')">Archivé</button>
|
<button type="button" class="filter-tab" data-filter-btn="archived" onclick="filterTablos('archived')">Archivé</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Card/list grid -->
|
<!-- Grid view -->
|
||||||
<div id="tablos-list" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6" data-view="grid">
|
<div id="tablos-grid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6">
|
||||||
if len(cards) == 0 {
|
if len(cards) == 0 {
|
||||||
@TablosEmptyState()
|
@TablosEmptyState()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -59,28 +59,52 @@ templ TablosDashboard(user *auth.User, csrfToken string, activePath string, tabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- List view (hidden by default) -->
|
||||||
|
<div id="tablos-table" class="hidden bg-white rounded-xl border border-[#EAECF0] overflow-x-auto">
|
||||||
|
<table class="w-full min-w-[600px]">
|
||||||
|
<thead class="bg-gray-50 border-b border-[#EAECF0]">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Projet</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Statut</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Créé le</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Progression</th>
|
||||||
|
<th class="px-6 py-3 w-12"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
for _, card := range cards {
|
||||||
|
@TabloListRow(card, csrfToken)
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
function setTablosView(v) {
|
function setTablosView(v) {
|
||||||
document.getElementById('tablos-list').dataset.view = v;
|
document.getElementById('tablos-grid').classList.toggle('hidden', v === 'list');
|
||||||
|
document.getElementById('tablos-table').classList.toggle('hidden', v === 'grid');
|
||||||
document.querySelectorAll('[data-view-btn]').forEach(function(b) {
|
document.querySelectorAll('[data-view-btn]').forEach(function(b) {
|
||||||
b.classList.toggle('is-active', b.dataset.viewBtn === v);
|
b.classList.toggle('is-active', b.dataset.viewBtn === v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function filterTablos(s) {
|
function filterTablos(s) {
|
||||||
document.querySelectorAll('#tablos-list article').forEach(function(el) {
|
document.querySelectorAll('#tablos-grid article').forEach(function(el) {
|
||||||
|
el.style.display = (s === 'tous' || el.dataset.displayStatus === s) ? '' : 'none';
|
||||||
|
});
|
||||||
|
document.querySelectorAll('#tablos-table tbody tr').forEach(function(el) {
|
||||||
el.style.display = (s === 'tous' || el.dataset.displayStatus === s) ? '' : 'none';
|
el.style.display = (s === 'tous' || el.dataset.displayStatus === s) ? '' : 'none';
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-filter-btn]').forEach(function(b) {
|
document.querySelectorAll('[data-filter-btn]').forEach(function(b) {
|
||||||
b.classList.toggle('is-active', b.dataset.filterBtn === s);
|
b.classList.toggle('is-active', b.dataset.filterBtn === s);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Card click → navigate to tablo detail (stop on delete zone)
|
document.getElementById('tablos-grid').addEventListener('click', function(e) {
|
||||||
document.getElementById('tablos-list').addEventListener('click', function(e) {
|
var a = e.target.closest('article[data-href]');
|
||||||
var article = e.target.closest('article[data-href]');
|
if (a && !e.target.closest('.tablo-delete-zone')) window.location = a.dataset.href;
|
||||||
if (article && !e.target.closest('.tablo-delete-zone')) {
|
});
|
||||||
window.location = article.dataset.href;
|
document.getElementById('tablos-table').addEventListener('click', function(e) {
|
||||||
}
|
var r = e.target.closest('tr[data-href]');
|
||||||
|
if (r && !e.target.closest('.tablo-delete-zone')) window.location = r.dataset.href;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
@ -189,21 +213,66 @@ templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- List row (hidden by default, shown when data-view="list") -->
|
</article>
|
||||||
<div class="tablo-list-row" onclick="event.stopPropagation()">
|
}
|
||||||
<a class="flex items-center gap-4 w-full px-4 py-3 hover:bg-gray-50" href={ templ.SafeURL("/tablos/" + card.Tablo.ID.String()) }>
|
|
||||||
if tabloDisplayStatus(card.Progress, card.TotalTasks) == "termine" {
|
// TabloListRow renders one table row for the list view.
|
||||||
<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-green-50 text-green-600 border border-green-200 shrink-0">Terminé</span>
|
templ TabloListRow(card TabloCardView, csrfToken string) {
|
||||||
} else if tabloDisplayStatus(card.Progress, card.TotalTasks) == "en-cours" {
|
<tr
|
||||||
<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-[#FFF4E2] text-[#DB9729] border border-[#DB9729] shrink-0">En cours</span>
|
class="border-t border-[#EAECF0] hover:bg-gray-50 transition-colors cursor-pointer"
|
||||||
|
data-display-status={ card.Tablo.Status }
|
||||||
|
data-href={ "/tablos/" + card.Tablo.ID.String() }
|
||||||
|
>
|
||||||
|
<!-- Projet: icon + title -->
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
if card.Tablo.Color.Valid && card.Tablo.Color.String != "" {
|
||||||
|
<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden" style={ "background-color: " + card.Tablo.Color.String }>
|
||||||
|
<span class="text-white font-bold text-sm">
|
||||||
|
if len(card.Tablo.Title) > 0 {
|
||||||
|
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-600 border border-blue-200 shrink-0">À faire</span>
|
<div class="w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden bg-blue-500">
|
||||||
|
<span class="text-white font-bold text-sm">
|
||||||
|
if len(card.Tablo.Title) > 0 {
|
||||||
|
{ string([]rune(card.Tablo.Title)[0:1]) }
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<span class="tablo-list-row-title flex-1">{ card.Tablo.Title }</span>
|
<span class="font-medium text-gray-900 truncate">{ card.Tablo.Title }</span>
|
||||||
<span class="tablo-list-row-meta shrink-0">{ card.Tablo.CreatedAt.Time.Format("2 Jan 2006") }</span>
|
</div>
|
||||||
<span class="tablo-list-row-meta shrink-0">{ strconv.Itoa(card.DoneTasks) }/{ strconv.Itoa(card.TotalTasks) }</span>
|
</td>
|
||||||
</a>
|
<!-- Statut -->
|
||||||
<div class="tablo-delete-zone shrink-0" onclick="event.stopPropagation()">
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
if card.Tablo.Status == "archived" {
|
||||||
|
<span class="px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-600 border border-gray-200">Archivé</span>
|
||||||
|
} else {
|
||||||
|
<span class="px-3 py-1 rounded-full text-sm font-medium bg-green-50 text-green-600 border border-green-200">Actif</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<!-- Créé le -->
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 shrink-0" aria-hidden="true"><path d="M8 2v4"></path><path d="M16 2v4"></path><rect width="18" height="18" x="3" y="4" rx="2"></rect><path d="M3 10h18"></path></svg>
|
||||||
|
{ card.Tablo.CreatedAt.Time.Format("2 Jan 2006") }
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<!-- Progression -->
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="flex-1 bg-gray-200 rounded-full h-2 min-w-[80px]">
|
||||||
|
<div class="bg-green-500 h-2 rounded-full" style={ "width: " + strconv.Itoa(card.Progress) + "%" }></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-semibold text-gray-900 w-8 text-right">{ strconv.Itoa(card.Progress) }%</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<!-- Actions -->
|
||||||
|
<td class="px-6 py-4 text-right">
|
||||||
|
<div class="tablo-delete-zone inline-flex">
|
||||||
@ui.IconButton(ui.IconButtonProps{
|
@ui.IconButton(ui.IconButtonProps{
|
||||||
Label: "Delete tablo",
|
Label: "Delete tablo",
|
||||||
Icon: "trash",
|
Icon: "trash",
|
||||||
|
|
@ -217,8 +286,8 @@ templ TabloProjectCard(card TabloCardView, csrfToken string) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
</article>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabloCard renders a single tablo as a ui.Card on the dashboard.
|
// TabloCard renders a single tablo as a ui.Card on the dashboard.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue