xtablo-source/deprecated/internal/web/views/tasks.templ
Arthur Belleville 5d0c201e86
Some checks failed
backend-ci / Backend tests (pull_request) Failing after 53s
backend-ci / Backend tests (push) Failing after 1s
Some work
2026-05-23 17:26:01 +02:00

520 lines
24 KiB
Text

package views
import taskmodel "xtablo-backend/internal/tasks"
import "xtablo-backend/internal/web/ui"
templ TasksPageContent(vm TasksPageViewModel) {
<div class="min-h-screen" data-current-view={ string(vm.State.View) }>
<div class="px-4 md:px-6 pt-6 md:pt-10 pb-5">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Mes Tâches</h1>
@ui.Button(ui.ButtonProps{
Label: "Nouvelle tâche",
Variant: ui.ButtonVariantDefault,
Size: ui.SizeMD,
Type: "button",
Icon: "plus",
})
</div>
@TasksViewTabs(vm.State)
<div class="flex flex-col md:flex-row md:items-center md:justify-end gap-3">
@ui.Button(ui.ButtonProps{
Label: tasksFilterSummaryLabel(vm.Filters),
Variant: ui.ButtonVariantNeutral,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "button",
Icon: "filter",
Attrs: templ.Attributes{
"data-tasks-filter-trigger": "",
"aria-haspopup": "menu",
"aria-expanded": "false",
},
})
</div>
@TasksFilterMenu(vm)
</div>
<main class="px-4 md:px-6 pb-6">
if !vm.HasTasks {
<div class="rounded-[12px] border border-dashed border-[#D0D5DD] bg-[#F9FAFB] dark:bg-gray-800/60 p-8 text-center text-sm text-[#667085] dark:text-gray-400">
Aucune tâche pour le moment.
</div>
} else if vm.State.View == taskmodel.TaskViewList {
@TasksListLayout(vm.List, vm.State)
} else if vm.State.View == taskmodel.TaskViewRoadmap {
@TasksRoadmapLayout(vm.Roadmap, vm.State)
} else {
@TasksKanbanLayout(vm.Kanban, vm.State)
}
</main>
</div>
}
templ TasksFilterMenu(vm TasksPageViewModel) {
<div class="relative">
<form method="get" action="/tasks" class="hidden absolute right-0 top-2 z-50 w-56 max-h-[28rem] overflow-y-auto overflow-x-hidden rounded-md border bg-white dark:bg-gray-900 p-1 text-gray-900 dark:text-gray-100 shadow-md" data-tasks-filter-menu role="menu" aria-orientation="vertical">
<input type="hidden" name="view" value={ string(vm.State.View) }/>
if vm.State.View == taskmodel.TaskViewRoadmap {
<input type="hidden" name="roadmap_mode" value={ string(vm.State.RoadmapMode) }/>
}
<div class="px-2 py-1.5 text-sm font-semibold">Projet</div>
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<a href={ templ.SafeURL(tasksClearTabloFiltersHref(vm.State)) } class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if tasksFilterGroupAllSelected(vm.Filters.Tablos) {
@TasksIcon("check", "h-4 w-4")
}
</span>
Tous les projets
</a>
for _, option := range vm.Filters.Tablos {
<label class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<input class="sr-only" type="checkbox" name="tablo" value={ option.Value } if option.Selected {
checked
} onchange="this.form.requestSubmit()"/>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if option.Selected {
@TasksIcon("check", "h-4 w-4")
}
</span>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full shrink-0 bg-blue-500"></div>
{ option.Label }
</div>
</label>
}
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<div class="px-2 py-1.5 text-sm font-semibold">Statut</div>
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<a href={ templ.SafeURL(tasksClearStatusFiltersHref(vm.State)) } class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if tasksFilterGroupAllSelected(vm.Filters.Statuses) {
@TasksIcon("check", "h-4 w-4")
}
</span>
Tous
</a>
for _, option := range vm.Filters.Statuses {
<label class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<input class="sr-only" type="checkbox" name="status" value={ option.Value } if option.Selected {
checked
} onchange="this.form.requestSubmit()"/>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if option.Selected {
@TasksIcon("check", "h-4 w-4")
}
</span>
{ option.Label }
</label>
}
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<div class="px-2 py-1.5 text-sm font-semibold">Assigné</div>
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<a href={ templ.SafeURL(tasksClearAssigneeFiltersHref(vm.State)) } class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if tasksFilterGroupAllSelected(vm.Filters.Assignees) {
@TasksIcon("check", "h-4 w-4")
}
</span>
Tous
</a>
for _, option := range vm.Filters.Assignees {
<label class="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-gray-100 dark:hover:bg-gray-800">
<input class="sr-only" type="checkbox" name="assignee" value={ option.Value } if option.Selected {
checked
} onchange="this.form.requestSubmit()"/>
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
if option.Selected {
@TasksIcon("check", "h-4 w-4")
}
</span>
{ option.Label }
</label>
}
<div class="-mx-1 my-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<div class="p-1">
<a href={ templ.SafeURL(stateAction("/tasks", taskmodel.TaskPageState{View: vm.State.View, RoadmapMode: vm.State.RoadmapMode})) } class="block rounded-sm px-2 py-1.5 text-sm font-medium text-[#667085] hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100">Réinitialiser</a>
</div>
</form>
<script>
(function () {
if (window.__tasksFilterMenuInit) {
window.__tasksFilterMenuInit(document);
return;
}
window.__tasksFilterMenuInit = function (scope) {
(scope || document).querySelectorAll("[data-tasks-filter-trigger]").forEach(function (trigger) {
if (trigger.dataset.tasksFilterBound === "true") {
return;
}
trigger.dataset.tasksFilterBound = "true";
var shell = trigger.closest(".px-4, .md\\:px-6") || trigger.parentElement;
var menu = shell ? shell.querySelector("[data-tasks-filter-menu]") : null;
if (!menu) {
return;
}
trigger.addEventListener("click", function (event) {
event.preventDefault();
var open = !menu.classList.contains("hidden");
document.querySelectorAll("[data-tasks-filter-menu]").forEach(function (other) {
other.classList.add("hidden");
});
document.querySelectorAll("[data-tasks-filter-trigger]").forEach(function (otherTrigger) {
otherTrigger.setAttribute("aria-expanded", "false");
});
if (!open) {
menu.classList.remove("hidden");
trigger.setAttribute("aria-expanded", "true");
}
});
});
};
document.addEventListener("click", function (event) {
if (event.target.closest("[data-tasks-filter-menu]") || event.target.closest("[data-tasks-filter-trigger]")) {
return;
}
document.querySelectorAll("[data-tasks-filter-menu]").forEach(function (menu) {
menu.classList.add("hidden");
});
document.querySelectorAll("[data-tasks-filter-trigger]").forEach(function (trigger) {
trigger.setAttribute("aria-expanded", "false");
});
});
document.addEventListener("htmx:afterSwap", function (event) {
window.__tasksFilterMenuInit(event.target);
});
window.__tasksFilterMenuInit(document);
})();
</script>
</div>
}
templ TasksViewTabs(state taskmodel.TaskPageState) {
<div class="flex flex-wrap items-center gap-2 md:gap-6 mb-4 border-b border-[#EAECF0] dark:border-gray-700">
<a href={ templ.SafeURL(taskViewHref(state, taskmodel.TaskViewKanban)) } class={ taskViewTabClass(state, taskmodel.TaskViewKanban) } if state.View == taskmodel.TaskViewKanban {
aria-current="page"
}>
@TasksIcon("kanban", "w-4 h-4")
<span>Tableau</span>
</a>
<a href={ templ.SafeURL(taskViewHref(state, taskmodel.TaskViewList)) } class={ taskViewTabClass(state, taskmodel.TaskViewList) } if state.View == taskmodel.TaskViewList {
aria-current="page"
}>
@TasksIcon("list", "w-4 h-4")
<span>Liste</span>
</a>
<a href={ templ.SafeURL(taskViewHref(state, taskmodel.TaskViewRoadmap)) } class={ taskViewTabClass(state, taskmodel.TaskViewRoadmap) } if state.View == taskmodel.TaskViewRoadmap {
aria-current="page"
}>
@TasksIcon("map", "w-4 h-4")
<span>Roadmap</span>
</a>
<button type="button" disabled class="flex items-center gap-2 pb-3 pt-1 px-2 text-sm font-semibold transition-colors border-b-2 min-h-[44px] text-[#667085] border-transparent hover:text-gray-900 dark:hover:text-gray-100 cursor-not-allowed">
@TasksIcon("calendar", "lucide lucide-calendar w-4 h-4 opacity-40")
<span class="opacity-40">Calendrier</span>
<span class="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 leading-none">Bientôt</span>
</button>
</div>
if state.View == taskmodel.TaskViewRoadmap {
<div class="mb-4 max-w-[220px]">
@ui.Select(taskRoadmapModeSelectProps(state))
</div>
}
}
templ TasksKanbanLayout(view TasksKanbanView, state taskmodel.TaskPageState) {
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4" data-task-kanban>
for _, column := range view.Columns {
<div class="w-full h-fit bg-[#F9FAFB] dark:bg-gray-800/60 rounded-[12px] p-4" data-status-column={ column.ID }>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-2">
@TasksIcon("circle", "lucide lucide-circle w-5 h-5 "+statusIconClass(column.ID))
<h2 class="font-semibold text-gray-800 dark:text-gray-100">{ column.Label }</h2>
<span class="bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-medium px-2 py-0.5 rounded-full">{ len(column.Tasks) }</span>
</div>
@ui.IconButton(ui.IconButtonProps{
Label: "Nouvelle tâche dans " + column.Label,
Icon: "plus",
Variant: ui.IconButtonVariantNeutral,
Tone: ui.IconButtonToneGhost,
Type: "button",
})
</div>
<div class="space-y-3 pr-1 min-h-[80px]">
for _, task := range column.Tasks {
@TaskCard(task, state, false)
}
</div>
</div>
}
</div>
}
templ TasksListLayout(view TasksListView, state taskmodel.TaskPageState) {
<div class="space-y-6" data-task-list>
for _, group := range view.Groups {
<section class="rounded-[12px] bg-[#F9FAFB] dark:bg-gray-800/60 p-4" data-status-group={ group.ID }>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
@TasksIcon("circle", "lucide lucide-circle w-5 h-5 "+statusIconClass(group.ID))
<h2 class="font-semibold text-gray-800 dark:text-gray-100">{ group.Label }</h2>
<span class="bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300 text-xs font-medium px-2 py-0.5 rounded-full">{ len(group.Tasks) }</span>
</div>
@ui.IconButton(ui.IconButtonProps{
Label: "Nouvelle tâche dans " + group.Label,
Icon: "plus",
Variant: ui.IconButtonVariantNeutral,
Tone: ui.IconButtonToneGhost,
Type: "button",
})
</div>
<div class="space-y-3">
for _, task := range group.Tasks {
@TasksListRow(task, state)
}
</div>
</section>
}
</div>
}
templ TasksRoadmapLayout(view TasksRoadmapView, state taskmodel.TaskPageState) {
<div class="space-y-6" data-task-roadmap>
for _, lane := range view.Lanes {
<section class="rounded-[12px] bg-[#F9FAFB] dark:bg-gray-800/60 p-4" data-roadmap-lane={ lane.ID }>
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-4">
<div>
<h2 class="font-semibold text-gray-800 dark:text-gray-100">{ lane.Label }</h2>
<p class="text-xs text-[#667085] dark:text-gray-400">Étape comme lane horizontale, avec bucketisation par date d'échéance.</p>
</div>
@ui.IconButton(ui.IconButtonProps{
Label: "Nouvelle tâche dans " + lane.Label,
Icon: "plus",
Variant: ui.IconButtonVariantNeutral,
Tone: ui.IconButtonToneGhost,
Type: "button",
})
</div>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
for _, bucket := range lane.Buckets {
<div class="rounded-[12px] bg-white dark:bg-gray-900 border border-[#EAECF0] dark:border-gray-700 p-4" data-roadmap-bucket={ bucket.ID }>
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-gray-800 dark:text-gray-100">{ bucket.Label }</h3>
<span class="text-[11px] font-medium px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400">{ len(bucket.Tasks) }</span>
</div>
<div class="space-y-3 min-h-[56px]">
for _, task := range bucket.Tasks {
@TaskCard(task, state, false)
}
</div>
</div>
}
</div>
</section>
}
</div>
}
templ TaskCard(task TaskCardView, state taskmodel.TaskPageState, compact bool) {
<article draggable="true" class={ taskCardClass(compact) } data-task-id={ task.ID }>
<div class="flex items-start justify-between gap-2 mb-2">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 text-sm leading-tight line-clamp-2 flex-1">{ task.Title }</h3>
@ui.IconButton(ui.IconButtonProps{
Label: "Modifier la tâche " + task.Title,
Icon: "pencil",
Variant: ui.IconButtonVariantNeutral,
Tone: ui.IconButtonToneGhost,
Type: "button",
Attrs: templ.Attributes{
"hx-get": taskEditHref(task, state),
"hx-target": "#app-main-content",
"hx-swap": "beforeend",
},
})
@ui.IconButton(ui.IconButtonProps{
Label: taskDeleteAriaLabel(task),
Icon: "trash",
Variant: ui.IconButtonVariantDanger,
Tone: ui.IconButtonToneGhost,
Type: "button",
Attrs: templ.Attributes{
"hx-delete": taskDeleteHref(task, state),
"hx-target": "#app-main-content",
"hx-swap": "outerHTML",
},
})
</div>
if task.DueDate != "" {
<div class={ "flex items-center text-xs mb-3 " + dueDateToneClass(task.DueDateValue) }>
@TasksIcon("calendar", "lucide lucide-calendar w-3.5 h-3.5 mr-1.5")
{ task.DueDate }
</div>
}
<div class="flex items-center mb-3 border-b border-dashed border-[#D0D5DD] dark:border-gray-600 pb-3">
<div class={ "w-5 h-5 rounded-[5px] mr-2 flex items-center justify-center shrink-0 " + etapeBadgeClass(task) }>
@TasksIcon(etapeIconName(task), "w-3 h-3 text-white")
</div>
<span class="text-xs text-gray-600 dark:text-gray-400 truncate">{ etapeLabel(task) }</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3 text-gray-500 dark:text-gray-400">
<div class="flex items-center text-xs">
@TasksIcon("message-square", "lucide lucide-message-square w-3.5 h-3.5 mr-1")
0
</div>
<div class="flex items-center text-xs">
@TasksIcon("paperclip", "lucide lucide-paperclip w-3.5 h-3.5 mr-1")
0
</div>
<div class="flex items-center text-xs">
<span class="font-medium text-gray-700 dark:text-gray-300">Tablo</span>&nbsp;{ task.TabloName }
</div>
</div>
<div class="flex -space-x-2">
@TaskAssigneeAvatar(task)
</div>
</div>
</article>
}
templ TasksListRow(task TaskCardView, state taskmodel.TaskPageState) {
<div class="bg-white dark:bg-gray-900 rounded-[12px] border border-[#EAECF0] dark:border-gray-700 p-4 shadow-sm" data-task-id={ task.ID }>
<div class="grid grid-cols-1 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)] gap-4">
@TaskCard(task, state, true)
<div class="rounded-lg bg-[#F9FAFB] dark:bg-gray-800/60 p-4 text-sm text-gray-700 dark:text-gray-300">
<dl class="space-y-3">
<div>
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500">Tablo</dt>
<dd class="mt-1">{ task.TabloName }</dd>
</div>
<div>
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500">Étape</dt>
<dd class="mt-1">{ etapeLabel(task) }</dd>
</div>
<div>
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500">Assignée</dt>
<dd class="mt-1">{ emptyFallback(task.Assignee, "Non assignée") }</dd>
</div>
<div>
<dt class="text-xs font-semibold uppercase tracking-wide text-gray-500">Statut</dt>
<dd class="mt-1">{ task.StatusLabel }</dd>
</div>
</dl>
</div>
</div>
</div>
}
templ TaskAssigneeAvatar(task TaskCardView) {
if taskHasAssignee(task) {
<div class="w-6 h-6 rounded-full bg-purple-500 border-2 border-white dark:border-gray-800 flex items-center justify-center text-white text-[10px] font-medium">
{ assigneeInitials(task.Assignee) }
</div>
} else {
<div class="w-6 h-6 rounded-full bg-gray-200 dark:bg-gray-600 border-2 border-white dark:border-gray-800 flex items-center justify-center">
@TasksIcon("user", "lucide lucide-user w-3 h-3 text-gray-400 dark:text-gray-300")
</div>
}
}
templ TasksIcon(kind string, className string) {
switch kind {
case "circle":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<circle cx="12" cy="12" r="10"></circle>
</svg>
case "plus":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M5 12h14"></path>
<path d="M12 5v14"></path>
</svg>
case "kanban":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M6 5v11"></path>
<path d="M12 5v6"></path>
<path d="M18 5v14"></path>
</svg>
case "list":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M3 12h.01"></path>
<path d="M3 18h.01"></path>
<path d="M3 6h.01"></path>
<path d="M8 12h13"></path>
<path d="M8 18h13"></path>
<path d="M8 6h13"></path>
</svg>
case "map":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M14.106 5.553a2 2 0 0 0 1.788 0l3.659-1.83A1 1 0 0 1 21 4.619v12.764a1 1 0 0 1-.553.894l-4.553 2.277a2 2 0 0 1-1.788 0l-4.212-2.106a2 2 0 0 0-1.788 0l-3.659 1.83A1 1 0 0 1 3 19.381V6.618a1 1 0 0 1 .553-.894l4.553-2.277a2 2 0 0 1 1.788 0z"></path>
<path d="M15 5.764v15"></path>
<path d="M9 3.236v15"></path>
</svg>
case "calendar":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<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>
case "settings2":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M20 7h-9"></path>
<path d="M14 17H5"></path>
<circle cx="17" cy="17" r="3"></circle>
<circle cx="7" cy="7" r="3"></circle>
</svg>
case "ellipsis-vertical":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<circle cx="12" cy="12" r="1"></circle>
<circle cx="12" cy="5" r="1"></circle>
<circle cx="12" cy="19" r="1"></circle>
</svg>
case "trash2":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M3 6h18"></path>
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
<line x1="10" x2="10" y1="11" y2="17"></line>
<line x1="14" x2="14" y1="11" y2="17"></line>
</svg>
case "message-square":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
case "paperclip":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
case "user":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
case "gem":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M6 3h12l4 6-10 13L2 9Z"></path>
<path d="M11 3 8 9l4 13 4-13-3-6"></path>
<path d="M2 9h20"></path>
</svg>
case "flame":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"></path>
</svg>
case "zap":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"></path>
</svg>
case "check":
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={ className }>
<path d="M20 6 9 17l-5-5"></path>
</svg>
default:
@TasksIcon("circle", className)
}
}