185 lines
6.8 KiB
Svelte
185 lines
6.8 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/stores';
|
|
import { INTERNAL_ROUTES } from '$lib/routes-registry';
|
|
import { auth } from '$lib/stores/auth.js';
|
|
import { navPerms } from '$lib/stores/navPermissions';
|
|
import { onMount } from 'svelte';
|
|
|
|
export let open = false;
|
|
|
|
const NAV_ICONS: Record<string, string> = {
|
|
'/': 'M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z',
|
|
'/tickets':
|
|
'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 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',
|
|
'/users':
|
|
'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z',
|
|
'/tenants':
|
|
'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4',
|
|
'/categories':
|
|
'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10',
|
|
'/systems':
|
|
'M5 12a1 1 0 102 0V6.414l1.293 1.293a1 1 0 001.414-1.414l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L5 6.414V12zm14 0a1 1 0 10-2 0v5.586l-1.293-1.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L19 17.586V12z',
|
|
'/sla': 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
'/audit':
|
|
'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
|
|
'/test-report':
|
|
'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z'
|
|
};
|
|
|
|
onMount(() => {
|
|
navPerms.load();
|
|
});
|
|
|
|
$: navigation = (() => {
|
|
const role = $auth.user?.role;
|
|
const isAdmin = role === 'ADMIN';
|
|
|
|
const items: { name: string; href: string; icon: string }[] = [
|
|
{
|
|
name: 'Dashboard',
|
|
href: '/',
|
|
icon: NAV_ICONS['/']
|
|
}
|
|
];
|
|
|
|
INTERNAL_ROUTES.forEach(route => {
|
|
const hasPermission = isAdmin || $navPerms[route.key] === true;
|
|
if (hasPermission) {
|
|
items.push({
|
|
name: route.label,
|
|
href: route.path,
|
|
icon: NAV_ICONS[route.path] || NAV_ICONS['/tickets']
|
|
});
|
|
}
|
|
});
|
|
|
|
return items;
|
|
})();
|
|
|
|
function isCurrentPage(href: string) {
|
|
return $page.url.pathname === href || ($page.url.pathname.startsWith(href) && href !== '/');
|
|
}
|
|
</script>
|
|
|
|
<!-- Mobile sidebar backdrop -->
|
|
{#if open}
|
|
<div class="fixed inset-0 z-40 lg:hidden">
|
|
<div
|
|
class="fixed inset-0 bg-gray-600 bg-opacity-75"
|
|
role="dialog"
|
|
tabindex="0"
|
|
on:click={() => (open = false)}
|
|
on:keydown={e => e.key === 'Escape' && (open = false)}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Sidebar -->
|
|
<div
|
|
class="fixed inset-y-0 left-0 z-50 w-64 bg-white shadow-lg transform {open
|
|
? 'translate-x-0'
|
|
: '-translate-x-full'} transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0"
|
|
>
|
|
<div class="flex flex-col h-full">
|
|
<!-- Logo -->
|
|
<div class="flex items-center justify-between h-16 px-6 bg-primary-600">
|
|
<div class="flex items-center space-x-2">
|
|
<div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
|
|
<svg
|
|
class="w-5 h-5 text-primary-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192L5.636 18.364M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="text-white">
|
|
<h1 class="text-sm font-semibold">ServiceManager</h1>
|
|
<p class="text-xs text-primary-100">Panel Interno</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
on:click={() => (open = false)}
|
|
class="p-2 rounded-md text-primary-100 hover:text-white hover:bg-primary-500 lg:hidden"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="flex-1 px-4 py-6 space-y-2">
|
|
{#each navigation as item}
|
|
<a
|
|
href={item.href}
|
|
class="flex items-center space-x-3 px-3 py-2 rounded-md text-sm font-medium transition-colors {isCurrentPage(
|
|
item.href
|
|
)
|
|
? 'bg-primary-100 text-primary-700'
|
|
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'}"
|
|
on:click={() => (open = false)}
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
|
</svg>
|
|
<span>{item.name}</span>
|
|
</a>
|
|
{/each}
|
|
</nav>
|
|
|
|
<!-- User info -->
|
|
<div class="px-4 py-4 border-t border-gray-200">
|
|
<a
|
|
href="/profile"
|
|
class="flex items-center space-x-3 rounded-lg p-1 -m-1 hover:bg-gray-100 transition-colors group"
|
|
>
|
|
<div class="w-8 h-8 bg-primary-100 rounded-full flex items-center justify-center shrink-0">
|
|
<span class="text-primary-600 text-sm font-medium">
|
|
{$auth.user?.first_name?.[0]}{$auth.user?.last_name?.[0]}
|
|
</span>
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium text-gray-900 truncate group-hover:text-primary-600">
|
|
{$auth.user?.first_name}
|
|
{$auth.user?.last_name}
|
|
</p>
|
|
<p class="text-xs text-gray-500 truncate">
|
|
{$auth.user?.role === 'ADMIN'
|
|
? 'Administrador'
|
|
: $auth.user?.role === 'SUPPORT_MANAGER'
|
|
? 'Gerente de Soporte'
|
|
: $auth.user?.role === 'AGENT'
|
|
? 'Agente'
|
|
: 'Auditor'}
|
|
</p>
|
|
</div>
|
|
<svg
|
|
class="w-4 h-4 text-gray-400 group-hover:text-primary-500 shrink-0"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Version info -->
|
|
<div class="px-4 py-2 border-t border-gray-100">
|
|
<p class="text-xs text-gray-400 text-center">v1.9.0</p>
|
|
</div>
|
|
</div>
|
|
</div>
|