384 lines
12 KiB
Text
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>
|
|
}
|