Make sidebar sections collapsible with Master Data default collapsed
This commit is contained in:
parent
0f8ec8362b
commit
5df9523d5a
428
src/lib/components/Sidebar.svelte
Normal file
428
src/lib/components/Sidebar.svelte
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user