feat: Complete AI Chat Agent, Admin Settings (MCP/AI), and Translations
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-12-15 21:52:36 +01:00
parent 71d5c825c5
commit 9736864425
24 changed files with 1679 additions and 401 deletions
+168
View File
@@ -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";