Files
vberp/src/components/Sidebar.svelte
2025-07-08 17:36:04 +14:00

230 lines
6.9 KiB
Svelte

<script lang="ts">
import { goto } from "$app/navigation";
import { onMount } from "svelte";
import { supabase } from "$lib/supabaseClient";
let user: any = null;
onMount(() => {
const userStr = localStorage.getItem("user");
if (userStr) {
user = JSON.parse(userStr);
if (user?.role) {
userRole = user.role;
}
}
activeUrl = window.location.pathname;
// Expand parent menu if current path matches sub menu
for (const item of fullMenu) {
if (item.sub?.some((s) => s.url === activeUrl)) {
openMenus[item.name] = true;
}
}
});
type SubMenuItem = {
name: string;
icon: string;
url: string;
roles?: string[];
};
type MenuItem = {
name: string;
icon: string;
url: string;
sub?: SubMenuItem[];
roles?: string[];
};
let userRole:
| "it"
| "guest"
| "accounting"
| "ga"
| "hr"
| "s&m"
| "office"
| "hm"
| "vm"
| "it";
// Semua menu
const fullMenu: MenuItem[] = [
{
name: "Beranda",
icon: "🏠",
url: "/backoffice",
roles: [
"it",
"guest",
"accounting",
"ga",
"hr",
"s&m",
"office",
"hm",
"vm",
],
},
{
name: "Issues",
icon: "📂",
url: "/backoffice/issue",
roles: ["it", "ga", "office", "hm", "vm", "accounting"],
},
{
name: "Projects",
icon: "📂",
url: "/backoffice/project",
roles: ["it", "ga", "office", "hm", "vm", "accounting"],
},
{
name: "Purchase Orders",
icon: "📦",
url: "/backoffice/purchaseorder",
roles: ["it", "guest", "accounting", "ga", "office", "hm", "vm"],
sub: [
{
name: "PO Item",
icon: "📋",
url: "/backoffice/purchaseorder/poitem",
roles: ["it", "ga", "office", "hm", "vm", "accounting"],
},
],
},
{
name: "Timesheets",
icon: "⏰",
url: "/backoffice/timesheets",
roles: ["it", "ga", "hr", "office", "hm", "vm"],
},
{
name: "Vendors",
icon: "🏢",
url: "/backoffice/vendor",
roles: ["it", "ga", "office", "hm", "vm"],
},
{
name: "Inventory",
icon: "📋",
url: "/backoffice/inventory",
roles: ["it", "ga", "s&m", "office", "hm", "vm"],
},
{
name: "Villa",
icon: "🏡",
url: "/backoffice/villa",
roles: ["it", "ga", "s&m", "office", "hm", "vm"],
},
{
name: "Transport",
icon: "🚗",
url: "/backoffice/transport",
roles: ["it", "ga", "s&m", "office", "hm", "vm"],
},
{
name: "Dining",
icon: "🍽️",
url: "/backoffice/dining",
roles: ["it", "ga", "s&m", "office", "hm", "vm"],
},
{
name: "Users",
icon: "👤",
url: "/backoffice/account",
roles: ["it"],
},
{
name: "Feedback",
icon: "💬",
url: "/backoffice/feedback",
roles: ["it", "ga", "s&m", "office", "hm", "vm"],
},
];
let activeUrl = "/";
let openMenus: Record<string, boolean> = {};
onMount(() => {
activeUrl = window.location.pathname;
});
function handleMenuClick(item: MenuItem) {
if (item.sub) {
goto(item.url);
activeUrl = item.url;
openMenus[item.name] = !openMenus[item.name];
} else {
activeUrl = item.url;
goto(item.url);
}
}
function handleSubClick(sub: SubMenuItem) {
activeUrl = sub.url;
goto(sub.url);
}
// Filter menu dan submenu sesuai role
$: filteredMenu = fullMenu
.filter((item) => !item.roles || item.roles.includes(userRole))
.map((item) => ({
...item,
sub: item.sub
? item.sub.filter(
(sub) => !sub.roles || sub.roles.includes(userRole),
)
: undefined,
}));
</script>
<div class="w-64 h-screen bg-white border-r shadow-sm">
<div class="p-8 border-b">
<h1 class="text-xl font-semibold text-gray-800">Backoffice</h1>
<p class="text-sm text-gray-500">Manage your application</p>
</div>
<ul class="p-2 space-y-1">
{#each filteredMenu as item}
<li>
<a
href={item.url}
class={`flex items-center gap-2 px-4 py-2 rounded transition-colors duration-150
${activeUrl === item.url ? "bg-blue-100 text-blue-600 font-semibold" : "text-gray-700 hover:bg-blue-50"}
${item.sub ? "justify-between" : ""}
`}
on:click|preventDefault={() => handleMenuClick(item)}
>
<span class="flex items-center gap-2">
<span class="text-xl">{item.icon}</span>
<span class="truncate">{item.name}</span>
</span>
{#if item.sub && item.sub.length}
<span>{openMenus[item.name] ? "▲" : "▼"}</span>
{/if}
</a>
{#if item.sub && openMenus[item.name] && item.sub.length}
<ul class="ml-8 mt-1 space-y-1">
{#each item.sub as sub}
<li>
<a
href={sub.url}
class={`flex items-center gap-2 px-3 py-1 rounded transition-colors duration-150
${activeUrl === sub.url ? "bg-blue-50 text-blue-600 font-semibold" : "text-gray-700 hover:bg-blue-50"}
`}
on:click|preventDefault={() =>
handleSubClick(sub)}
>
<span class="text-lg">{sub.icon}</span>
<span class="truncate">{sub.name}</span>
</a>
</li>
{/each}
</ul>
{/if}
</li>
{/each}
</ul>
</div>