mirror of
https://github.com/CarGDev/pomodoro.git
synced 2025-09-18 20:38:29 +00:00
Set up project configuration and base UI components
Initializes the project with necessary configurations, including Replit settings and a .gitignore file. It also introduces foundational UI components for the application, such as buttons, dialogs, and layout elements, likely for the Pomodoro timer application. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 59a5ae27-3c71-459b-b42f-fe14121bf9c3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3007b6f6-d03b-45e1-9ed1-7ce8de18ea24/59a5ae27-3c71-459b-b42f-fe14121bf9c3/Uupe4F4
This commit is contained in:
132
client/src/components/organisms/Sidebar.tsx
Normal file
132
client/src/components/organisms/Sidebar.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Clock, BarChart3, Settings, Sun, Moon } from "lucide-react";
|
||||
import { Button } from "@/components/atoms/Button";
|
||||
import { useTheme } from "@/lib/theme";
|
||||
import { useStore } from "@/lib/store";
|
||||
import { Link, useLocation } from "wouter";
|
||||
|
||||
interface SidebarProps {
|
||||
className?: string;
|
||||
isMobile?: boolean;
|
||||
onNavigate?: () => void;
|
||||
}
|
||||
|
||||
export function Sidebar({ className = "", isMobile = false, onNavigate }: SidebarProps) {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const [location] = useLocation();
|
||||
const localSessions = useStore((state) => state.localSessions);
|
||||
|
||||
// Calculate today's stats
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const todaySessions = localSessions.filter(session => {
|
||||
const sessionDate = new Date(session.startedAt);
|
||||
sessionDate.setHours(0, 0, 0, 0);
|
||||
return sessionDate.getTime() === today.getTime();
|
||||
});
|
||||
|
||||
const todayFocusSessions = todaySessions.filter(s => s.type === 'focus');
|
||||
const completedToday = todayFocusSessions.filter(s => s.completed);
|
||||
const completionRate = todayFocusSessions.length > 0
|
||||
? Math.round((completedToday.length / todayFocusSessions.length) * 100)
|
||||
: 0;
|
||||
|
||||
const navItems = [
|
||||
{ path: "/", icon: Clock, label: "Timer", testId: "nav-timer" },
|
||||
{ path: "/history", icon: BarChart3, label: "History", testId: "nav-history" },
|
||||
{ path: "/settings", icon: Settings, label: "Settings", testId: "nav-settings" },
|
||||
];
|
||||
|
||||
const isActive = (path: string) => {
|
||||
if (path === "/" && location === "/") return true;
|
||||
if (path !== "/" && location.startsWith(path)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className={`w-64 bg-card border-r border-border flex-shrink-0 sidebar-transition ${
|
||||
isMobile ? 'sidebar-mobile' : ''
|
||||
} ${className}`}>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Logo/Brand */}
|
||||
<div className="p-6 border-b border-border">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
|
||||
<Clock className="text-primary-foreground text-sm" />
|
||||
</div>
|
||||
<h1 className="text-xl font-semibold">Pomodorian</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation Menu */}
|
||||
<nav className="flex-1 p-4 space-y-2">
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const active = isActive(item.path);
|
||||
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
<Button
|
||||
variant={active ? "default" : "ghost"}
|
||||
className={`w-full justify-start space-x-3 ${
|
||||
active
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
||||
}`}
|
||||
onClick={onNavigate}
|
||||
data-testid={item.testId}
|
||||
>
|
||||
<Icon className="w-5 h-5" />
|
||||
<span>{item.label}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Theme Toggle & Stats */}
|
||||
<div className="p-4 border-t border-border space-y-4">
|
||||
{/* Quick Stats */}
|
||||
<div className="bg-muted rounded-lg p-3 text-sm">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<span className="text-muted-foreground">Today's Sessions</span>
|
||||
<span className="font-medium" data-testid="stat-today-sessions">
|
||||
{todayFocusSessions.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-muted-foreground">Completion Rate</span>
|
||||
<span className="font-medium text-chart-2" data-testid="stat-today-completion">
|
||||
{completionRate}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Theme Toggle */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-between"
|
||||
onClick={toggleTheme}
|
||||
data-testid="button-theme-toggle"
|
||||
>
|
||||
<span className="flex items-center space-x-3">
|
||||
{theme === 'dark' ? (
|
||||
<Sun className="w-5 h-5" />
|
||||
) : (
|
||||
<Moon className="w-5 h-5" />
|
||||
)}
|
||||
<span>{theme === 'dark' ? 'Light Mode' : 'Dark Mode'}</span>
|
||||
</span>
|
||||
<div className={`w-10 h-6 rounded-full relative transition-colors ${
|
||||
theme === 'dark' ? 'bg-primary' : 'bg-secondary'
|
||||
}`}>
|
||||
<div className={`w-4 h-4 bg-white rounded-full absolute top-1 transition-transform ${
|
||||
theme === 'dark' ? 'translate-x-5' : 'translate-x-1'
|
||||
}`} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user