Finalize task-020 reports navigation and stability fixes
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import { page } from "$app/stores";
|
||||
import { t, _ } from "$lib/i18n";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
|
||||
let { maxVisible = 3 } = $props();
|
||||
|
||||
@@ -82,30 +83,103 @@
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function getCrumbMeta(item) {
|
||||
if (item.path === "/") {
|
||||
return {
|
||||
icon: "home",
|
||||
tone: "from-sky-100 to-cyan-100 text-sky-700 ring-sky-200",
|
||||
};
|
||||
}
|
||||
|
||||
const segment = item.path.split("/").filter(Boolean).at(-1) || "";
|
||||
|
||||
const map = {
|
||||
dashboards: {
|
||||
icon: "dashboard",
|
||||
tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
|
||||
},
|
||||
datasets: {
|
||||
icon: "database",
|
||||
tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
|
||||
},
|
||||
storage: {
|
||||
icon: "storage",
|
||||
tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
|
||||
},
|
||||
reports: {
|
||||
icon: "reports",
|
||||
tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
|
||||
},
|
||||
admin: {
|
||||
icon: "admin",
|
||||
tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
|
||||
},
|
||||
settings: {
|
||||
icon: "settings",
|
||||
tone: "from-slate-100 to-slate-200 text-slate-700 ring-slate-200",
|
||||
},
|
||||
git: {
|
||||
icon: "storage",
|
||||
tone: "from-orange-100 to-orange-200 text-orange-700 ring-orange-200",
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
map[segment] || {
|
||||
icon: "layers",
|
||||
tone: "from-slate-100 to-slate-200 text-slate-600 ring-slate-200",
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class="flex items-center space-x-2 text-sm text-gray-600"
|
||||
class="mx-4 md:mx-6"
|
||||
aria-label="Breadcrumb navigation"
|
||||
>
|
||||
{#each breadcrumbItems as item, index}
|
||||
<div class="flex items-center">
|
||||
{#if item.isEllipsis}
|
||||
<span class="text-gray-400">...</span>
|
||||
{:else if item.isLast}
|
||||
<span class="text-gray-900 font-medium">{item.label}</span>
|
||||
{:else}
|
||||
<a
|
||||
href={item.path}
|
||||
class="hover:text-primary hover:underline cursor-pointer transition-colors"
|
||||
>{item.label}</a
|
||||
>
|
||||
<div class="inline-flex max-w-full items-center gap-1.5 rounded-xl border border-slate-200/80 bg-white/85 px-2 py-1.5 shadow-sm backdrop-blur">
|
||||
{#each breadcrumbItems as item, index}
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
{#if item.isEllipsis}
|
||||
<span class="px-2 py-1 text-xs font-semibold tracking-wide text-slate-400"
|
||||
>...</span
|
||||
>
|
||||
{:else}
|
||||
{@const meta = getCrumbMeta(item)}
|
||||
{#if item.isLast}
|
||||
<span
|
||||
class="inline-flex min-w-0 items-center gap-2 rounded-lg bg-slate-900 px-2.5 py-1.5 text-sm font-medium text-white"
|
||||
>
|
||||
<span
|
||||
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-white/10"
|
||||
>
|
||||
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
|
||||
</span>
|
||||
<span class="truncate">{item.label}</span>
|
||||
</span>
|
||||
{:else}
|
||||
<a
|
||||
href={item.path}
|
||||
class="inline-flex min-w-0 items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm text-slate-700 ring-1 ring-transparent transition-all hover:bg-slate-50 hover:ring-slate-200"
|
||||
>
|
||||
<span
|
||||
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-gradient-to-br ring-1 {meta.tone}"
|
||||
>
|
||||
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
|
||||
</span>
|
||||
<span class="truncate">{item.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{#if index < breadcrumbItems.length - 1}
|
||||
<span class="text-slate-300">
|
||||
<Icon name="chevronRight" size={14} strokeWidth={2.1} />
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if index < breadcrumbItems.length - 1}
|
||||
<span class="text-gray-400">/</span>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- [/DEF:Breadcrumbs:Component] -->
|
||||
|
||||
@@ -24,59 +24,68 @@
|
||||
} from "$lib/stores/sidebar.js";
|
||||
import { t } from "$lib/i18n";
|
||||
import { browser } from "$app/environment";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
|
||||
// Sidebar categories with sub-items matching Superset-style navigation
|
||||
let categories = [
|
||||
{
|
||||
id: "dashboards",
|
||||
label: $t.nav?.dashboards || "DASHBOARDS",
|
||||
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z",
|
||||
path: "/dashboards",
|
||||
subItems: [
|
||||
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "datasets",
|
||||
label: $t.nav?.datasets || "DATASETS",
|
||||
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z",
|
||||
path: "/datasets",
|
||||
subItems: [
|
||||
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "storage",
|
||||
label: $t.nav?.storage || "STORAGE",
|
||||
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z",
|
||||
path: "/storage",
|
||||
subItems: [
|
||||
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
|
||||
{
|
||||
label: $t.nav?.repositories || "Repositories",
|
||||
path: "/storage/repos",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "reports",
|
||||
label: $t.nav?.reports || "REPORTS",
|
||||
icon: "M4 5h16M4 12h16M4 19h10",
|
||||
path: "/reports",
|
||||
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
label: $t.nav?.admin || "ADMIN",
|
||||
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
|
||||
path: "/admin",
|
||||
subItems: [
|
||||
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
|
||||
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
|
||||
{ label: $t.nav?.settings || "Settings", path: "/settings" },
|
||||
],
|
||||
},
|
||||
];
|
||||
function buildCategories() {
|
||||
return [
|
||||
{
|
||||
id: "dashboards",
|
||||
label: $t.nav?.dashboards || "DASHBOARDS",
|
||||
icon: "dashboard",
|
||||
tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
|
||||
path: "/dashboards",
|
||||
subItems: [
|
||||
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "datasets",
|
||||
label: $t.nav?.datasets || "DATASETS",
|
||||
icon: "database",
|
||||
tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
|
||||
path: "/datasets",
|
||||
subItems: [
|
||||
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "storage",
|
||||
label: $t.nav?.storage || "STORAGE",
|
||||
icon: "storage",
|
||||
tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
|
||||
path: "/storage",
|
||||
subItems: [
|
||||
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
|
||||
{
|
||||
label: $t.nav?.repositories || "Repositories",
|
||||
path: "/storage/repos",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "reports",
|
||||
label: $t.nav?.reports || "REPORTS",
|
||||
icon: "reports",
|
||||
tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
|
||||
path: "/reports",
|
||||
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
label: $t.nav?.admin || "ADMIN",
|
||||
icon: "admin",
|
||||
tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
|
||||
path: "/admin",
|
||||
subItems: [
|
||||
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
|
||||
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
|
||||
{ label: $t.nav?.settings || "Settings", path: "/settings" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let categories = buildCategories();
|
||||
|
||||
let isExpanded = true;
|
||||
let activeCategory = "dashboards";
|
||||
@@ -93,57 +102,7 @@
|
||||
}
|
||||
|
||||
// Reactive categories to update translations
|
||||
$: categories = [
|
||||
{
|
||||
id: "dashboards",
|
||||
label: $t.nav?.dashboards || "DASHBOARDS",
|
||||
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z",
|
||||
path: "/dashboards",
|
||||
subItems: [
|
||||
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "datasets",
|
||||
label: $t.nav?.datasets || "DATASETS",
|
||||
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z",
|
||||
path: "/datasets",
|
||||
subItems: [
|
||||
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "storage",
|
||||
label: $t.nav?.storage || "STORAGE",
|
||||
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z",
|
||||
path: "/storage",
|
||||
subItems: [
|
||||
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
|
||||
{
|
||||
label: $t.nav?.repositories || "Repositories",
|
||||
path: "/storage/repos",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "reports",
|
||||
label: $t.nav?.reports || "REPORTS",
|
||||
icon: "M4 5h16M4 12h16M4 19h10",
|
||||
path: "/reports",
|
||||
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
label: $t.nav?.admin || "ADMIN",
|
||||
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
|
||||
path: "/admin",
|
||||
subItems: [
|
||||
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
|
||||
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
|
||||
{ label: $t.nav?.settings || "Settings", path: "/settings" },
|
||||
],
|
||||
},
|
||||
];
|
||||
$: categories = buildCategories();
|
||||
|
||||
// Update active item when page changes
|
||||
$: if ($page && $page.url.pathname !== activeItem) {
|
||||
@@ -238,7 +197,12 @@
|
||||
: 'justify-center'}"
|
||||
>
|
||||
{#if isExpanded}
|
||||
<span class="font-semibold text-gray-800">Menu</span>
|
||||
<span class="font-semibold text-gray-800 flex items-center gap-2">
|
||||
<span class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-gradient-to-br from-slate-100 to-slate-200 text-slate-700 ring-1 ring-slate-200">
|
||||
<Icon name="layers" size={14} />
|
||||
</span>
|
||||
Menu
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-xs text-gray-500">M</span>
|
||||
{/if}
|
||||
@@ -264,16 +228,9 @@
|
||||
aria-expanded={expandedCategories.has(category.id)}
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<svg
|
||||
class="w-5 h-5 shrink-0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d={category.icon} />
|
||||
</svg>
|
||||
<span class="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br ring-1 transition-all {category.tone}">
|
||||
<Icon name={category.icon} size={16} strokeWidth={2} />
|
||||
</span>
|
||||
{#if isExpanded}
|
||||
<span class="ml-3 text-sm font-medium truncate"
|
||||
>{category.label}</span
|
||||
@@ -281,22 +238,15 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if isExpanded}
|
||||
<svg
|
||||
<Icon
|
||||
name="chevronDown"
|
||||
size={16}
|
||||
class="text-gray-400 transition-transform duration-200 {expandedCategories.has(
|
||||
category.id,
|
||||
)
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -332,18 +282,9 @@
|
||||
class="flex items-center justify-center w-full px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
on:click={handleToggleClick}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
class="mr-2"
|
||||
>
|
||||
<path d="M15 18l-6-6 6-6" />
|
||||
</svg>
|
||||
<span class="mr-2 inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-100 text-slate-600">
|
||||
<Icon name="chevronLeft" size={14} />
|
||||
</span>
|
||||
Collapse
|
||||
</button>
|
||||
</div>
|
||||
@@ -354,17 +295,7 @@
|
||||
on:click={handleToggleClick}
|
||||
aria-label="Expand sidebar"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M9 18l6-6-6-6" />
|
||||
</svg>
|
||||
<Icon name="chevronRight" size={16} />
|
||||
<span class="ml-2">Expand</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import PasswordPrompt from "../../../components/PasswordPrompt.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import { api } from "$lib/api.js";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
|
||||
let isOpen = false;
|
||||
let activeTaskId = null;
|
||||
@@ -209,9 +210,7 @@
|
||||
<div class="flex items-center gap-2.5">
|
||||
{#if !activeTaskId && recentTasks.length > 0}
|
||||
<span class="flex items-center justify-center p-1.5 mr-1 text-cyan-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>
|
||||
</svg>
|
||||
<Icon name="list" size={16} strokeWidth={2} />
|
||||
</span>
|
||||
{:else if activeTaskId}
|
||||
<button
|
||||
@@ -219,17 +218,7 @@
|
||||
on:click={goBackToList}
|
||||
aria-label="Back to task list"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M19 12H5M12 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<Icon name="back" size={16} strokeWidth={2} />
|
||||
</button>
|
||||
{/if}
|
||||
<h2 class="text-sm font-semibold text-slate-100 tracking-tight">
|
||||
@@ -256,17 +245,7 @@
|
||||
on:click={handleClose}
|
||||
aria-label="Close drawer"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
<Icon name="close" size={18} strokeWidth={2} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -301,18 +280,12 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center justify-center h-full text-slate-500">
|
||||
<svg
|
||||
class="w-12 h-12 mb-3 text-slate-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<Icon
|
||||
name="clipboard"
|
||||
size={48}
|
||||
strokeWidth={1.6}
|
||||
className="mb-3 text-slate-700"
|
||||
/>
|
||||
<p>{$t.tasks?.select_task || 'No recent tasks'}</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -330,4 +303,3 @@
|
||||
{/if}
|
||||
|
||||
<!-- [/DEF:TaskDrawer:Component] -->
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js";
|
||||
import { t } from "$lib/i18n";
|
||||
import { auth } from "$lib/auth/store.js";
|
||||
import Icon from "$lib/ui/Icon.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -99,19 +100,7 @@
|
||||
on:click={handleHamburgerClick}
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
<Icon name="menu" size={22} />
|
||||
</button>
|
||||
|
||||
<!-- Logo/Brand -->
|
||||
@@ -119,14 +108,9 @@
|
||||
href="/"
|
||||
class="flex items-center text-xl font-bold text-gray-800 hover:text-primary transition-colors"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 mr-2 text-primary"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||
</svg>
|
||||
<span class="mr-2 inline-flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-sky-500 via-cyan-500 to-indigo-600 text-white shadow-sm">
|
||||
<Icon name="layers" size={18} strokeWidth={2.1} />
|
||||
</span>
|
||||
<span>Superset Tools</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -147,7 +131,7 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Activity Indicator -->
|
||||
<div
|
||||
class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||
class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors text-slate-600"
|
||||
on:click={handleActivityClick}
|
||||
on:keydown={(e) =>
|
||||
(e.key === "Enter" || e.key === " ") && handleActivityClick()}
|
||||
@@ -155,18 +139,7 @@
|
||||
tabindex="0"
|
||||
aria-label="Activity"
|
||||
>
|
||||
<svg
|
||||
class="w-6 h-6 text-gray-600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
|
||||
/>
|
||||
</svg>
|
||||
<Icon name="activity" size={22} />
|
||||
{#if activeCount > 0}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 bg-destructive text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center"
|
||||
|
||||
66
frontend/src/lib/ui/Icon.svelte
Normal file
66
frontend/src/lib/ui/Icon.svelte
Normal file
@@ -0,0 +1,66 @@
|
||||
<script>
|
||||
export let name = "circle";
|
||||
export let size = 20;
|
||||
export let className = "";
|
||||
export let strokeWidth = 1.9;
|
||||
|
||||
const iconPaths = {
|
||||
home: ["M3 11l9-7 9 7", "M5 10v9h14v-9", "M10 19v-5h4v5"],
|
||||
dashboard: ["M4 4h16v16H4z", "M4 10h16", "M10 4v16"],
|
||||
database: [
|
||||
"M4 7c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z",
|
||||
"M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3",
|
||||
"M4 17c0 1.7 3.6 3 8 3s8-1.3 8-3",
|
||||
"M4 7v10",
|
||||
"M20 7v10",
|
||||
],
|
||||
storage: [
|
||||
"M3 8l9-4 9 4-9 4-9-4z",
|
||||
"M3 13l9 4 9-4",
|
||||
"M3 17l9 4 9-4",
|
||||
],
|
||||
reports: ["M5 5h14v14H5z", "M8 9h8", "M8 13h8", "M8 17h5"],
|
||||
admin: ["M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z", "M9 12l2 2 4-4"],
|
||||
chevronDown: ["M6 9l6 6 6-6"],
|
||||
chevronLeft: ["M15 6l-6 6 6 6"],
|
||||
chevronRight: ["M9 6l6 6-6 6"],
|
||||
menu: ["M4 7h16", "M4 12h16", "M4 17h16"],
|
||||
activity: [
|
||||
"M12 3v3",
|
||||
"M12 18v3",
|
||||
"M4.9 4.9l2.1 2.1",
|
||||
"M17 17l2.1 2.1",
|
||||
"M3 12h3",
|
||||
"M18 12h3",
|
||||
"M4.9 19.1L7 17",
|
||||
"M17 7l2.1-2.1",
|
||||
"M12 15a3 3 0 100-6 3 3 0 000 6z",
|
||||
],
|
||||
layers: ["M12 4l8 4-8 4-8-4 8-4z", "M4 12l8 4 8-4", "M4 16l8 4 8-4"],
|
||||
back: ["M19 12H5", "M12 5l-7 7 7 7"],
|
||||
close: ["M18 6L6 18", "M6 6l12 12"],
|
||||
list: ["M8 7h12", "M8 12h12", "M8 17h12", "M4 7h.01", "M4 12h.01", "M4 17h.01"],
|
||||
clipboard: ["M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2", "M9 5a2 2 0 002 2h2a2 2 0 002-2", "M9 5a2 2 0 012-2h2a2 2 0 012 2"],
|
||||
settings: ["M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z", "M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z"],
|
||||
};
|
||||
|
||||
$: paths = iconPaths[name] || iconPaths.dashboard;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width={strokeWidth}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{#each paths as d}
|
||||
<path d={d} />
|
||||
{/each}
|
||||
</svg>
|
||||
Reference in New Issue
Block a user