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