Den is the private cloud vault for your reminders, calendars and to-dos.

feat: dynamic budget list addition to sidebar

hayden.moe fe28f984 ab57a3d0

verified
Changed files
+145 -105
src
Den.Client.Web
src
components
+145 -105
src/Den.Client.Web/src/components/app-sidebar.tsx
··· 7 7 import { t } from "i18next"; 8 8 import { Link } from "@tanstack/react-router"; 9 9 import { useAuth } from "@/lib/state/auth"; 10 + import { useEffect, useState } from "react"; 11 + import { useBudgetsListQuery } from "@/lib/state/queries/budgets"; 10 12 11 13 type MenuItem = { 12 14 type: 'group'; ··· 26 28 icon?: LucideIcon; 27 29 }; 28 30 29 - const items = [ 30 - { 31 - type: 'group', 32 - title: t('menu.home.title'), 33 - children: [ 34 - { 35 - type: 'link', 36 - title: t('menu.home.dashboard'), 37 - url: '/', 38 - icon: Home 39 - } 40 - ], 41 - }, 31 + export const AppSidebar = () => { 32 + const [items, setItems] = useState<MenuItem[]>([ 33 + { 34 + type: 'group', 35 + title: t('menu.home.title'), 36 + children: [ 37 + { 38 + type: 'link', 39 + title: t('menu.home.dashboard'), 40 + url: '/', 41 + icon: Home 42 + } 43 + ], 44 + }, 42 45 43 - { 44 - type: 'group', 45 - title: t('menu.organisation.title'), 46 - children: [ 47 - { 48 - type: 'link', 49 - title: t('menu.organisation.groceries'), 50 - url: '#', 51 - icon: ShoppingBasketIcon, 52 - badge: 15, 53 - }, 54 - { 55 - type: 'link', 56 - title: t('menu.organisation.calendar'), 57 - url: '#', 58 - icon: CalendarIcon, 59 - badge: 3, 60 - }, 61 - { 62 - type: 'link', 63 - title: t('menu.organisation.reminders'), 64 - url: '#', 65 - icon: LightbulbIcon, 66 - badge: 10, 67 - }, 68 - { 69 - type: 'link', 70 - title: t('menu.organisation.recipes'), 71 - url: '#', 72 - icon: UtensilsIcon, 73 - badge: 56 74 - }, 75 - ], 76 - }, 46 + { 47 + type: 'group', 48 + title: t('menu.organisation.title'), 49 + children: [ 50 + { 51 + type: 'link', 52 + title: t('menu.organisation.groceries'), 53 + url: '#', 54 + icon: ShoppingBasketIcon, 55 + badge: 15, 56 + }, 57 + { 58 + type: 'link', 59 + title: t('menu.organisation.calendar'), 60 + url: '#', 61 + icon: CalendarIcon, 62 + badge: 3, 63 + }, 64 + { 65 + type: 'link', 66 + title: t('menu.organisation.reminders'), 67 + url: '#', 68 + icon: LightbulbIcon, 69 + badge: 10, 70 + }, 71 + { 72 + type: 'link', 73 + title: t('menu.organisation.recipes'), 74 + url: '#', 75 + icon: UtensilsIcon, 76 + badge: 56 77 + }, 78 + ], 79 + }, 77 80 78 - { 79 - type: 'group', 80 - title: t('menu.budgeting.title'), 81 - children: [ 82 - { 83 - type: 'submenu', 84 - title: t('menu.budgeting.budgets'), 85 - icon: CirclePoundSterlingIcon, 86 - children: [ 87 - { 88 - type: 'link', 89 - title: 'Household', 90 - url: '#', 91 - }, 92 - { 93 - type: 'link', 94 - title: 'Grocery', 95 - url: '#', 96 - }, 97 - { 98 - type: 'link', 99 - title: 'Bills & Services', 100 - url: '#', 101 - }, 102 - { 103 - type: 'link', 104 - title: 'Disposable Income', 105 - url: '#', 106 - }, 107 - { 108 - type: 'link', 109 - title: t('menu.budgeting.allBudgets'), 110 - url: '/budgets', 111 - }, 112 - ] 113 - }, 114 - ], 115 - }, 81 + { 82 + type: 'group', 83 + title: t('menu.budgeting.title'), 84 + children: [ 85 + { 86 + type: 'submenu', 87 + title: t('menu.budgeting.budgets'), 88 + icon: CirclePoundSterlingIcon, 89 + children: [ 90 + { 91 + type: 'link', 92 + title: t('menu.budgeting.allBudgets'), 93 + url: '/budgets', 94 + }, 95 + ] 96 + }, 97 + ], 98 + }, 116 99 117 - { 118 - type: 'group', 119 - title: t('menu.admin.title'), 120 - children: [ 121 - { 122 - type: 'link', 123 - title: t('menu.admin.settings'), 124 - url: '#', 125 - icon: CogIcon, 126 - }, 127 - { 128 - type: 'link', 129 - title: t('menu.admin.users'), 130 - url: '#', 131 - icon: UsersIcon, 132 - }, 133 - ], 134 - }, 135 - ] as MenuItem[]; 100 + { 101 + type: 'group', 102 + title: t('menu.admin.title'), 103 + children: [ 104 + { 105 + type: 'link', 106 + title: t('menu.admin.settings'), 107 + url: '#', 108 + icon: CogIcon, 109 + }, 110 + { 111 + type: 'link', 112 + title: t('menu.admin.users'), 113 + url: '#', 114 + icon: UsersIcon, 115 + }, 116 + ], 117 + }, 118 + ]); 136 119 137 - export const AppSidebar = () => { 138 120 const { logout } = useAuth(); 139 121 const { data, isLoading } = useMeQuery(); 122 + const { data: budgetsData } = useBudgetsListQuery(); 123 + 124 + useEffect(() => { 125 + if (budgetsData) { 126 + setItems((prevItems) => { 127 + const budgetingGroupIndex = prevItems.findIndex( 128 + (item) => item.type === 'group' && item.title === t('menu.budgeting.title') 129 + ); 130 + 131 + if (budgetingGroupIndex === -1) return prevItems; 132 + 133 + const budgetingGroup = prevItems[budgetingGroupIndex]; 134 + if (budgetingGroup.type !== 'group') return prevItems; 135 + 136 + const budgetsSubmenuIndex = budgetingGroup.children.findIndex( 137 + (child) => child.type === 'submenu' && child.title === t('menu.budgeting.budgets') 138 + ); 139 + 140 + if (budgetsSubmenuIndex === -1) return prevItems; 141 + 142 + const budgetsSubmenu = budgetingGroup.children[budgetsSubmenuIndex]; 143 + if (budgetsSubmenu.type !== 'submenu') return prevItems; 144 + 145 + const dynamicBudgetItems: MenuItem[] = budgetsData.map((budget) => ({ 146 + type: 'link', 147 + title: budget.displayName, 148 + url: `/budgets/${budget.id}`, 149 + })); 150 + 151 + const updatedBudgetsSubmenu = { 152 + ...budgetsSubmenu, 153 + children: [ 154 + { 155 + type: 'link' as const, 156 + title: t('menu.budgeting.allBudgets'), 157 + url: '/budgets', 158 + }, 159 + ...dynamicBudgetItems, 160 + ], 161 + }; 162 + 163 + const updatedBudgetingGroup = { 164 + ...budgetingGroup, 165 + children: [ 166 + ...budgetingGroup.children.slice(0, budgetsSubmenuIndex), 167 + updatedBudgetsSubmenu, 168 + ...budgetingGroup.children.slice(budgetsSubmenuIndex + 1), 169 + ], 170 + }; 171 + 172 + return [ 173 + ...prevItems.slice(0, budgetingGroupIndex), 174 + updatedBudgetingGroup, 175 + ...prevItems.slice(budgetingGroupIndex + 1), 176 + ]; 177 + }); 178 + } 179 + }, [budgetsData]); 140 180 141 181 return ( 142 182 <Sidebar>