289 lines
9 KiB
Text
289 lines
9 KiB
Text
package views
|
|
|
|
import "xtablo-backend/internal/web/ui"
|
|
|
|
templ TablosPageContent(vm TablosPageViewModel) {
|
|
<div class="px-4 pt-8 pb-6">
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8 gap-4">
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Mes Projets</h1>
|
|
@ui.Button(ui.ButtonProps{
|
|
Label: "Nouveau projet",
|
|
Variant: ui.ButtonVariantPrimary,
|
|
Size: ui.SizeMD,
|
|
Type: "button",
|
|
Icon: "plus",
|
|
Attrs: templ.Attributes{
|
|
"hx-get": vm.CreateModalHref(),
|
|
"hx-target": "#app-main-content",
|
|
"hx-swap": "outerHTML",
|
|
"hx-push-url": "true",
|
|
},
|
|
})
|
|
</div>
|
|
<div class="flex items-center gap-6 mb-6 border-b border-[#EAECF0] dark:border-gray-700">
|
|
<a
|
|
href={ templ.SafeURL(vm.ViewHref("grid")) }
|
|
hx-get={ vm.ViewHref("grid") }
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="true"
|
|
class={ gridToggleClass(vm.IsGridView()) }
|
|
>
|
|
<span class="w-5 h-5">
|
|
@ActionIcon("grid3x3")
|
|
</span>
|
|
<span class="font-medium">Vue en grille</span>
|
|
</a>
|
|
<a
|
|
href={ templ.SafeURL(vm.ViewHref("list")) }
|
|
hx-get={ vm.ViewHref("list") }
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="true"
|
|
class={ listToggleClass(vm.IsGridView()) }
|
|
>
|
|
<span class="w-5 h-5">
|
|
@ActionIcon("list")
|
|
</span>
|
|
<span class="font-medium">Vue en liste</span>
|
|
</a>
|
|
</div>
|
|
<div class="flex flex-col md:flex-row gap-4 mb-6">
|
|
<form
|
|
class="relative md:w-[350px]"
|
|
hx-get={ vm.SearchHref() }
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="true"
|
|
hx-trigger="input changed delay:300ms from:input[name='q']"
|
|
>
|
|
<input type="hidden" name="view" value={ vm.View }/>
|
|
<input type="hidden" name="status" value={ vm.Status }/>
|
|
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5 pointer-events-none">
|
|
@ActionIcon("search")
|
|
</span>
|
|
<input
|
|
name="q"
|
|
value={ vm.Query }
|
|
placeholder="Rechercher..."
|
|
class="w-full pl-10 pr-4 py-3 border border-[#EAECF0] dark:border-gray-700 rounded-[8px] focus:outline-none focus:ring-2 focus:ring-purple-500 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400"
|
|
type="text"
|
|
/>
|
|
</form>
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
@StatusPill(vm, "all", "Tous")
|
|
@StatusPill(vm, "todo", "Pas commencé")
|
|
@StatusPill(vm, "in_progress", "En cours")
|
|
@StatusPill(vm, "done", "Terminé")
|
|
</div>
|
|
</div>
|
|
if vm.HasTablos() {
|
|
if vm.IsGridView() {
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6">
|
|
for _, tablo := range vm.Tablos {
|
|
@TabloGridCard(tablo)
|
|
}
|
|
</div>
|
|
} else {
|
|
<div class="bg-white dark:bg-gray-800 rounded-xl border border-[#EAECF0] dark:border-gray-700 overflow-x-auto -mx-4 sm:mx-0">
|
|
@ui.Table(ui.TableProps{
|
|
Head: TabloListHead(),
|
|
Body: TabloListBody(vm.Tablos),
|
|
})
|
|
</div>
|
|
}
|
|
} else {
|
|
@ui.EmptyState(ui.EmptyStateProps{
|
|
Title: "Aucun projet trouvé",
|
|
Description: "Créez votre premier projet",
|
|
Icon: ui.UIIcon("grid3x3"),
|
|
Action: ui.Button(ui.ButtonProps{
|
|
Label: "Nouveau projet",
|
|
Variant: ui.ButtonVariantPrimary,
|
|
Size: ui.SizeMD,
|
|
Type: "button",
|
|
Icon: "plus",
|
|
Attrs: templ.Attributes{
|
|
"hx-get": vm.CreateModalHref(),
|
|
"hx-target": "#app-main-content",
|
|
"hx-swap": "outerHTML",
|
|
"hx-push-url": "true",
|
|
},
|
|
}),
|
|
})
|
|
}
|
|
if vm.ModalOpen {
|
|
@CreateTabloModal(vm)
|
|
}
|
|
</div>
|
|
}
|
|
|
|
templ StatusPill(vm TablosPageViewModel, status string, label string) {
|
|
<a
|
|
href={ templ.SafeURL(vm.StatusHref(status)) }
|
|
hx-get={ vm.StatusHref(status) }
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="true"
|
|
class={ statusPillClass(vm.Status == status) }
|
|
>
|
|
if status == "all" {
|
|
<span class="w-4 h-4">
|
|
@ActionIcon("filter")
|
|
</span>
|
|
}
|
|
{ label }
|
|
</a>
|
|
}
|
|
|
|
templ BorderlessDeleteButton(deleteRequestURL string) {
|
|
@ui.IconButton(ui.IconButtonProps{
|
|
Label: "Supprimer le projet",
|
|
Icon: "trash",
|
|
Variant: ui.IconButtonVariantDangerGhost,
|
|
Type: "button",
|
|
Attrs: templ.Attributes{
|
|
"hx-delete": deleteRequestURL,
|
|
"hx-target": "#app-main-content",
|
|
"hx-swap": "outerHTML",
|
|
"hx-confirm": "Supprimer ce projet ?",
|
|
},
|
|
})
|
|
}
|
|
|
|
templ TabloGridCard(tablo TabloCardView) {
|
|
<article class="project-card">
|
|
<div class="project-card-top">
|
|
@ui.Badge(ui.BadgeProps{
|
|
Label: tablo.StatusLabel,
|
|
Variant: badgeVariantForTone(tablo.StatusTone),
|
|
})
|
|
@BorderlessDeleteButton(tablo.DeleteRequestURL)
|
|
</div>
|
|
<div class="project-card-title-row">
|
|
<div class={ "project-avatar " + projectAccentClass(tablo.Accent) }>
|
|
<span>{ tablo.Initial }</span>
|
|
</div>
|
|
<h4>{ tablo.Name }</h4>
|
|
</div>
|
|
<div class="project-date-row">
|
|
@ActionIcon("calendar")
|
|
<span>{ tablo.CardDateLabel }</span>
|
|
</div>
|
|
<div class="project-progress">
|
|
<div class="project-progress-label">
|
|
<span>Progression:</span>
|
|
<strong>{ tablo.ProgressLabel }</strong>
|
|
</div>
|
|
<div class="project-progress-track">
|
|
<div class={ "project-progress-bar " + projectAccentClass(tablo.Accent) } style={ progressInlineStyle(tablo.Progress) }></div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
}
|
|
|
|
templ TabloListRow(tablo TabloCardView) {
|
|
<tr class="border-t border-[#EAECF0] dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
|
|
<td class="px-6 py-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class={ "w-8 h-8 rounded-lg flex items-center justify-center shrink-0 overflow-hidden [&>svg]:w-4 [&>svg]:h-4 " + tablo.IconBgClass + " " + tablo.IconFgClass }>
|
|
@ActionIcon(tablo.IconKind)
|
|
</div>
|
|
<span class="font-medium text-gray-900 dark:text-gray-100 truncate">{ tablo.Name }</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
@ui.Badge(ui.BadgeProps{
|
|
Label: tablo.StatusLabel,
|
|
Variant: badgeVariantForTone(tablo.StatusTone),
|
|
})
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-gray-400">
|
|
<div class="flex items-center gap-1.5 [&>svg]:w-4 [&>svg]:h-4 [&>svg]:shrink-0">
|
|
@ActionIcon("calendar")
|
|
{ tablo.CreatedAtLabel }
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2 min-w-[80px]">
|
|
<div class="bg-green-500 h-2 rounded-full transition-all" style={ progressInlineStyle(tablo.Progress) }></div>
|
|
</div>
|
|
<span class="text-sm font-semibold text-gray-900 dark:text-gray-100 w-8 text-right">{ tablo.ProgressLabel }</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-4 text-right">
|
|
@BorderlessDeleteButton(tablo.DeleteRequestURL)
|
|
</td>
|
|
</tr>
|
|
}
|
|
|
|
templ CreateTabloModal(vm TablosPageViewModel) {
|
|
@ui.Modal(ui.ModalProps{
|
|
Title: "Nouveau projet",
|
|
Body: CreateTabloModalBody(vm),
|
|
})
|
|
}
|
|
|
|
templ TabloListHead() {
|
|
<tr class="bg-gray-50 dark:bg-gray-800/80 border-b border-[#EAECF0] dark:border-gray-700">
|
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">Projet</th>
|
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">Statut</th>
|
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">Créé le</th>
|
|
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">Progression</th>
|
|
<th class="px-6 py-3 w-12"></th>
|
|
</tr>
|
|
}
|
|
|
|
templ TabloListBody(tablos []TabloCardView) {
|
|
for _, tablo := range tablos {
|
|
@TabloListRow(tablo)
|
|
}
|
|
}
|
|
|
|
templ CreateTabloModalBody(vm TablosPageViewModel) {
|
|
<form
|
|
hx-post="/tablos"
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
class="flex flex-col gap-4"
|
|
>
|
|
<input type="hidden" name="view" value={ vm.View }/>
|
|
<input type="hidden" name="status" value={ vm.Status }/>
|
|
<input type="hidden" name="q" value={ vm.Query }/>
|
|
<input type="hidden" name="modal" value="create"/>
|
|
if vm.ErrorMessage != "" {
|
|
<div class="mb-1 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{ vm.ErrorMessage }</div>
|
|
}
|
|
@ui.FormField(ui.FormFieldProps{
|
|
Label: "Nom du projet",
|
|
For: "tablo-name",
|
|
Field: ui.Input(ui.InputProps{
|
|
ID: "tablo-name",
|
|
Name: "name",
|
|
Value: vm.FormName,
|
|
Placeholder: "Nom du projet",
|
|
Type: "text",
|
|
}),
|
|
Error: vm.ErrorMessage,
|
|
})
|
|
<div class="flex items-center justify-end gap-3">
|
|
<a
|
|
href={ templ.SafeURL(vm.CloseModalHref()) }
|
|
hx-get={ vm.CloseModalHref() }
|
|
hx-target="#app-main-content"
|
|
hx-swap="outerHTML"
|
|
hx-push-url="true"
|
|
class="ui-button ui-button-secondary ui-button-md"
|
|
>
|
|
Annuler
|
|
</a>
|
|
@ui.Button(ui.ButtonProps{
|
|
Label: "Créer le projet",
|
|
Variant: ui.ButtonVariantPrimary,
|
|
Size: ui.SizeMD,
|
|
Type: "submit",
|
|
})
|
|
</div>
|
|
</form>
|
|
}
|