7ce4f7efdc
continuous-integration/drone/push Build is passing
- Add Focus Tools dashboard with collapsible help section - Implement Quick Wins page for tasks under 15 minutes - Add Single Task Focus mode to reduce overwhelm - Create Body Doubling page for virtual co-working - Add visual timer, break reminders, and energy tracking - Implement hyperfocus protection alerts - Add ADHD settings panel with customizable options - Include full English and German translations - Fix larger touch targets CSS to not break button layouts - Add Playwright tests for Focus Tools features
158 lines
8.0 KiB
TypeScript
158 lines
8.0 KiB
TypeScript
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Home, Calendar, List, LayoutGrid, Settings, CheckSquare, Target, Trophy, LogOut, Award, Bot, CalendarOff, Bell, Clock, Brain } from 'lucide-react';
|
|
import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuItem,
|
|
SidebarMenuButton,
|
|
SidebarRail,
|
|
useSidebar,
|
|
SidebarTrigger,
|
|
SidebarSeparator,
|
|
} from "@/components/ui/sidebar"
|
|
import { Task, User } from '@shared/schema';
|
|
import ThemeToggle from './ThemeToggle';
|
|
import { useLocation } from "wouter";
|
|
import { GamificationBar } from './GamificationBar';
|
|
|
|
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
|
user: User | undefined;
|
|
}
|
|
|
|
export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
|
const { t } = useTranslation();
|
|
const { state } = useSidebar();
|
|
const { toast } = useToast();
|
|
const queryClient = useQueryClient();
|
|
const [location, setLocation] = useLocation();
|
|
|
|
// Fetch unscheduled count
|
|
// const { data: unscheduledCount = 0 } = useQuery({
|
|
// queryKey: ['/api/tasks'],
|
|
// select: (tasks: Task[]) => tasks.filter(t => !t.dueDate && t.status !== 'done').length,
|
|
// enabled: !!user
|
|
// });
|
|
|
|
const logoutMutation = useMutation({
|
|
mutationFn: async () => {
|
|
await fetch("/api/logout", { method: "POST" });
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.clear();
|
|
// toast({ title: "Logged out successfully" });
|
|
window.location.href = "/auth?mode=login";
|
|
},
|
|
});
|
|
|
|
const items = [
|
|
{ title: t('navigation.focus'), id: 'focus', path: '/', icon: Target, color: 'text-red-500' },
|
|
{ title: t('navigation.tasks'), id: 'tasks', path: '/tasks', icon: Home, color: 'text-blue-500' },
|
|
{ title: t('navigation.calendar'), id: 'calendar', path: '/calendar', icon: Calendar, color: 'text-violet-500' },
|
|
{ title: t('navigation.weekList'), id: 'weeklist', path: '/weeklist', icon: List, color: 'text-pink-500' },
|
|
{ title: t('unscheduled.title', 'Unscheduled'), id: 'unscheduled', path: '/unscheduled', icon: CalendarOff, color: 'text-slate-500' },
|
|
{ title: t('navigation.kanban'), id: 'kanban', path: '/kanban', icon: LayoutGrid, color: 'text-orange-500' },
|
|
{ title: t('navigation.adhd', 'ADHD Mode'), id: 'adhd', path: '/adhd', icon: Brain, color: 'text-purple-500' },
|
|
{ title: t('navigation.achievements'), id: 'achievements', path: '/achievements', icon: Trophy, color: 'text-yellow-500' },
|
|
{ title: t('navigation.leaderboard'), id: 'leaderboard', path: '/leaderboard', icon: Award, color: 'text-yellow-500' },
|
|
{ title: t('analytics.timeTracking'), id: 'time-tracking', path: '/time-tracking', icon: Clock, color: 'text-teal-500' },
|
|
...(user?.aiEnabled ? [{ title: t('navigation.aiChat', 'AI Chat'), id: 'ai-chat', path: '/ai', icon: Bot, color: 'text-indigo-500' }] : []),
|
|
{ title: t('navigation.settings'), id: 'settings', path: '/settings', icon: Settings, color: 'text-gray-500' },
|
|
]
|
|
|
|
return (
|
|
<Sidebar collapsible="icon" {...props}>
|
|
<SidebarHeader>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground h-14">
|
|
<div className="flex aspect-square size-9 items-center justify-center rounded-xl overflow-hidden shadow-lg bg-transparent">
|
|
<img src="/favicon.png" alt="Logo" className="w-full h-full object-cover" />
|
|
</div>
|
|
{state !== 'collapsed' && (
|
|
<div className="grid flex-1 text-left text-sm leading-tight ml-2">
|
|
<span className="truncate font-bold text-base">{t('app.title')}</span>
|
|
<span className="truncate text-xs opacity-70">Personal</span>
|
|
</div>
|
|
)}
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarHeader>
|
|
<SidebarContent>
|
|
<SidebarMenu className="gap-2 px-2">
|
|
{items.map((item) => (
|
|
<SidebarMenuItem key={item.id}>
|
|
<SidebarMenuButton
|
|
isActive={location === item.path || (item.path !== '/' && location.startsWith(item.path))}
|
|
onClick={() => setLocation(item.path)}
|
|
tooltip={item.title}
|
|
className={`h-12 transition-all duration-200 ${location === item.path || (item.path !== '/' && location.startsWith(item.path))
|
|
? 'bg-gradient-to-r from-violet-600 to-indigo-600 text-white shadow-md hover:from-violet-500 hover:to-indigo-500 hover:text-white'
|
|
: 'hover:bg-sidebar-accent hover:pl-4'
|
|
}`}
|
|
>
|
|
<item.icon className={`transition-all duration-200 ${state === 'collapsed' ? 'size-7' : 'size-5'} ${location === item.path || (item.path !== '/' && location.startsWith(item.path)) ? 'text-white' : item.color}`} />
|
|
{state !== 'collapsed' && (
|
|
<span className="font-medium text-base ml-2">{item.title}</span>
|
|
)}
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
))}
|
|
</SidebarMenu>
|
|
</SidebarContent>
|
|
<SidebarSeparator />
|
|
<SidebarFooter>
|
|
{state !== 'collapsed' && user && (
|
|
<GamificationBar xp={user.xp} level={user.level} streak={user.currentStreak} />
|
|
)}
|
|
<div className={`p-4 ${state === 'collapsed'
|
|
? 'flex flex-col items-center justify-center gap-4'
|
|
: 'grid grid-cols-4 items-center gap-2'
|
|
}`}>
|
|
<div className={state === 'collapsed' ? '' : 'justify-self-start'}>
|
|
<ThemeToggle />
|
|
</div>
|
|
|
|
<div className={state === 'collapsed' ? '' : 'justify-self-center'}>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => setLocation('/notifications')}
|
|
className="bg-transparent hover:bg-sidebar-accent"
|
|
title={t('navigation.notifications', 'Notifications')}
|
|
>
|
|
<Bell className="size-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className={state === 'collapsed' ? '' : 'justify-self-center'}>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => logoutMutation.mutate()}
|
|
disabled={logoutMutation.isPending}
|
|
className="text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950/20"
|
|
title={t('auth.logout', 'Logout')}
|
|
>
|
|
<LogOut className="size-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className={state === 'collapsed' ? '' : 'justify-self-end'}>
|
|
<SidebarTrigger />
|
|
</div>
|
|
</div>
|
|
</SidebarFooter>
|
|
<SidebarRail />
|
|
</Sidebar>
|
|
)
|
|
}
|
|
|