feat: Complete AI Chat Agent, Admin Settings (MCP/AI), and Translations
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import { useRoute, useLocation } from "wouter";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Task, User } from "@shared/schema";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Sun, Moon, ArrowRight, CheckCircle2, ListTodo, Calendar as CalendarIcon } from "lucide-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { format } from "date-fns";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function FocusRoutinePage() {
|
||||
const [match, params] = useRoute("/focus/routine/:type");
|
||||
const type = params?.type as 'morning' | 'evening';
|
||||
const { t } = useTranslation();
|
||||
const [, setLocation] = useLocation();
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
const { data: user } = useQuery<User>({ queryKey: ["/api/user"] });
|
||||
const { data: tasks = [] } = useQuery<Task[]>({ queryKey: ["/api/tasks"] });
|
||||
|
||||
// Filter tasks
|
||||
const today = new Date();
|
||||
const todayTasks = tasks.filter(t => {
|
||||
if (!t.dueDate) return false;
|
||||
const d = new Date(t.dueDate);
|
||||
return d.getDate() === today.getDate() && d.getMonth() === today.getMonth();
|
||||
});
|
||||
|
||||
const completedToday = todayTasks.filter(t => t.status === 'done');
|
||||
const pendingTasks = tasks.filter(t => t.status !== 'done');
|
||||
|
||||
// Handlers
|
||||
const handleComplete = async () => {
|
||||
if (type === 'morning') {
|
||||
setLocation('/focus');
|
||||
} else {
|
||||
triggerConfetti(0.5, 0.5);
|
||||
toast({ title: t('routine.dayComplete', "Day Complete! Great job.") });
|
||||
setLocation('/achievements');
|
||||
}
|
||||
};
|
||||
|
||||
if (!match || !['morning', 'evening'].includes(type)) {
|
||||
return <div className="p-8">Invalid routine type</div>;
|
||||
}
|
||||
|
||||
// Animation variants
|
||||
const pageVariants = {
|
||||
initial: { opacity: 0, y: 20 },
|
||||
in: { opacity: 1, y: 0 },
|
||||
out: { opacity: 0, y: -20 }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen w-full flex flex-col justify-center items-center p-4 transition-colors duration-1000 ${type === 'morning' ? 'bg-orange-50/50 dark:bg-orange-950/20' : 'bg-indigo-50/50 dark:bg-indigo-950/20'}`}>
|
||||
|
||||
<motion.div
|
||||
initial="initial" animate="in" exit="out" variants={pageVariants}
|
||||
className="w-full max-w-2xl"
|
||||
>
|
||||
<Card className="border-none shadow-2xl bg-background/80 backdrop-blur-sm">
|
||||
<CardHeader className="text-center pb-2">
|
||||
<div className="mx-auto mb-4 w-16 h-16 rounded-full flex items-center justify-center bg-primary/10">
|
||||
{type === 'morning' ? <Sun className="w-8 h-8 text-orange-500" /> : <Moon className="w-8 h-8 text-indigo-500" />}
|
||||
</div>
|
||||
<CardTitle className="text-3xl font-bold">
|
||||
{type === 'morning' ? t('routine.goodMorning', 'Good Morning') : t('routine.goodEvening', 'Good Evening')}, {user?.username}
|
||||
</CardTitle>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{type === 'morning'
|
||||
? t('routine.morningSubtitle', "Let's plan your day for success.")
|
||||
: t('routine.eveningSubtitle', "Time to reflect and unwind.")}
|
||||
</p>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="pt-6">
|
||||
<AnimatePresence mode="wait">
|
||||
{type === 'morning' ? (
|
||||
<MorningRoutine tasks={pendingTasks} onComplete={handleComplete} />
|
||||
) : (
|
||||
<EveningRoutine completed={completedToday} pending={pendingTasks} onComplete={handleComplete} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MorningRoutine({ tasks, onComplete }: { tasks: Task[], onComplete: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<motion.div className="space-y-6">
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<h3 className="font-semibold mb-2 flex items-center gap-2">
|
||||
<ListTodo className="w-4 h-4" />
|
||||
{t('routine.tasksForToday', 'Tasks for Today')}
|
||||
</h3>
|
||||
<ScrollArea className="h-[300px] pr-4">
|
||||
{tasks.length === 0 ? (
|
||||
<div className="text-center text-muted-foreground py-8">
|
||||
{t('routine.noTasks', 'No tasks scheduled yet. Add some!')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{tasks.map(task => (
|
||||
<div key={task.id} className="flex items-center gap-3 p-3 bg-card border rounded-md">
|
||||
<div className={`w-1 h-8 rounded-full ${getPriorityColor(task.priority)}`} />
|
||||
<span className="flex-1 font-medium">{task.title}</span>
|
||||
{task.estimatedDuration && <span className="text-xs text-muted-foreground">{task.estimatedDuration}m</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button size="lg" onClick={onComplete} className="w-full sm:w-auto">
|
||||
{t('routine.startFocus', 'Start Focus Mode')} <ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function EveningRoutine({ completed, pending, onComplete }: { completed: Task[], pending: Task[], onComplete: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<motion.div className="space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="p-4 bg-green-50 dark:bg-green-950/20 rounded-lg border border-green-100 dark:border-green-900 text-center">
|
||||
<div className="text-3xl font-bold text-green-600 mb-1">{completed.length}</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-400">{t('routine.completed', 'Completed')}</div>
|
||||
</div>
|
||||
<div className="p-4 bg-orange-50 dark:bg-orange-950/20 rounded-lg border border-orange-100 dark:border-orange-900 text-center">
|
||||
<div className="text-3xl font-bold text-orange-600 mb-1">{pending.length}</div>
|
||||
<div className="text-sm text-orange-700 dark:text-orange-400">{t('routine.open', 'Remaining')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4">
|
||||
<Button size="lg" onClick={onComplete} className="w-full sm:w-auto">
|
||||
{t('routine.endDay', 'End Day')} <CheckCircle2 className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function getPriorityColor(priority: string) {
|
||||
if (priority === 'high') return 'bg-red-500';
|
||||
if (priority === 'medium') return 'bg-yellow-500';
|
||||
return 'bg-blue-500';
|
||||
}
|
||||
|
||||
// Temporary import fix if confetti not available in module scope
|
||||
import { triggerConfetti } from "@/lib/confetti";
|
||||
Reference in New Issue
Block a user