Make sidebar sections collapsible with Master Data default collapsed

This commit is contained in:
mahdahar 2026-02-10 16:58:45 +07:00
parent 0f8ec8362b
commit 5df9523d5a

View File

@ -0,0 +1,428 @@
<script>
import {
LayoutDashboard,
Database,
FileText,
Printer,
Users,
ClipboardList,
FlaskConical,
CheckCircle2,
Building2,
UserCircle,
LogOut,
List,
MapPin,
Stethoscope,
Briefcase,
Hash,
Globe,
ChevronDown
} from 'lucide-svelte';
import { auth } from '$lib/stores/auth.js';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
let { isOpen = $bindable(false) } = $props();
// Collapsible section states - default collapsed
let masterDataExpanded = $state(false);
let laboratoryExpanded = $state(false);
let administrationExpanded = $state(false);
// Load states from localStorage on mount
$effect(() => {
if (browser) {
const savedStates = localStorage.getItem('sidebar_section_states');
if (savedStates) {
try {
const parsed = JSON.parse(savedStates);
masterDataExpanded = parsed.masterData ?? false;
laboratoryExpanded = parsed.laboratory ?? false;
administrationExpanded = parsed.administration ?? false;
} catch (e) {
// Keep defaults if parsing fails
}
}
}
});
// Save states to localStorage when they change
$effect(() => {
if (browser) {
localStorage.setItem('sidebar_section_states', JSON.stringify({
masterData: masterDataExpanded,
laboratory: laboratoryExpanded,
administration: administrationExpanded
}));
}
});
// Close all sections when sidebar collapses
$effect(() => {
if (!isOpen) {
masterDataExpanded = false;
laboratoryExpanded = false;
administrationExpanded = false;
}
});
function handleLogout() {
auth.logout();
goto('/login');
}
function toggleMasterData() {
masterDataExpanded = !masterDataExpanded;
}
function toggleLaboratory() {
laboratoryExpanded = !laboratoryExpanded;
}
function toggleAdministration() {
administrationExpanded = !administrationExpanded;
}
</script>
<!-- Sidebar -->
<div
class="sidebar-container fixed lg:sticky left-0 top-0 h-screen max-h-screen z-40 bg-base-200 shadow-xl border-r border-base-300 transition-all duration-300 ease-out"
class:sidebar-expanded={isOpen}
class:sidebar-collapsed={!isOpen}
>
<div class="h-screen overflow-y-auto flex flex-col sidebar-content" class:expanded={isOpen} class:collapsed={!isOpen}>
<div class="p-3">
<!-- Navigation Menu -->
<ul class="menu w-full gap-1" class:menu-collapsed={!isOpen}>
{#if isOpen}
<li class="menu-title uppercase font-bold text-xs text-emerald-600/70 mt-2">Main</li>
{/if}
<li>
<a
href="/dashboard"
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700"
class:centered={!isOpen}
title={!isOpen ? 'Dashboard' : ''}
>
<LayoutDashboard class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Dashboard</span>
{/if}
</a>
</li>
<li class="collapsible-section">
<button
onclick={toggleMasterData}
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 w-full text-left justify-between"
class:centered={!isOpen}
title={!isOpen ? 'Master Data' : ''}
>
<div class="flex items-center gap-2">
<Database class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Master Data</span>
{/if}
</div>
{#if isOpen}
<ChevronDown class="w-4 h-4 flex-shrink-0 transition-transform duration-200 {masterDataExpanded ? 'rotate-180' : ''}" />
{/if}
</button>
{#if isOpen && masterDataExpanded}
<ul class="ml-6 mt-1 space-y-1 collapsible-content">
<li>
<a
href="/master-data/valuesets"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<List class="w-4 h-4 flex-shrink-0" />
<span>ValueSets</span>
</a>
</li>
<li>
<a
href="/master-data/locations"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<MapPin class="w-4 h-4 flex-shrink-0" />
<span>Locations</span>
</a>
</li>
<li>
<a
href="/master-data/contacts"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Users class="w-4 h-4 flex-shrink-0" />
<span>Contacts</span>
</a>
</li>
<li>
<a
href="/master-data/specialties"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Stethoscope class="w-4 h-4 flex-shrink-0" />
<span>Specialties</span>
</a>
</li>
<li>
<a
href="/master-data/occupations"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Briefcase class="w-4 h-4 flex-shrink-0" />
<span>Occupations</span>
</a>
</li>
<li>
<a
href="/master-data/counters"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Hash class="w-4 h-4 flex-shrink-0" />
<span>Counters</span>
</a>
</li>
<li>
<a
href="/master-data/geography"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Globe class="w-4 h-4 flex-shrink-0" />
<span>Geography</span>
</a>
</li>
</ul>
{/if}
</li>
<li>
<a
href="/result-entry"
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700"
class:centered={!isOpen}
title={!isOpen ? 'Result Entry' : ''}
>
<FileText class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Result Entry</span>
{/if}
</a>
</li>
<li>
<a
href="/reports"
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700"
class:centered={!isOpen}
title={!isOpen ? 'Reports' : ''}
>
<Printer class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Reports</span>
{/if}
</a>
</li>
<li class="collapsible-section">
<button
onclick={toggleLaboratory}
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 w-full text-left justify-between"
class:centered={!isOpen}
title={!isOpen ? 'Laboratory' : ''}
>
<div class="flex items-center gap-2">
<FlaskConical class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Laboratory</span>
{/if}
</div>
{#if isOpen}
<ChevronDown class="w-4 h-4 flex-shrink-0 transition-transform duration-200 {laboratoryExpanded ? 'rotate-180' : ''}" />
{/if}
</button>
{#if isOpen && laboratoryExpanded}
<ul class="ml-6 mt-1 space-y-1 collapsible-content">
<li>
<a
href="/patients"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Users class="w-4 h-4 flex-shrink-0" />
<span>Patients</span>
</a>
</li>
<li>
<a
href="/orders"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<ClipboardList class="w-4 h-4 flex-shrink-0" />
<span>Orders</span>
</a>
</li>
<li>
<a
href="/specimens"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<FlaskConical class="w-4 h-4 flex-shrink-0" />
<span>Specimens</span>
</a>
</li>
<li>
<a
href="/results"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<CheckCircle2 class="w-4 h-4 flex-shrink-0" />
<span>Results</span>
</a>
</li>
</ul>
{/if}
</li>
<li class="collapsible-section">
<button
onclick={toggleAdministration}
class="menu-item text-gray-700 hover:bg-emerald-50 hover:text-emerald-700 w-full text-left justify-between"
class:centered={!isOpen}
title={!isOpen ? 'Administration' : ''}
>
<div class="flex items-center gap-2">
<Building2 class="w-5 h-5 text-emerald-600 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Administration</span>
{/if}
</div>
{#if isOpen}
<ChevronDown class="w-4 h-4 flex-shrink-0 transition-transform duration-200 {administrationExpanded ? 'rotate-180' : ''}" />
{/if}
</button>
{#if isOpen && administrationExpanded}
<ul class="ml-6 mt-1 space-y-1 collapsible-content">
<li>
<a
href="/organization"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<Building2 class="w-4 h-4 flex-shrink-0" />
<span>Organization</span>
</a>
</li>
<li>
<a
href="/users"
class="menu-item py-1 px-2 text-sm text-gray-600 hover:bg-emerald-50 hover:text-emerald-700"
>
<UserCircle class="w-4 h-4 flex-shrink-0" />
<span>Users</span>
</a>
</li>
</ul>
{/if}
</li>
<!-- Logout -->
{#if isOpen}
<li class="mt-2 pt-2 border-t border-gray-200"></li>
{/if}
<li>
<button
onclick={handleLogout}
class="menu-item text-red-500 hover:bg-red-50 w-full text-left"
class:centered={!isOpen}
title={!isOpen ? 'Logout' : ''}
>
<LogOut class="w-5 h-5 flex-shrink-0" />
{#if isOpen}
<span class="menu-text whitespace-nowrap overflow-hidden">Logout</span>
{/if}
</button>
</li>
</ul>
</div>
</div>
</div>
<style>
.sidebar-container {
overflow: visible;
transition: width 300ms ease-out;
}
.sidebar-expanded {
width: 14rem;
}
.sidebar-collapsed {
width: 4rem;
}
.sidebar-content {
transition: width 300ms ease-out;
}
.sidebar-content.expanded {
width: 14rem;
}
.sidebar-content.collapsed {
width: 4rem;
}
.menu-item {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
transition: all 0.2s;
gap: 0.5rem;
}
.menu-item.centered {
justify-content: center;
padding: 0.5rem 0;
width: 2.5rem;
margin-left: auto;
margin-right: auto;
}
.menu-text {
flex: 1;
transition: opacity 300ms ease-out;
}
/* Collapsed menu styling */
.menu-collapsed :global(li > a),
.menu-collapsed :global(li > button) {
justify-content: center !important;
padding-left: 0 !important;
padding-right: 0 !important;
}
/* Collapsible section styles */
.collapsible-section button {
display: flex;
align-items: center;
}
.collapsible-section button.centered {
justify-content: center;
}
.collapsible-content {
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>