| 1 |
:-( |
defmodule DaProductAppWeb.SidebarComponent do |
| 2 |
|
@moduledoc """ |
| 3 |
|
Sidebar LiveComponent for navigation menu |
| 4 |
|
""" |
| 5 |
:-( |
use DaProductAppWeb, :live_component |
| 6 |
|
|
| 7 |
|
@impl true |
| 8 |
|
def render(assigns) do |
| 9 |
:-( |
~H""" |
| 10 |
|
<aside class="min-h-full w-64 bg-base-200 text-base-content"> |
| 11 |
|
<!-- Logo Section --> |
| 12 |
|
<div class="flex h-16 items-center justify-center border-b border-base-300 bg-base-100"> |
| 13 |
|
<div class="flex items-center space-x-2"> |
| 14 |
|
<div class="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> |
| 15 |
|
<svg class="w-5 h-5 text-primary-content" fill="currentColor" viewBox="0 0 20 20"> |
| 16 |
|
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4zM18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z"></path> |
| 17 |
|
</svg> |
| 18 |
|
</div> |
| 19 |
|
<span class="text-lg font-bold text-base-content">Mercury UPI</span> |
| 20 |
|
</div> |
| 21 |
|
</div> |
| 22 |
|
|
| 23 |
|
<!-- Navigation Menu --> |
| 24 |
|
<div class="p-4"> |
| 25 |
|
<nav class="space-y-2"> |
| 26 |
:-( |
<%= for menu_item <- @menu_items do %> |
| 27 |
:-( |
<.nav_link |
| 28 |
:-( |
href={menu_item.path} |
| 29 |
:-( |
active={@current_page == menu_item.key} |
| 30 |
:-( |
icon={menu_item.icon} |
| 31 |
|
> |
| 32 |
:-( |
<%= menu_item.label %> |
| 33 |
|
</.nav_link> |
| 34 |
|
<% end %> |
| 35 |
|
</nav> |
| 36 |
|
</div> |
| 37 |
|
|
| 38 |
|
<!-- User Profile Section at Bottom --> |
| 39 |
|
<div class="absolute bottom-0 left-0 right-0 p-4 border-t border-base-300 bg-base-100"> |
| 40 |
|
<div class="flex items-center space-x-3"> |
| 41 |
|
<div class="avatar"> |
| 42 |
|
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center"> |
| 43 |
|
<span class="text-sm font-medium text-primary"> |
| 44 |
:-( |
<%= String.first(@current_user.name || "U") %> |
| 45 |
|
</span> |
| 46 |
|
</div> |
| 47 |
|
</div> |
| 48 |
|
<div class="flex-1 min-w-0"> |
| 49 |
|
<p class="text-sm font-medium text-base-content truncate"> |
| 50 |
:-( |
<%= @current_user.name || "User" %> |
| 51 |
|
</p> |
| 52 |
|
<p class="text-xs text-base-content/70 truncate"> |
| 53 |
:-( |
<%= @current_user.role.name || "user" %> |
| 54 |
|
</p> |
| 55 |
|
</div> |
| 56 |
|
<div class="dropdown dropdown-top dropdown-end"> |
| 57 |
|
<label tabindex="0" class="btn btn-ghost btn-xs"> |
| 58 |
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 59 |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path> |
| 60 |
|
</svg> |
| 61 |
|
</label> |
| 62 |
|
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-32"> |
| 63 |
|
<li><a href="#" class="text-xs">Profile</a></li> |
| 64 |
|
<li><a href="#" class="text-xs">Settings</a></li> |
| 65 |
:-( |
<li><a href={~p"/logout"} data-method="delete" class="text-xs">Logout</a></li> |
| 66 |
|
</ul> |
| 67 |
|
</div> |
| 68 |
|
</div> |
| 69 |
|
</div> |
| 70 |
|
</aside> |
| 71 |
|
""" |
| 72 |
|
end |
| 73 |
|
|
| 74 |
|
@impl true |
| 75 |
:-( |
def update(assigns, socket) do |
| 76 |
|
# Get menu items based on current user's role |
| 77 |
:-( |
menu_items = DaProductAppWeb.MenuManager.get_menu_items(assigns.current_user) |
| 78 |
|
|
| 79 |
:-( |
socket = |
| 80 |
|
socket |
| 81 |
|
|> assign(assigns) |
| 82 |
|
|> assign(:menu_items, menu_items) |
| 83 |
|
|
| 84 |
|
{:ok, socket} |
| 85 |
|
end |
| 86 |
|
|
| 87 |
|
# Private function components |
| 88 |
|
defp nav_link(assigns) do |
| 89 |
:-( |
assigns = assign_new(assigns, :mobile, fn -> false end) |
| 90 |
|
|
| 91 |
:-( |
~H""" |
| 92 |
:-( |
<a href={@href} class={[ |
| 93 |
|
"flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors", |
| 94 |
:-( |
if(@active, |
| 95 |
|
do: "bg-primary text-primary-content", |
| 96 |
|
else: "text-base-content/70 hover:text-base-content hover:bg-base-300") |
| 97 |
|
]}> |
| 98 |
:-( |
<.hero_icon name={@icon} class={[ |
| 99 |
|
"w-5 h-5 flex-shrink-0", |
| 100 |
:-( |
if(@active, |
| 101 |
|
do: "text-primary-content", |
| 102 |
|
else: "text-base-content/60") |
| 103 |
|
]} /> |
| 104 |
:-( |
<%= render_slot(@inner_block) %> |
| 105 |
|
</a> |
| 106 |
|
""" |
| 107 |
|
end |
| 108 |
|
|
| 109 |
|
defp hero_icon(%{name: "home"} = assigns) do |
| 110 |
:-( |
~H""" |
| 111 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 112 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> |
| 113 |
|
</svg> |
| 114 |
|
""" |
| 115 |
|
end |
| 116 |
|
|
| 117 |
|
defp hero_icon(%{name: "building-office"} = assigns) do |
| 118 |
:-( |
~H""" |
| 119 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 120 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 21h16.5M4.5 3h15l-.75 18h-13.5L4.5 3ZM12.75 8.25h.008v.008h-.008V8.25Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008V14.25Zm-3.75-6h.008v.008H9V8.25Zm0 3h.008v.008H9v-.008Zm0 3h.008v.008H9V14.25Zm7.5-6h.008v.008h-.008V8.25Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008V14.25Z" /> |
| 121 |
|
</svg> |
| 122 |
|
""" |
| 123 |
|
end |
| 124 |
|
|
| 125 |
|
defp hero_icon(%{name: "users"} = assigns) do |
| 126 |
:-( |
~H""" |
| 127 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 128 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" /> |
| 129 |
|
</svg> |
| 130 |
|
""" |
| 131 |
|
end |
| 132 |
|
|
| 133 |
|
defp hero_icon(%{name: "credit-card"} = assigns) do |
| 134 |
:-( |
~H""" |
| 135 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 136 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z" /> |
| 137 |
|
</svg> |
| 138 |
|
""" |
| 139 |
|
end |
| 140 |
|
|
| 141 |
|
defp hero_icon(%{name: "chart-bar"} = assigns) do |
| 142 |
:-( |
~H""" |
| 143 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 144 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" /> |
| 145 |
|
</svg> |
| 146 |
|
""" |
| 147 |
|
end |
| 148 |
|
|
| 149 |
|
defp hero_icon(%{name: "cog-6-tooth"} = assigns) do |
| 150 |
:-( |
~H""" |
| 151 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 152 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a6.759 6.759 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" /> |
| 153 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /> |
| 154 |
|
</svg> |
| 155 |
|
""" |
| 156 |
|
end |
| 157 |
|
|
| 158 |
|
defp hero_icon(%{name: "document-check"} = assigns) do |
| 159 |
:-( |
~H""" |
| 160 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 161 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| 162 |
|
</svg> |
| 163 |
|
""" |
| 164 |
|
end |
| 165 |
|
|
| 166 |
|
# Add more icons as needed |
| 167 |
|
defp hero_icon(assigns) do |
| 168 |
:-( |
~H""" |
| 169 |
:-( |
<svg class={@class} fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> |
| 170 |
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3-12h.75M15 6h.75M18 6h.75" /> |
| 171 |
|
</svg> |
| 172 |
|
""" |
| 173 |
|
end |
| 174 |
|
|
| 175 |
|
# JavaScript helpers |
| 176 |
|
defp hide_mobile_sidebar do |
| 177 |
|
%JS{} |
| 178 |
|
|> JS.hide(to: "#mobile-sidebar") |
| 179 |
|
end |
| 180 |
|
end |