feat: Notifications page, Sidebar layout fixes, and Achievements enhancements
continuous-integration/drone/push Build is failing
continuous-integration/drone/push Build is failing
- implemented /notifications page with overdue/upcoming alerts - Fixed sidebar scrolling in collapsed mode - Moved notification button to sidebar footer - Enhanced Achievements page with streak stats and tooltips - Improved XP history to show task titles - Added missing translations (en/de) - Removed top bar header - Fixed Docker environment routing
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Home, Calendar, List, LayoutGrid, Settings, CheckSquare, Target, Trophy, LogOut, Award, Bot, CalendarOff } from 'lucide-react';
|
||||
import { Home, Calendar, List, LayoutGrid, Settings, CheckSquare, Target, Trophy, LogOut, Award, Bot, CalendarOff, Bell } from 'lucide-react';
|
||||
import { useQueryClient, useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SidebarRail,
|
||||
useSidebar,
|
||||
SidebarTrigger,
|
||||
SidebarSeparator,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { Task, User } from '@shared/schema';
|
||||
import ThemeToggle from './ThemeToggle';
|
||||
@@ -44,8 +45,9 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
await fetch("/api/logout", { method: "POST" });
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.setQueryData(["/api/user"], null);
|
||||
toast({ title: "Logged out successfully" });
|
||||
queryClient.clear();
|
||||
// toast({ title: "Logged out successfully" });
|
||||
window.location.href = "/auth?mode=login";
|
||||
},
|
||||
});
|
||||
|
||||
@@ -68,8 +70,8 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
<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 bg-gradient-to-br from-violet-600 to-indigo-600 text-white shadow-lg">
|
||||
<CheckSquare className="size-5" />
|
||||
<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">
|
||||
@@ -103,18 +105,31 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
))}
|
||||
</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-3 items-center'
|
||||
: '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"
|
||||
@@ -122,7 +137,7 @@ export function AppSidebar({ user, ...props }: AppSidebarProps) {
|
||||
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="Logout"
|
||||
title={t('auth.logout', 'Logout')}
|
||||
>
|
||||
<LogOut className="size-4" />
|
||||
</Button>
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
HoverCard,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { Component, ErrorInfo, ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
public state: State = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
public static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error("Uncaught error:", error, errorInfo);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-red-50 text-red-900">
|
||||
<div className="max-w-xl p-8 bg-white rounded-lg shadow-xl border border-red-200">
|
||||
<h1 className="text-2xl font-bold mb-4">Something went wrong</h1>
|
||||
<p className="mb-4">The application crashed. Here is the error:</p>
|
||||
<pre className="bg-red-100 p-4 rounded overflow-auto text-sm font-mono">
|
||||
{this.state.error?.toString()}
|
||||
</pre>
|
||||
<button
|
||||
className="mt-6 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
Reload Page
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ interface KanbanBoardProps {
|
||||
onTaskStatusChange?: (taskId: string, newStatus: Task['status']) => void;
|
||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||
onTaskClick?: (task: Task) => void;
|
||||
onTaskEdit?: (task: Task) => void;
|
||||
onTaskDelete?: (taskId: string) => void;
|
||||
onStartTimer?: (taskId: string) => void;
|
||||
onStopTimer?: (taskId: string) => void;
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function PomodoroOverlay({ taskId, taskTitle, isOpen, onClose, on
|
||||
// Initialize Worker
|
||||
useEffect(() => {
|
||||
if (typeof Worker !== 'undefined') {
|
||||
workerRef.current = new Worker(new URL('../workers/timer.worker.ts', import.meta.url));
|
||||
workerRef.current = new Worker(new URL('../workers/timer.worker.ts', import.meta.url), { type: 'module' });
|
||||
|
||||
workerRef.current.onmessage = (e) => {
|
||||
const { type, remaining } = e.data;
|
||||
|
||||
@@ -55,8 +55,12 @@ export function useRoutineBlocker() {
|
||||
const morningStartTime = parseTime(settings.morning_routine_time || "09:00");
|
||||
|
||||
// Block if it's morning time (>= start) AND < evening start (if evening enabled).
|
||||
// Effectively: Morning Routine is mandatory from 9:00 AM until 5:00 PM.
|
||||
const cutoffTime = eveningEnabled ? eveningStartTime : 24 * 60; // Up to evening or end of day
|
||||
// FIX: Add a hard cutoff (e.g. 13:00 / 1 PM) so morning routine doesn't haunt users all day.
|
||||
const HARD_MORNING_CUTOFF = 13 * 60;
|
||||
|
||||
const cutoffTime = eveningEnabled
|
||||
? Math.min(eveningStartTime, HARD_MORNING_CUTOFF)
|
||||
: HARD_MORNING_CUTOFF;
|
||||
|
||||
if (currentTimeVal >= morningStartTime && currentTimeVal < cutoffTime && !isToday(user.lastMorningRoutine)) {
|
||||
return '/focus/routine/morning';
|
||||
@@ -84,17 +88,21 @@ export function useRoutineBlocker() {
|
||||
const currentTimeVal = now.getHours() * 60 + now.getMinutes();
|
||||
|
||||
const eveningEnabled = settings.evening_routine_enabled !== "false";
|
||||
const eveningStartTime = (settings.evening_routine_time || "17:00").split(':').map(Number);
|
||||
const eveningStartVal = eveningStartTime[0] * 60 + eveningStartTime[1];
|
||||
// Safe parse
|
||||
const [eh, em] = (settings.evening_routine_time || "17:00").split(':').map(Number);
|
||||
const eveningStartVal = (eh || 0) * 60 + (em || 0);
|
||||
|
||||
const morningEnabled = settings.morning_routine_enabled !== "false";
|
||||
if (!morningEnabled) return false;
|
||||
|
||||
const morningStartTime = (settings.morning_routine_time || "09:00").split(':').map(Number);
|
||||
const startVal = morningStartTime[0] * 60 + morningStartTime[1];
|
||||
const [mh, mm] = (settings.morning_routine_time || "09:00").split(':').map(Number);
|
||||
const startVal = (mh || 0) * 60 + (mm || 0);
|
||||
|
||||
// Cutoff: End of day OR Evening Start
|
||||
const cutoffVal = eveningEnabled ? eveningStartVal : 24 * 60;
|
||||
const HARD_MORNING_CUTOFF = 13 * 60; // 13:00
|
||||
|
||||
// Cutoff: Min of (End of day OR Evening Start) AND Hard Cutoff
|
||||
const baseCutoff = eveningEnabled ? eveningStartVal : 24 * 60;
|
||||
const cutoffVal = Math.min(baseCutoff, HARD_MORNING_CUTOFF);
|
||||
|
||||
if (currentTimeVal >= startVal && currentTimeVal < cutoffVal && !isSameDay(user.lastMorningRoutine, now)) return true;
|
||||
|
||||
@@ -110,8 +118,8 @@ export function useRoutineBlocker() {
|
||||
const eveningEnabled = settings.evening_routine_enabled !== "false";
|
||||
if (!eveningEnabled) return false;
|
||||
|
||||
const eveningStartTime = (settings.evening_routine_time || "17:00").split(':').map(Number);
|
||||
const startVal = eveningStartTime[0] * 60 + eveningStartTime[1];
|
||||
const [eh, em] = (settings.evening_routine_time || "17:00").split(':').map(Number);
|
||||
const startVal = (eh || 0) * 60 + (em || 0);
|
||||
|
||||
if (currentTimeVal >= startVal && !isSameDay(user.lastEveningRoutine, now)) return true;
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle, X, Lock, CornerDownRight } from "lucide-react";
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle, X, Lock, CornerDownRight, Share2, Users } from "lucide-react";
|
||||
import { Task, Label, User } from '@shared/schema';
|
||||
import { ShareTaskModal } from './ShareTaskModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { motion, PanInfo, useAnimation } from 'framer-motion';
|
||||
@@ -47,10 +48,6 @@ interface TaskCardProps {
|
||||
isDragging?: boolean;
|
||||
}
|
||||
|
||||
import { ShareTaskModal } from './ShareTaskModal';
|
||||
import { Share2, Users } from "lucide-react";
|
||||
import { User } from "@shared/schema";
|
||||
|
||||
function SharedTaskIcon({ task }: { task: Task }) {
|
||||
const { t } = useTranslation();
|
||||
const { data: user } = useQuery<User>({ queryKey: ["/api/user"], retry: false });
|
||||
|
||||
@@ -10,11 +10,12 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CalendarIcon, Plus, Tag, Check, Link2 } from 'lucide-react';
|
||||
import { CalendarIcon, Plus, Tag, Check, Link2, Info } from 'lucide-react';
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { parseTaskInput } from '../lib/nlp';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
||||
interface TaskCreationModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -47,13 +48,15 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Fetch labels
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
const { data: labelsData } = useQuery<Label[]>({
|
||||
queryKey: ['/api/labels'],
|
||||
});
|
||||
const labels = labelsData ?? [];
|
||||
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
const { data: tasksData } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
const tasks = tasksData ?? [];
|
||||
|
||||
const handleSave = () => {
|
||||
if (!title.trim()) {
|
||||
@@ -193,7 +196,21 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
|
||||
|
||||
<Select value={energyLevel} onValueChange={(value: 'low' | 'medium' | 'high') => setEnergyLevel(value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('taskCreation.energy')} />
|
||||
<div className="flex items-center gap-2">
|
||||
<SelectValue placeholder={t('taskCreation.energy')} />
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="cursor-help" onClick={(e) => e.stopPropagation()}>
|
||||
<p className="sr-only">Help</p>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p className="max-w-xs">{t('taskCreation.energyHelp', 'Match tasks to your energy levels. Low energy tasks are good for slumps, High energy tasks require focus.')}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">{t('gamification.energy.low')}</SelectItem>
|
||||
|
||||
@@ -66,9 +66,10 @@ export default function TaskDetailsModal({
|
||||
const [isCreatingSubtask, setIsCreatingSubtask] = useState(false);
|
||||
const [isScheduling, setIsScheduling] = useState(false);
|
||||
|
||||
const { data: tasks = [] } = useQuery<Task[]>({
|
||||
const { data: tasksData } = useQuery<Task[]>({
|
||||
queryKey: ['/api/tasks'],
|
||||
});
|
||||
const tasks = tasksData ?? [];
|
||||
|
||||
// Notes state
|
||||
const [notes, setNotes] = useState('');
|
||||
|
||||
@@ -51,7 +51,7 @@ export function SMTPSettingsCard() {
|
||||
mutationFn: (data: Partial<typeof settings>) => apiRequest("POST", "/api/admin/settings", data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/admin/settings"] });
|
||||
toast({ title: t('smtp.saveSuccess', "Settings updated") });
|
||||
toast({ title: t('settings.smtp.saveSuccess', "Settings updated") });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import BottomNavigation from '../BottomNavigation';
|
||||
|
||||
export default function BottomNavigationExample() {
|
||||
return (
|
||||
<div className="relative h-[400px] bg-muted/20">
|
||||
<div className="p-4">
|
||||
<h3 className="text-lg font-semibold mb-2">App Content Area</h3>
|
||||
<p className="text-muted-foreground">
|
||||
This is where your main app content would be displayed.
|
||||
The bottom navigation is fixed at the bottom of the screen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<BottomNavigation />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import CalendarView from '../CalendarView';
|
||||
import { Task } from '../TaskCard';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Team meeting',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
dueDate: new Date(),
|
||||
timeTracked: 0,
|
||||
isTracking: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Review design mockups',
|
||||
status: 'inProgress',
|
||||
priority: 'medium',
|
||||
dueDate: addDays(new Date(), 2),
|
||||
timeTracked: 30,
|
||||
isTracking: false
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Client presentation',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
dueDate: addDays(new Date(), 5),
|
||||
timeTracked: 0,
|
||||
isTracking: false
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Code review',
|
||||
status: 'todo',
|
||||
priority: 'low',
|
||||
dueDate: addDays(new Date(), 1),
|
||||
timeTracked: 0,
|
||||
isTracking: false
|
||||
}
|
||||
];
|
||||
|
||||
export default function CalendarViewExample() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<CalendarView tasks={mockTasks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import KanbanBoard from '../KanbanBoard';
|
||||
import { Task } from '../TaskCard';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Design new landing page',
|
||||
description: 'Create wireframes and mockups for the new product landing page',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
dueDate: addDays(new Date(), 3),
|
||||
timeTracked: 0,
|
||||
isTracking: false,
|
||||
projectId: 'project-1'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Implement user authentication',
|
||||
description: 'Set up login, registration, and password reset functionality',
|
||||
status: 'inProgress',
|
||||
priority: 'high',
|
||||
dueDate: addDays(new Date(), 1),
|
||||
timeTracked: 120,
|
||||
isTracking: true,
|
||||
projectId: 'project-2'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Write API documentation',
|
||||
status: 'inProgress',
|
||||
priority: 'medium',
|
||||
dueDate: addDays(new Date(), 5),
|
||||
timeTracked: 45,
|
||||
isTracking: false,
|
||||
projectId: 'project-1'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Set up CI/CD pipeline',
|
||||
description: 'Configure automated testing and deployment',
|
||||
status: 'done',
|
||||
priority: 'medium',
|
||||
dueDate: addDays(new Date(), -2),
|
||||
timeTracked: 180,
|
||||
isTracking: false,
|
||||
projectId: 'project-2'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Review code changes',
|
||||
status: 'todo',
|
||||
priority: 'low',
|
||||
dueDate: new Date(),
|
||||
timeTracked: 0,
|
||||
isTracking: false
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Update dependencies',
|
||||
status: 'done',
|
||||
priority: 'low',
|
||||
dueDate: addDays(new Date(), -1),
|
||||
timeTracked: 30,
|
||||
isTracking: false
|
||||
}
|
||||
];
|
||||
|
||||
export default function KanbanBoardExample() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<KanbanBoard tasks={mockTasks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import ProjectTemplate from '../ProjectTemplate';
|
||||
|
||||
export default function ProjectTemplateExample() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<ProjectTemplate />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import TaskCard from '../TaskCard';
|
||||
import { Task } from '@shared/schema';
|
||||
|
||||
const mockTask: Task = {
|
||||
id: '1',
|
||||
title: 'Design mobile task interface',
|
||||
description: 'Create wireframes and mockups for the mobile-first task management interface',
|
||||
status: 'inProgress',
|
||||
priority: 'high',
|
||||
dueDate: new Date('2024-09-20'),
|
||||
timeTracked: 45,
|
||||
isTracking: false,
|
||||
projectId: 'project-1',
|
||||
notes: 'Focus on mobile-first design principles'
|
||||
};
|
||||
|
||||
export default function TaskCardExample() {
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<TaskCard task={mockTask} />
|
||||
<TaskCard
|
||||
task={{
|
||||
...mockTask,
|
||||
id: '2',
|
||||
title: 'Quick task without details',
|
||||
description: undefined,
|
||||
status: 'todo',
|
||||
priority: 'low',
|
||||
timeTracked: 0
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import TaskCreationModal from '../TaskCreationModal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
export default function TaskCreationModalExample() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Button
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="w-full flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Create New Task
|
||||
</Button>
|
||||
|
||||
<TaskCreationModal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
onSave={(task) => {
|
||||
console.log('Task saved:', task);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import TaskList from '../TaskList';
|
||||
import { Task } from '../TaskCard';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Review quarterly reports',
|
||||
description: 'Analyze Q3 performance metrics and prepare summary',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
dueDate: addDays(new Date(), 2),
|
||||
timeTracked: 0,
|
||||
isTracking: false,
|
||||
projectId: 'project-1'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Update website copy',
|
||||
description: 'Revise landing page content based on user feedback',
|
||||
status: 'inProgress',
|
||||
priority: 'medium',
|
||||
dueDate: addDays(new Date(), 5),
|
||||
timeTracked: 90,
|
||||
isTracking: true,
|
||||
projectId: 'project-2'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Team standup meeting',
|
||||
status: 'todo',
|
||||
priority: 'low',
|
||||
dueDate: new Date(),
|
||||
timeTracked: 0,
|
||||
isTracking: false
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Fix login bug',
|
||||
description: 'Users unable to login with special characters in password',
|
||||
status: 'todo',
|
||||
priority: 'high',
|
||||
dueDate: subDays(new Date(), 1), // Overdue
|
||||
timeTracked: 30,
|
||||
isTracking: false,
|
||||
projectId: 'project-2'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Deploy new features',
|
||||
status: 'done',
|
||||
priority: 'medium',
|
||||
dueDate: subDays(new Date(), 2),
|
||||
timeTracked: 120,
|
||||
isTracking: false,
|
||||
projectId: 'project-1'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Write documentation',
|
||||
description: 'Document new API endpoints for external developers',
|
||||
status: 'inProgress',
|
||||
priority: 'medium',
|
||||
dueDate: addDays(new Date(), 7),
|
||||
timeTracked: 60,
|
||||
isTracking: false,
|
||||
projectId: 'project-1'
|
||||
}
|
||||
];
|
||||
|
||||
export default function TaskListExample() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<TaskList tasks={mockTasks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import ThemeToggle from '../ThemeToggle';
|
||||
|
||||
export default function ThemeToggleExample() {
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div>
|
||||
<h3 className="font-semibold">Dark Mode</h3>
|
||||
<p className="text-sm text-muted-foreground">Toggle between light and dark themes</p>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-card border rounded-lg">
|
||||
<p className="text-sm">This card will change appearance when you toggle the theme.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -375,7 +375,7 @@ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-slot="sidebar-content"
|
||||
data-sidebar="content"
|
||||
className={cn(
|
||||
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
|
||||
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user