source: frontend/src/pages/Dashboard/components/DashboardSidebar.jsx@ 194c016

Last change on this file since 194c016 was a8381b1, checked in by Andrej <asumanovski@…>, 2 months ago

Control center page developed

  • Property mode set to 100644
File size: 6.4 KB
Line 
1import React, { useEffect, useMemo, useState } from "react";
2import { NavLink, useLocation } from "react-router-dom";
3
4import {
5 Button,
6 Sidebar,
7 SidebarContent,
8 SidebarHeader,
9 SidebarMenu,
10 SidebarMenuButton,
11 SidebarMenuItem,
12} from "@relume_io/relume-ui";
13
14import {
15 MdTune,
16 MdFitnessCenter,
17 MdMonitorWeight,
18 MdAccountBalanceWallet,
19 MdTrendingUp,
20 MdChecklist,
21 MdAdd,
22 MdBook,
23 MdFlag,
24 MdStar,
25 MdBolt,
26 MdNoteAlt,
27 MdAutoGraph,
28} from "react-icons/md";
29
30import logo from "../../../assets/logo.png";
31
32import AddCustomCategoryModal from "./AddCustomCategoryModal.jsx";
33import {
34 createCustomTrackingCategory,
35 getCustomTrackingCategories,
36} from "../../../api/discipline.js";
37
38const navItems = [
39 { title: "Control Center", to: "/dashboard/control-center", icon: MdTune },
40 { title: "Training", to: "/dashboard/training", icon: MdFitnessCenter },
41 { title: "Weight", to: "/dashboard/weight", icon: MdMonitorWeight },
42 { title: "Finance", to: "/dashboard/finance", icon: MdAccountBalanceWallet },
43 { title: "Investing", to: "/dashboard/investing", icon: MdTrendingUp },
44 { title: "Discipline", to: "/dashboard/discipline", icon: MdChecklist },
45];
46
47const CUSTOM_ICONS = [MdBook, MdFlag, MdStar, MdBolt, MdNoteAlt, MdAutoGraph];
48
49function stableIconForId(id) {
50 const n = Number(id);
51 if (!Number.isFinite(n)) return CUSTOM_ICONS[0];
52 return CUSTOM_ICONS[Math.abs(n) % CUSTOM_ICONS.length];
53}
54
55const DashboardSidebar = () => {
56 const location = useLocation();
57 const [customOpen, setCustomOpen] = useState(false);
58 const [customCategories, setCustomCategories] = useState([]);
59
60 useEffect(() => {
61 let mounted = true;
62 (async () => {
63 try {
64 const res = await getCustomTrackingCategories();
65 const items = res?.items ?? [];
66 if (mounted) setCustomCategories(items);
67 } catch {
68 // If the user isn't authenticated yet or the endpoint fails,
69 // keep the sidebar usable (no custom categories).
70 if (mounted) setCustomCategories([]);
71 }
72 })();
73 return () => {
74 mounted = false;
75 };
76 }, []);
77
78 const customNavItems = useMemo(() => {
79 return (customCategories ?? []).map((c) => {
80 const Icon = stableIconForId(c.customTrackingId);
81 return {
82 title: c.name,
83 to: `/dashboard/custom/${c.customTrackingId}`,
84 icon: Icon,
85 };
86 });
87 }, [customCategories]);
88
89 return (
90 <Sidebar
91 className="py-6 border-r border-white/10 bg-base-100/60 backdrop-blur-xl"
92 closeButtonClassName="fixed top-4 right-4"
93 collapsible="none"
94 >
95 <SidebarHeader className="hidden lg:block">
96 <NavLink to="/" className="flex items-center gap-3 px-2">
97 <img src={logo} alt="Trekr" className="h-10 w-auto rounded-2xl" />
98 </NavLink>
99 </SidebarHeader>
100
101 <SidebarContent className="mt-6">
102 <SidebarMenu>
103 {navItems.map((item) => (
104 <SidebarMenuItem key={item.title}>
105 {(() => {
106 const isActive =
107 location.pathname === item.to ||
108 location.pathname.startsWith(`${item.to}/`);
109
110 return (
111 <SidebarMenuButton
112 asChild
113 isActive={isActive}
114 className={
115 isActive
116 ? "bg-green-400! text-black! hover:bg-green-400! active:bg-green-400!"
117 : "hover:bg-white/10!"
118 }
119 >
120 <NavLink
121 to={item.to}
122 className="flex w-full items-center gap-3"
123 >
124 <item.icon
125 className={
126 isActive
127 ? "size-6 shrink-0 text-black"
128 : "size-6 shrink-0"
129 }
130 />
131 <span className={isActive ? "text-black" : ""}>
132 {item.title}
133 </span>
134 </NavLink>
135 </SidebarMenuButton>
136 );
137 })()}
138 </SidebarMenuItem>
139 ))}
140
141 {customNavItems.length > 0 ? (
142 <div className="my-4 border-t border-white/10" />
143 ) : null}
144
145 {customNavItems.map((item) => (
146 <SidebarMenuItem key={`custom-${item.to}`}>
147 {(() => {
148 const isActive =
149 location.pathname === item.to ||
150 location.pathname.startsWith(`${item.to}/`);
151
152 return (
153 <SidebarMenuButton
154 asChild
155 isActive={isActive}
156 className={
157 isActive
158 ? "bg-green-400! text-black! hover:bg-green-400! active:bg-green-400!"
159 : "hover:bg-white/10!"
160 }
161 >
162 <NavLink
163 to={item.to}
164 className="flex w-full items-center gap-3"
165 >
166 <item.icon
167 className={
168 isActive
169 ? "size-6 shrink-0 text-black"
170 : "size-6 shrink-0"
171 }
172 />
173 <span className={isActive ? "text-black" : ""}>
174 {item.title}
175 </span>
176 </NavLink>
177 </SidebarMenuButton>
178 );
179 })()}
180 </SidebarMenuItem>
181 ))}
182 </SidebarMenu>
183
184 <div className="mt-6 px-2">
185 <Button
186 variant="secondary"
187 className="w-full justify-start gap-2 bg-green-400! text-black! hover:bg-green-500!"
188 type="button"
189 onClick={() => setCustomOpen(true)}
190 >
191 <MdAdd className="size-5" />
192 <span>Add Custom</span>
193 </Button>
194 </div>
195
196 <AddCustomCategoryModal
197 open={customOpen}
198 onClose={() => setCustomOpen(false)}
199 onSubmit={async (payload) => {
200 const created = await createCustomTrackingCategory(payload);
201 setCustomCategories((prev) => [created, ...(prev ?? [])]);
202 }}
203 />
204 </SidebarContent>
205 </Sidebar>
206 );
207};
208
209export default DashboardSidebar;
Note: See TracBrowser for help on using the repository browser.