xtablo-source/go-backend/internal/web/views/dashboard_components.templ
2026-05-10 10:37:47 +02:00

384 lines
12 KiB
Text

package views
import "strconv"
import "xtablo-backend/internal/web/ui"
templ DashboardPage(activePath string, content templ.Component) {
@DashboardPageWithMainClass(activePath, "dashboard-main flex-1 overflow-auto", content)
}
templ DashboardPageWithMainClass(activePath string, mainClass string, content templ.Component) {
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>XTablo</title>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@4.0.0-beta2/dist/htmx.min.js"></script>
<link rel="stylesheet" href="/static/tailwind.css"/>
<link rel="stylesheet" href="/static/styles.css"/>
</head>
<body>
<div class="dashboard-shell">
@DashboardSidebar(activePath)
@DashboardMainContentWithClass(mainClass, content)
</div>
</body>
</html>
}
templ DashboardNotFoundPage(displayName string, email string) {
@DashboardPage("", NotFoundContent(displayName))
}
templ DashboardMainContent(content templ.Component) {
@DashboardMainContentWithClass("dashboard-main flex-1 overflow-auto", content)
}
templ DashboardMainContentWithClass(mainClass string, content templ.Component) {
<main id="app-main-content" class={ mainClass }>
@content
</main>
}
templ DashboardContentSwap(activePath string, content templ.Component) {
@DashboardContentSwapWithMainClass(activePath, "dashboard-main flex-1 overflow-auto", content)
}
templ DashboardContentSwapWithMainClass(activePath string, mainClass string, content templ.Component) {
@DashboardMainContentWithClass(mainClass, content)
@DashboardNavOOB(activePath)
}
templ DashboardSidebar(activePath string) {
<aside class="dashboard-sidebar">
<nav aria-label="Main navigation" class="sidebar-nav-shell">
<div class="sidebar-brand">
<a class="sidebar-brand-link" aria-label="Home" href="/" hx-get="/" hx-target="#app-main-content" hx-swap="outerHTML" hx-push-url="true">
<img alt="Logo XTablo" class="sidebar-brand-logo" src="/logo_dark.png"/>
<h1 class="sidebar-brand-title">XTablo</h1>
</a>
<button class="sidebar-collapse-button" aria-label="Collapse navigation" aria-expanded="true" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 12h14"></path>
</svg>
</button>
</div>
<div class="sidebar-primary">
<ul class="sidebar-list" role="list">
for _, item := range sidebarPrimaryNavItems(activePath) {
<li>
@SidebarNavItem(item)
</li>
if item.DividerAfter {
<li class="sidebar-divider"><hr role="separator"/></li>
}
}
</ul>
<div class="sidebar-projects">
<hr role="separator"/>
<div class="sidebar-section-label">Projets</div>
<ul class="sidebar-project-list">
for _, item := range sidebarProjectItems() {
<li>
@SidebarProjectItem(item)
</li>
}
</ul>
</div>
<ul class="sidebar-list sidebar-footer-links" role="list">
for _, item := range sidebarFooterNavItems(activePath) {
<li>
@SidebarNavItem(item)
</li>
}
</ul>
</div>
@SidebarOrganization()
</nav>
</aside>
}
templ DashboardNavOOB(activePath string) {
for _, item := range sidebarPrimaryNavItems(activePath) {
@SidebarNavItemOOB(item)
}
for _, item := range sidebarFooterNavItems(activePath) {
@SidebarNavItemOOB(item)
}
}
templ SidebarOrganization() {
<div class="sidebar-organization">
<button class="organization-button" aria-label="Organization menu" type="button">
<span class="organization-avatar">
<img alt="Arctic Matrix" src="/logo_dark.png"/>
</span>
<span class="organization-copy">
<span class="organization-name">Arctic Matrix</span>
<span class="organization-meta">1 membre</span>
</span>
</button>
</div>
}
templ OverviewMainContent(displayName string, email string, tablos []TabloCardView) {
<div class="overview-page">
@OverviewHeader(displayName)
@OverviewActions(overviewQuickActions())
@OverviewProjectsSection(tablos)
@OverviewTasks(overviewTasks())
</div>
}
templ TasksMainContent() {
@AppSectionMainContent("Tâches", "Suivez les tâches de votre équipe, les priorités en cours et ce qui reste à livrer.")
}
templ TablosMainContent() {
@AppSectionMainContent("Projets", "Gardez une vue claire sur vos tablos, leur état d'avancement et les prochaines décisions à prendre.")
}
templ PlanningMainContent() {
@AppSectionMainContent("Planning", "Visualisez le rythme de l'équipe, les jalons à venir et les arbitrages de charge.")
}
templ ChatMainContent() {
@AppSectionMainContent("Discussions", "Retrouvez les conversations importantes, les décisions récentes et les échanges à relancer.")
}
templ FilesMainContent() {
@AppSectionMainContent("Fichiers", "Centralisez les documents utiles, les pièces partagées et les ressources de travail.")
}
templ FeedbackMainContent() {
@AppSectionMainContent("Feedback", "Collectez les retours produit, priorisez les signaux et transformez-les en actions concrètes.")
}
templ AppSectionMainContent(title string, description string) {
<div class="app-section-page">
<div class="app-section-surface">
<div class="app-section-eyebrow">Espace de travail</div>
<h2>{ title }</h2>
<p>{ description }</p>
</div>
</div>
}
templ NotFoundContent(displayName string) {
<div class="not-found-page">
<div class="not-found-surface">
<div class="not-found-eyebrow">Erreur de navigation</div>
<div class="not-found-code">404</div>
<h2>Page introuvable</h2>
<p>Cette page n'existe pas ou n'est plus disponible.</p>
<div class="not-found-actions">
<a class="not-found-primary" href="/">Retour à l'aperçu</a>
<form action="/logout" method="post" class="not-found-secondary-form">
<button type="submit" class="not-found-secondary">Se déconnecter</button>
</form>
</div>
<div class="not-found-meta">
<span>Connecté en tant que</span>
<strong>{ dashboardGreetingName(displayName) }</strong>
</div>
</div>
</div>
}
templ OverviewHeader(displayName string) {
<header class="overview-header">
<p class="overview-date">{ dashboardTodayLabel() }</p>
<div class="overview-header-row">
<h2 class="overview-greeting">Bonjour, <span>{ dashboardGreetingName(displayName) }</span>!</h2>
<div class="overview-header-actions">
<span class="overview-badge">Founder</span>
<form action="/logout" method="post" class="overview-logout-form">
@ui.Button(ui.ButtonProps{
Label: "Se déconnecter",
Variant: ui.ButtonVariantDanger,
Tone: ui.ButtonToneSoft,
Size: ui.SizeMD,
Type: "submit",
})
</form>
</div>
</div>
</header>
}
templ OverviewActions(actions []quickAction) {
<section class="overview-actions">
for _, action := range actions {
@QuickActionCard(action)
}
</section>
}
templ OverviewProjectsSection(projects []TabloCardView) {
<section id="overview-projects-section" class="overview-section">
<div class="overview-section-heading">
<h3>Mes Projets</h3>
</div>
<div class="project-grid">
for _, project := range visibleOverviewProjects(projects) {
@TabloGridCard(project)
}
for _, project := range hiddenOverviewProjects(projects) {
@TabloGridCardWithAttrs(project, templ.Attributes{
"data-overview-project-hidden": "true",
"hidden": true,
})
}
</div>
@SeeMoreProjects(hiddenOverviewProjectsCount(projects))
</section>
@OverviewProjectsScript()
}
templ SeeMoreProjects(hiddenCount int) {
if hiddenCount > 0 {
<div class="overview-more-row">
<button
type="button"
class="overview-more-button"
data-overview-see-more="true"
data-overview-expanded="false"
data-overview-hidden-count={ strconv.Itoa(hiddenCount) }
data-overview-hide-label="Masquer"
data-overview-chevron-down="m6 9 6 6 6-6"
data-overview-chevron-up="m6 15 6-6 6 6"
>
<span data-overview-see-more-label="true">Voir { hiddenCount } de plus</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path data-overview-see-more-chevron="true" d="m6 9 6 6 6-6"></path>
</svg>
</button>
</div>
}
}
templ OverviewProjectsScript() {
<script>
(function () {
if (window.xtabloOverviewProjectsInitialized) return;
document.addEventListener("click", function (event) {
var button = event.target.closest("[data-overview-see-more]");
if (!button) return;
var section = button.closest("#overview-projects-section");
if (!section) return;
var expanded = button.getAttribute("data-overview-expanded") === "true";
var label = button.querySelector("[data-overview-see-more-label]");
var chevron = button.querySelector("[data-overview-see-more-chevron]");
var hiddenCount = button.getAttribute("data-overview-hidden-count");
var hideLabel = button.getAttribute("data-overview-hide-label") || "Masquer";
var downChevron = button.getAttribute("data-overview-chevron-down") || "m6 9 6 6 6-6";
var upChevron = button.getAttribute("data-overview-chevron-up") || "m6 15 6-6 6 6";
section.querySelectorAll("[data-overview-project-hidden=\"true\"]").forEach(function (project) {
project.hidden = expanded;
if (expanded) {
project.setAttribute("hidden", "");
} else {
project.removeAttribute("hidden");
}
});
button.setAttribute("data-overview-expanded", expanded ? "false" : "true");
if (label) {
label.textContent = expanded ? "Voir " + hiddenCount + " de plus" : hideLabel;
}
if (chevron) {
chevron.setAttribute("d", expanded ? downChevron : upChevron);
}
});
window.xtabloOverviewProjectsInitialized = true;
})();
</script>
}
templ OverviewTasks(tasks []dashboardTask) {
<section class="overview-section tasks-section">
<div class="tasks-section-header">
<h3>Mes Tâches</h3>
<button type="button" class="tasks-add-button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
<span>Ajouter</span>
</button>
</div>
<div class="task-list">
for _, task := range tasks {
@TaskRow(task)
}
</div>
</section>
}
templ QuickActionCard(action quickAction) {
<button class="quick-action-card" type="button">
<div class="quick-action-icon">
@ActionIcon(action.Icon)
</div>
<div class="quick-action-copy">
<div class="quick-action-title">{ action.Title }</div>
<p>{ action.Description }</p>
</div>
</button>
}
templ TaskRow(task dashboardTask) {
<div class={ taskRowClass(task.Completed) }>
<button class={ taskCheckClass(task.Completed) } type="button" aria-label="Marquer la tâche">
if task.Completed {
@ActionIcon("check-circle")
}
</button>
<div class="task-body">
<p>{ task.Title }</p>
<div class="task-meta">
<div class={ "task-project-badge " + projectAccentClass(task.ProjectHue) }>
<span>{ task.ProjectKey }</span>
</div>
<span class="task-project-name">{ task.Project }</span>
<span class="task-date">{ task.Date }</span>
</div>
</div>
<span class={ "task-status " + toneClass(task.StatusTone) }>{ task.Status }</span>
</div>
}
templ SidebarNavItem(item sidebarNavItem) {
<div id={ sidebarNavItemID(item.Href) } class={ sidebarNavItemClass(item.Active) }>
<a class="sidebar-nav-link" href={ templ.SafeURL(item.Href) } hx-get={ item.Href } hx-target="#app-main-content" hx-swap="outerHTML" hx-push-url="true">
<div class="sidebar-nav-link-inner">
<span class="sidebar-nav-icon">
@SidebarIcon(item.Icon)
</span>
<div class="sidebar-nav-label">{ item.Label }</div>
</div>
</a>
</div>
}
templ SidebarNavItemOOB(item sidebarNavItem) {
<div id={ sidebarNavItemID(item.Href) } class={ sidebarNavItemClass(item.Active) } hx-swap-oob="outerHTML">
<a class="sidebar-nav-link" href={ templ.SafeURL(item.Href) } hx-get={ item.Href } hx-target="#app-main-content" hx-swap="outerHTML" hx-push-url="true">
<div class="sidebar-nav-link-inner">
<span class="sidebar-nav-icon">
@SidebarIcon(item.Icon)
</span>
<div class="sidebar-nav-label">{ item.Label }</div>
</div>
</a>
</div>
}
templ SidebarProjectItem(item sidebarProjectItem) {
<a class="sidebar-project-link" href={ templ.SafeURL(item.Href) }>
<span class="sidebar-project-icon">
@SidebarIcon(item.Icon)
</span>
<span class="sidebar-project-label">{ item.Label }</span>
</a>
}