From d50f91503460a5f2121254bfc374a1accc9719ec Mon Sep 17 00:00:00 2001 From: Rupesh Date: Fri, 23 Jan 2026 11:31:37 +0530 Subject: [PATCH 1/7] commit --- src/App2.js | 1453 ------------------------------------------------- src/App_v1.js | 1089 ------------------------------------ 2 files changed, 2542 deletions(-) delete mode 100644 src/App2.js delete mode 100644 src/App_v1.js diff --git a/src/App2.js b/src/App2.js deleted file mode 100644 index 8532da4..0000000 --- a/src/App2.js +++ /dev/null @@ -1,1453 +0,0 @@ -import React, { useState, useEffect, useMemo } from 'react'; -import { initializeApp } from 'firebase/app'; -import { getAuth, onAuthStateChanged, GoogleAuthProvider, signInWithPopup, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth'; -import { getFirestore, collection, doc, addDoc, updateDoc, deleteDoc, onSnapshot, query, where, getDocs, writeBatch } from 'firebase/firestore'; -import { Book, Calendar, CheckSquare, ChevronDown, ChevronRight, Clock, Edit2, Flame, Info, LogOut, Plus, Repeat, Save, Sparkles, Tag, Trash2, TrendingUp, X } from 'lucide-react'; -import { callGeminiWithRetry } from './services/geminiService'; - -// --- Firebase Configuration --- -// It's recommended to store these in environment variables -const firebaseConfig = { - apiKey: process.env.REACT_APP_FIREBASE_API_KEY, - authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, - projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, - storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.REACT_APP_FIREBASE_APP_ID -}; - -const appId = 'my-prod-hub'; -const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID || ""; - -// --- Firebase Initialization --- -let app; -let auth; -let db; -// A simple check to see if the config is placeholder or not -const isFirebaseConfigured = firebaseConfig.apiKey && firebaseConfig.apiKey !== "YOUR_API_KEY"; - -if (isFirebaseConfigured) { - try { - app = initializeApp(firebaseConfig); - auth = getAuth(app); - db = getFirestore(app); - } catch (error) { - console.error("Firebase initialization error:", error); - } -} - -// --- Helper Functions --- -const formatDate = (date) => { - if (!date) return 'N/A'; - // Handles both Firestore Timestamps and JS Date objects - const d = date instanceof Date ? date : date.toDate(); - return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric' }).format(d); -}; - -const formatTime = (date) => { - if (!date) return ''; - const d = new Date(date); - // Check if it's an all-day event (time is midnight UTC) - if (d.getUTCHours() === 0 && d.getUTCMinutes() === 0 && d.getUTCSeconds() === 0) { - return 'All-day'; - } - return new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).format(d); -}; - -const getLocalDateKey = (date) => { - const d = new Date(date); - // Adjust for timezone offset to get the correct local date - d.setMinutes(d.getMinutes() - d.getTimezoneOffset()); - return d.toISOString().split('T')[0]; // YYYY-MM-DD -}; - -// --- Configuration Error Component --- -function ConfigurationNeeded({ missingFirebase, missingGoogle }) { - return ( -
-
- -

Configuration Required

- {missingFirebase &&

Firebase Config Missing: Please ensure your Firebase environment variables (REACT_APP_FIREBASE_*) are set correctly in your .env.local file.

} - {missingGoogle &&

Google Client ID Missing: Please ensure REACT_APP_GOOGLE_CLIENT_ID is set in your .env.local file to enable Google Calendar sync.

} -
-
- ); -} - -// --- Login Screen Component --- -function LoginScreen() { - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const [isSigningUp, setIsSigningUp] = useState(false); - - const handleGoogleSignIn = async () => { - const provider = new GoogleAuthProvider(); - try { - await signInWithPopup(auth, provider); - } catch (error) { - setError(error.message); - console.error("Google Sign-In Error:", error); - } - }; - - const handleEmailAuth = async (e) => { - e.preventDefault(); - setError(''); - try { - if (isSigningUp) { - await createUserWithEmailAndPassword(auth, email, password); - } else { - await signInWithEmailAndPassword(auth, email, password); - } - } catch (error) { - setError(error.message); - } - }; - - return ( -
-
-
-

Welcome to ProdHub

-

Sign in to continue

-
- -
Or continue with
-
- setEmail(e.target.value)} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required /> - setPassword(e.target.value)} className="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required /> - {error &&

{error}

} - -
-

- {isSigningUp ? 'Already have an account?' : "Don't have an account?"} - -

-
-
- ); -} - -// This is the main content of your application -function HubApp({ user, handleSignOut }) { - const [projects, setProjects] = useState([]); - const [tasks, setTasks] = useState([]); - const [habits, setHabits] = useState([]); - const [goals, setGoals] = useState([]); - const [habitEntries, setHabitEntries] = useState([]); - const [activeView, setActiveView] = useState('dashboard'); - const [selectedProjectId, setSelectedProjectId] = useState(null); - - const [syncedEvents, setSyncedEvents] = useState([]); - const [tokenClient, setTokenClient] = useState(null); - const [isGsiScriptLoaded, setIsGsiScriptLoaded] = useState(false); - - // Effect to load the Google Sign-In script - useEffect(() => { - const script = document.createElement('script'); - script.src = 'https://accounts.google.com/gsi/client'; - script.async = true; - script.defer = true; - script.onload = () => setIsGsiScriptLoaded(true); - document.body.appendChild(script); - return () => document.body.removeChild(script); - }, []); - - // Effect to initialize the Google token client once the script is loaded - useEffect(() => { - if (isGsiScriptLoaded && window.google && GOOGLE_CLIENT_ID) { - const client = window.google.accounts.oauth2.initTokenClient({ client_id: GOOGLE_CLIENT_ID, scope: 'https://www.googleapis.com/auth/calendar.readonly', callback: '' }); - setTokenClient(client); - } - }, [isGsiScriptLoaded]); - - // Effect to subscribe to Firestore data - useEffect(() => { - if (!user || !db) return; - const basePath = `artifacts/${appId}/users/${user.uid}`; - - const projectsQuery = query(collection(db, `${basePath}/projects`)); - const unsubscribeProjects = onSnapshot(projectsQuery, (snapshot) => { - const projectsData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); - setProjects(projectsData); - if (selectedProjectId && !snapshot.docs.some(doc => doc.id === selectedProjectId)) { - setActiveView('dashboard'); - setSelectedProjectId(null); - } - }); - - const tasksQuery = query(collection(db, `${basePath}/tasks`)); - const unsubscribeTasks = onSnapshot(tasksQuery, (snapshot) => setTasks(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })))); - - const habitsQuery = query(collection(db, `${basePath}/habits`)); - const unsubscribeHabits = onSnapshot(habitsQuery, (snapshot) => setHabits(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })))); - - const goalsQuery = query(collection(db, `${basePath}/goals`)); - const unsubscribeGoals = onSnapshot(goalsQuery, (snapshot) => setGoals(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })))); - - const habitEntriesQuery = query(collection(db, `${basePath}/habit_entries`)); - const unsubscribeHabitEntries = onSnapshot(habitEntriesQuery, (snapshot) => setHabitEntries(snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })))); - - // Cleanup subscriptions on component unmount - return () => { - unsubscribeProjects(); - unsubscribeTasks(); - unsubscribeHabits(); - unsubscribeGoals(); - unsubscribeHabitEntries(); - }; - }, [user, selectedProjectId]); // Re-run if user changes - - const selectedProject = useMemo(() => projects.find(p => p.id === selectedProjectId), [selectedProjectId, projects]); - - const handleSetView = (view, projectId = null) => { - setActiveView(view); - setSelectedProjectId(projectId); - }; - - return ( -
- -
- {activeView === 'dashboard' && } - {activeView === 'project' && selectedProject && } - {activeView === 'all_tasks' && } - {activeView === 'schedule' && } - {activeView === 'habit_tracker' && } - {activeView === 'weekly_review' && } -
-
- ); -} - -// --- Top-Level App Component (Handles Conditional Logic) --- -export default function App() { - const [user, setUser] = useState(null); - const [isAuthReady, setIsAuthReady] = useState(false); - - useEffect(() => { - if (!auth) { - setIsAuthReady(true); // If firebase isn't configured, we can't check auth - return; - } - const unsubscribe = onAuthStateChanged(auth, (currentUser) => { - setUser(currentUser); - setIsAuthReady(true); - }); - return () => unsubscribe(); - }, []); - - const handleSignOut = async () => { - if(auth) { - await signOut(auth); - } - }; - - if (!isFirebaseConfigured || !GOOGLE_CLIENT_ID) { - return ; - } - - if (!isAuthReady) { - return
; - } - - return user ? : ; -} - -// --- Components --- -function Sidebar({ onViewChange, projects, goals, userId, handleSignOut }) { - const [isAddingProject, setIsAddingProject] = useState(false); - const [newProjectName, setNewProjectName] = useState(''); - const [newProjectType, setNewProjectType] = useState('Course'); - const [showGoalModal, setShowGoalModal] = useState(false); - - const handleAddProject = async (e) => { - e.preventDefault(); - if (!newProjectName.trim() || !userId || !db) return; - const project = { name: newProjectName, type: newProjectType, createdAt: new Date(), status: 'In Progress', progress: 0, goalId: null }; - try { - await addDoc(collection(db, `artifacts/${appId}/users/${userId}/projects`), project); - setNewProjectName(''); - setIsAddingProject(false); - } catch (error) { - console.error("Error adding project:", error); - } - }; - - const { standaloneProjects, goalProjects } = useMemo(() => { - const standalone = projects.filter(p => !p.goalId); - const grouped = projects.reduce((acc, project) => { - if (project.goalId) { - (acc[project.goalId] = acc[project.goalId] || []).push(project); - } - return acc; - }, {}); - return { standaloneProjects: standalone, goalProjects: grouped }; - }, [projects]); - - - return ( - <> - setShowGoalModal(false)} userId={userId} /> - - - ); -} - -function GoalDropdown({ goal, projects, onViewChange, userId }) { - const [isOpen, setIsOpen] = useState(true); - const [isEditing, setIsEditing] = useState(false); - const [editedName, setEditedName] = useState(goal.name); - const [showDeleteModal, setShowDeleteModal] = useState(false); - - const handleUpdate = async (e) => { - e.preventDefault(); - if (!editedName.trim()) return; - await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/goals`, goal.id), { name: editedName }); - setIsEditing(false); - }; - - const handleDelete = async () => { - if (!userId || !db) return; - setShowDeleteModal(false); - const batch = writeBatch(db); - - // Delete the goal - batch.delete(doc(db, `artifacts/${appId}/users/${userId}/goals`, goal.id)); - - // Delete all projects and tasks associated with the goal - for (const project of projects) { - batch.delete(doc(db, `artifacts/${appId}/users/${userId}/projects`, project.id)); - const tasksQuery = query(collection(db, `artifacts/${appId}/users/${userId}/tasks`), where("projectId", "==", project.id)); - const tasksSnapshot = await getDocs(tasksQuery); - tasksSnapshot.forEach(taskDoc => batch.delete(taskDoc.ref)); - } - await batch.commit(); - }; - - return ( -
- setShowDeleteModal(false)} onConfirm={handleDelete} title="Delete Goal" message={`Are you sure you want to delete the goal "${goal.name}" and all its projects and tasks?`} /> -
- {isEditing ? ( -
- setEditedName(e.target.value)} className="w-full bg-gray-700 border border-gray-600 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500" autoFocus /> - -
- ) : ( - - )} -
- - -
-
- {isOpen && ( -
- {projects.map(p => ( - - ))} - {projects.length === 0 &&

No projects yet.

} -
- )} -
- ); -} - -function Dashboard({ projects, tasks, onViewChange, syncedEvents }) { - const [showPlanModal, setShowPlanModal] = useState(false); - const [dailyPlan, setDailyPlan] = useState(''); - const [isPlanning, setIsPlanning] = useState(false); - const [planningError, setPlanningError] = useState(''); - - const today = new Date(); - today.setHours(0,0,0,0); - const todayKey = getLocalDateKey(new Date()); - - const upcomingTasks = tasks.filter(t => !t.completed && t.dueDate).map(t => ({...t, dueDateObj: new Date(t.dueDate)})).filter(t => t.dueDateObj >= today).sort((a, b) => a.dueDateObj - b.dueDateObj).slice(0, 5); - const overdueTasks = tasks.filter(t => !t.completed && t.dueDate && new Date(t.dueDate) < today); - - const handlePlanMyDay = async () => { - setShowPlanModal(true); - setIsPlanning(true); - setDailyPlan(''); - setPlanningError(''); - - const apiKey = process.env.REACT_APP_GEMINI_API_KEY; - if (!apiKey) { - setPlanningError("Gemini API key is not configured."); - setIsPlanning(false); - return; - } - - const todaysTasks = tasks.filter(t => !t.completed && t.dueDate === todayKey); - const todaysEvents = syncedEvents.filter(e => getLocalDateKey(e.date) === todayKey); - - let context = `Today is ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.\n\n`; - - if(overdueTasks.length > 0) { - context += "Here are the overdue tasks that need urgent attention:\n" + overdueTasks.map(t => `- ${t.title} (Priority: ${t.priority})`).join('\n') + '\n\n'; - } - - if(todaysTasks.length > 0) { - context += "Here are the tasks scheduled for today:\n" + todaysTasks.map(t => `- ${t.title} (Priority: ${t.priority})`).join('\n') + '\n\n'; - } - - if(todaysEvents.length > 0) { - context += "Here are the fixed appointments from the calendar:\n" + todaysEvents.map(e => `- ${e.title} at ${formatTime(e.date)}`).join('\n') + '\n\n'; - } else { - context += "There are no events synced from the calendar for today. You can add them from the Schedule page for a better plan.\n\n" - } - - if (overdueTasks.length === 0 && todaysTasks.length === 0 && todaysEvents.length === 0) { - setDailyPlan("You have a clear schedule today! No overdue tasks, today's tasks, or calendar events. It's a great day to get ahead on a project or start planning your next steps."); - setIsPlanning(false); - return; - } - - const prompt = `You are a productivity coach. Based on the following information, create a prioritized, motivational, and realistic schedule for the day. Group tasks logically, suggest breaks, and slot tasks around fixed appointments. Start with the most critical items. - - ${context} - - Generate a step-by-step plan.`; - - const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }] }; - - try { - const result = await callGeminiWithRetry(payload, apiKey); - - if (result.candidates && result.candidates[0].content.parts[0].text) { - setDailyPlan(result.candidates[0].content.parts[0].text); - } else { - throw new Error("Received an invalid response from the AI."); - } - } catch (e) { - console.error("AI Daily Planner Error:", e); - setPlanningError(e.message || "An unexpected error occurred."); - } finally { - setIsPlanning(false); - } - }; - - return ( - <> - setShowPlanModal(false)} - plan={dailyPlan} - isLoading={isPlanning} - error={planningError} - retry={handlePlanMyDay} - /> -
-
-

Dashboard

- -
- {overdueTasks.length > 0 && ( -
-

Overdue Tasks ({overdueTasks.length})

-
{overdueTasks.map(task => )}
-
- )} -
-
-

Upcoming Deadlines

- {upcomingTasks.length > 0 ?
{upcomingTasks.map(task => )}
:

No upcoming deadlines. Great job!

} -
-
-

Projects Overview

-
- {projects.length > 0 ? projects.map(p => ( -
onViewChange('project', p.id)}> -
{p.name}{Math.round(p.progress || 0)}%
-
-
- )) :

No projects yet. Add one from the sidebar!

} -
-
-
- -
- - ); -} - -function ProjectDetail({ project, allTasks, syncedEvents }) { - const [isAddingTask, setIsAddingTask] = useState(false); - const [newTask, setNewTask] = useState({ title: '', dueDate: '', priority: 'Medium' }); - const [editingProject, setEditingProject] = useState(null); - const [showDeleteModal, setShowDeleteModal] = useState(null); - const [isGenerating, setIsGenerating] = useState(false); - const [geminiError, setGeminiError] = useState(''); - const [showAiContextModal, setShowAiContextModal] = useState(false); - - const userId = auth.currentUser?.uid; - const tasks = useMemo(() => allTasks.filter(t => t.projectId === project.id), [allTasks, project.id]); - - // Effect to update project progress when tasks change - useEffect(() => { - if (!userId || !project || !db) return; - const completedTasks = tasks.filter(t => t.completed).length; - const totalTasks = tasks.length; - const progress = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; - if (project.progress !== progress) { - const projectRef = doc(db, `artifacts/${appId}/users/${userId}/projects`, project.id); - updateDoc(projectRef, { progress }); - } - }, [tasks, project, userId]); - - const handleAddTask = async (e) => { - e.preventDefault(); - if (!newTask.title.trim() || !userId || !db) return; - const task = { ...newTask, projectId: project.id, completed: false, createdAt: new Date() }; - await addDoc(collection(db, `artifacts/${appId}/users/${userId}/tasks`), task); - setNewTask({ title: '', dueDate: '', priority: 'Medium' }); - setIsAddingTask(false); - }; - - const handleUpdateProject = async (e) => { - e.preventDefault(); - if (!editingProject.name.trim() || !userId || !db) return; - await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/projects`, project.id), { name: editingProject.name, type: editingProject.type }); - setEditingProject(null); - }; - - const handleDeleteProject = async () => { - if (!userId || !db) return; - setShowDeleteModal(null); - // Batch delete tasks for efficiency (optional but good practice) - for (const task of tasks) { - await deleteDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, task.id)); - } - await deleteDoc(doc(db, `artifacts/${appId}/users/${userId}/projects`, project.id)); - }; - - const handleGenerateTasks = async (customContext) => { - setShowAiContextModal(false); - const apiKey = process.env.REACT_APP_GEMINI_API_KEY; - if (!apiKey) { - setGeminiError("Gemini API key is not configured. Please add REACT_APP_GEMINI_API_KEY to your .env.local file."); - return; - } - if (!userId || !db) return; - setIsGenerating(true); - setGeminiError(''); - - const projectKeywords = project.name.toLowerCase().split(' ').filter(k => k.length > 2); - const relevantEvents = syncedEvents.filter(event => { - const eventTitle = event.title.toLowerCase(); - return projectKeywords.some(keyword => eventTitle.includes(keyword)); - }); - - let calendarContext = ""; - if (relevantEvents.length > 0) { - const eventList = relevantEvents.map(e => `- "${e.title}" on ${formatDate(e.date)}`).join('\n'); - calendarContext = `For background context, here are some of my upcoming, related events from my Google Calendar:\n${eventList}\n\n`; - } - - const userContext = customContext ? `Your main instruction is: "${customContext}"\n\n` : ""; - - const prompt = `You are a project planning assistant. Your primary goal is to generate a list of actionable to-do items based on the user's main instruction. Use the other information as background context. - - **Main Instruction from User:** - ${userContext || "Break down the project into actionable steps."} - - **Background Context:** - - Project Name: "${project.name}" - - Project Type: "${project.type}" - ${calendarContext} - - Based on the user's main instruction and the background context, generate a list of 5 to 7 to-do items. For each item, provide a title and estimate its difficulty as 'High', 'Medium', or 'Low'. Do not create tasks that are identical to the calendar events, but rather tasks that lead up to them.`; - - const payload = { contents: [{ role: "user", parts: [{ text: prompt }] }], generationConfig: { responseMimeType: "application/json", responseSchema: { type: "OBJECT", properties: { tasks: { type: "ARRAY", items: { type: "OBJECT", properties: { title: { type: "STRING" }, priority: { type: "STRING", enum: ["High", "Medium", "Low"] } }, required: ["title", "priority"] } } }, required: ["tasks"] } } }; - try { - const result = await callGeminiWithRetry(payload, apiKey); - if (result.candidates && result.candidates[0].content.parts[0].text) { - const generated = JSON.parse(result.candidates[0].content.parts[0].text); - if (generated.tasks && generated.tasks.length > 0) { - for (const task of generated.tasks) { - await addDoc(collection(db, `artifacts/${appId}/users/${userId}/tasks`), { title: task.title, priority: task.priority, projectId: project.id, completed: false, createdAt: new Date(), dueDate: '' }); - } - } - } else { throw new Error("No tasks were generated."); } - } catch (error) { - console.error("Gemini API error:", error); - setGeminiError(error.message || "An error occurred."); - } finally { - setIsGenerating(false); - } - }; - - return ( -
- setShowAiContextModal(false)} onConfirm={handleGenerateTasks} /> - setShowDeleteModal(null)} onConfirm={handleDeleteProject} title="Delete Project" message={`Are you sure you want to delete "${project.name}" and all its tasks?`} /> - {editingProject ? ( -
- setEditingProject({...editingProject, name: e.target.value})} className="flex-grow bg-gray-700 border border-gray-600 rounded-md px-3 py-2 text-2xl font-bold focus:outline-none focus:ring-2 focus:ring-blue-500" /> - - - -
- ) : ( -
-

{project.name}

{project.type}
-
- - -
-
- )} -
-
Progress{Math.round(project.progress || 0)}%
-
-
-
-
-

To-Do List

-
- - -
-
- {geminiError &&
{geminiError}
} - {isAddingTask && ( -
- setNewTask({...newTask, title: e.target.value})} placeholder="Task title..." className="md:col-span-2 bg-gray-700 border border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required /> -
- setNewTask({...newTask, dueDate: e.target.value})} className="bg-gray-700 border border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" /> - -
- -
- )} -
- {tasks.length > 0 ? tasks.sort((a,b) => a.completed - b.completed).map(task => ) :

No tasks for this project yet. Try generating some with AI!

} -
-
-
- ); -} - -function AllTasksView({ tasks, projects }) { - const [filter, setFilter] = useState('All'); - const [sortBy, setSortBy] = useState('dueDate'); - const filteredAndSortedTasks = useMemo(() => { - let filtered = tasks; - if (filter === 'Active') filtered = tasks.filter(t => !t.completed); - if (filter === 'Completed') filtered = tasks.filter(t => t.completed); - return [...filtered].sort((a, b) => { - if (sortBy === 'dueDate') return (a.dueDate ? new Date(a.dueDate) : new Date('2999-12-31')) - (b.dueDate ? new Date(b.dueDate) : new Date('2999-12-31')); - if (sortBy === 'priority') { const priorityOrder = { 'High': 1, 'Medium': 2, 'Low': 3 }; return priorityOrder[a.priority] - priorityOrder[b.priority]; } - if (sortBy === 'project') { const projectA = projects.find(p => p.id === a.projectId)?.name || ''; const projectB = projects.find(p => p.id === b.projectId)?.name || ''; return projectA.localeCompare(projectB); } - return 0; - }); - }, [tasks, projects, filter, sortBy]); - return ( -
-

All Tasks

-
-
Filter by:
-
Sort by:
-
-
{filteredAndSortedTasks.length > 0 ? filteredAndSortedTasks.map(task => ) :

No tasks found.

}
-
- ); -} - -function ScheduleView({ projects, tasks, syncedEvents, setSyncedEvents, tokenClient }) { - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const handleSync = () => { - if (tokenClient) { - tokenClient.callback = async (tokenResponse) => { - if (tokenResponse && tokenResponse.access_token) { - setIsLoading(true); - setError(null); - try { - const startTime = new Date().toISOString(); - const response = await fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=${startTime}&singleEvents=true&orderBy=startTime`, { - headers: { 'Authorization': `Bearer ${tokenResponse.access_token}` }, - }); - if (!response.ok) throw new Error('Failed to fetch calendar events.'); - const data = await response.json(); - const formattedEvents = data.items - .filter(item => !(item.summary && item.summary.toLowerCase().includes('birthday'))) - .map(item => ({ - id: `gcal-${item.id}`, - title: item.summary, - date: item.start.dateTime ? new Date(item.start.dateTime) : new Date(item.start.date), - type: 'Google Calendar', - color: 'bg-red-500' - })); - setSyncedEvents(formattedEvents); - } catch (e) { - setError('Could not fetch events. Please try again.'); - console.error(e); - } finally { - setIsLoading(false); - } - } - }; - tokenClient.requestAccessToken(); - } else { - setError("Google Auth is not ready. Please wait a moment and try again."); - } - }; - - const allEvents = useMemo(() => { - const today = new Date(); - today.setHours(0, 0, 0, 0); // Set to start of today for accurate overdue comparison - - const projectEvents = projects.map(p => ({ - id: `proj-${p.id}`, - title: p.name, - date: p.deadline ? new Date(p.deadline) : null, - type: 'Project Deadline', - color: 'bg-purple-500' - })); - - const taskEvents = tasks - .filter(t => !t.completed) // 1. Filter out completed tasks - .map(t => { - const dueDate = t.dueDate ? new Date(t.dueDate) : null; - const isOverdue = dueDate && dueDate < today; // 2. Check if task is overdue - return { - id: `task-${t.id}`, - title: t.title, - date: dueDate, - type: 'Task Deadline', - color: isOverdue ? 'bg-red-700' : 'bg-green-500', // 3. Assign color based on overdue status - isOverdue: isOverdue - }; - }); - - return [...projectEvents, ...taskEvents, ...syncedEvents] - .filter(e => e.date && !isNaN(e.date)) - .sort((a, b) => a.date - b.date); - }, [projects, tasks, syncedEvents]); - - return ( -
-
-

Schedule

- -
- {error &&
{error}
} -
-

Upcoming Deadlines & Events

-
- {allEvents.length > 0 ? allEvents.map(event => ( -
-
- {event.date.toLocaleString('default', { month: 'short' })} - {event.date.getDate()} -
-
-
-

{event.title}

-

- {event.type} - {event.isOverdue && (Overdue)} - {event.type === 'Google Calendar' && ` at ${formatTime(event.date)}`} -

-
-
- )) :

No scheduled events with deadlines.

} -
-
-
- ); -} - -function TaskItem({ task, projects = [], isCompact = false }) { - const [isEditing, setIsEditing] = useState(false); - const [editTitle, setEditTitle] = useState(task.title); - const [editPriority, setEditPriority] = useState(task.priority || 'Medium'); - const [showDeleteModal, setShowDeleteModal] = useState(false); - const userId = auth.currentUser?.uid; - - const handleToggleComplete = async () => { - if (!userId || !db) return; - await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, task.id), { completed: !task.completed, completedAt: !task.completed ? new Date() : null }); - }; - - const handleUpdate = async (e) => { - e.preventDefault(); - if (!userId || !editTitle.trim() || !db) return; - await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, task.id), { - title: editTitle, - priority: editPriority - }); - setIsEditing(false); - }; - - const handleDelete = async () => { - if (!userId || !db) return; - await deleteDoc(doc(db, `artifacts/${appId}/users/${userId}/tasks`, task.id)); - setShowDeleteModal(false); - }; - - const projectName = projects.find(p => p.id === task.projectId)?.name; - const priorityColor = { High: 'text-red-400', Medium: 'text-yellow-400', Low: 'text-green-400' }; - - if (isCompact) { - return ( -
- {task.title} -
- {task.dueDate && {formatDate(new Date(task.dueDate))}} - {projectName && {projectName}} -
-
- ); - } - - return ( - <> - setShowDeleteModal(false)} onConfirm={handleDelete} title="Delete Task" message={`Are you sure you want to delete this task: "${task.title}"?`} /> -
- -
- {isEditing ? ( -
- setEditTitle(e.target.value)} className="flex-grow bg-gray-700 border border-gray-600 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500" autoFocus /> - - -
- ) : ( -

{task.title}

- )} -
- {task.dueDate &&
{formatDate(new Date(task.dueDate))}
} - {task.priority &&
{task.priority}
} - {projectName &&
{projectName}
} -
-
-
- - -
-
- - ); -} - -function HabitTrackerView({ habits, entries }) { - const [newHabitName, setNewHabitName] = useState(''); - const [isAdding, setIsAdding] = useState(false); - const [showAnalytics, setShowAnalytics] = useState(false); - const userId = auth.currentUser?.uid; - - const handleAddHabit = async (e) => { - e.preventDefault(); - if (!newHabitName.trim() || !userId || !db) return; - await addDoc(collection(db, `artifacts/${appId}/users/${userId}/habits`), { name: newHabitName, createdAt: new Date() }); - setNewHabitName(''); - setIsAdding(false); - }; - - const habitStreaks = useMemo(() => { - const streaks = {}; - habits.forEach(habit => { - const habitEntries = entries - .filter(e => e.habitId === habit.id && e.completed) - .map(e => e.date) - .sort((a, b) => new Date(b) - new Date(a)); - - let currentStreak = 0; - if (habitEntries.length > 0) { - const today = new Date(); - const todayKey = getLocalDateKey(today); - const yesterday = new Date(today); - yesterday.setDate(yesterday.getDate() - 1); - const yesterdayKey = getLocalDateKey(yesterday); - - if (habitEntries[0] === todayKey || habitEntries[0] === yesterdayKey) { - currentStreak = 1; - for (let i = 0; i < habitEntries.length - 1; i++) { - const current = new Date(habitEntries[i]); - const next = new Date(habitEntries[i+1]); - const diffDays = Math.round((current - next) / (1000 * 60 * 60 * 24)); - if (diffDays === 1) { - currentStreak++; - } else { - break; - } - } - } - } - streaks[habit.id] = currentStreak; - }); - return streaks; - }, [habits, entries]); - - return ( -
-
-

Habit Tracker

-
- - -
-
- - {isAdding && ( -
- setNewHabitName(e.target.value)} placeholder="e.g., Read for 30 minutes" className="flex-grow bg-gray-700 border border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required /> - -
- )} - - {showAnalytics && ( -
-

Habit Analytics

-
- {habits.map(habit => e.habitId === habit.id)} />)} - {habits.length === 0 &&

No habits to analyze yet.

} -
-
- )} - -
- {habits.length === 0 && !isAdding && (

No habits defined yet. Add your first one!

)} - {habits.map(habit => ( e.habitId === habit.id)} streak={habitStreaks[habit.id] || 0} />))} -
-
- ); -} - -function HabitDayCard({ habit, entries, streak }) { - const [isEditing, setIsEditing] = useState(false); - const [editedName, setEditedName] = useState(habit.name); - const [showDeleteModal, setShowDeleteModal] = useState(false); - const userId = auth.currentUser?.uid; - const todayKey = getLocalDateKey(new Date()); - const entry = entries.find(e => e.date === todayKey); - const isCompleted = entry ? entry.completed : false; - - const handleHabitToggle = async () => { - if (!userId || !db) return; - if (entry) { - await updateDoc(doc(db, `artifacts/${appId}/users/${userId}/habit_entries`, entry.id), { completed: !isCompleted }); - } else { - await addDoc(collection(db, `artifacts/${appId}/users/${userId}/habit_entries`), { habitId: habit.id, date: todayKey, completed: true }); - } - }; - - const handleUpdateHabit = async (e) => { - e.preventDefault(); - if (!editedName.trim() || !userId || !db) return; - const habitRef = doc(db, `artifacts/${appId}/users/${userId}/habits`, habit.id); - try { - await updateDoc(habitRef, { name: editedName }); - setIsEditing(false); - } catch (error) { - console.error("Error updating habit:", error); - } - }; - - const handleDeleteHabit = async () => { - if (!userId || !db) return; - setShowDeleteModal(false); - - const entriesPath = `artifacts/${appId}/users/${userId}/habit_entries`; - const q = query(collection(db, entriesPath), where("habitId", "==", habit.id)); - - try { - const querySnapshot = await getDocs(q); - const batch = writeBatch(db); - querySnapshot.forEach((doc) => { - batch.delete(doc.ref); - }); - await batch.commit(); - - const habitRef = doc(db, `artifacts/${appId}/users/${userId}/habits`, habit.id); - await deleteDoc(habitRef); - } catch (error) { - console.error("Error deleting habit and its entries:", error); - } - }; - - return ( - <> - setShowDeleteModal(false)} - onConfirm={handleDeleteHabit} - title="Delete Habit" - message={`Are you sure you want to delete the habit "${habit.name}"? All its tracked history will also be removed.`} - /> -
- {isEditing ? ( -
- setEditedName(e.target.value)} - className="w-full bg-gray-700 border border-gray-600 rounded-md px-2 py-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500" - autoFocus - /> -
- - -
-
- ) : ( -
-
- {habit.name} -
- - -
-
-
0 ? 'text-orange-400' : 'text-gray-500'}`}> - - {streak} day streak -
-
- )} - - -
- - ); -} - -function HabitCalendar({ habit, entries }) { - const today = new Date(); - const [date, setDate] = useState(new Date(today.getFullYear(), today.getMonth(), 1)); - - const goToPreviousMonth = () => { - setDate(currentDate => new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1)); - }; - - const goToNextMonth = () => { - setDate(currentDate => new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1)); - }; - - const completedDates = useMemo(() => new Set(entries.filter(e => e.completed).map(e => e.date)), [entries]); - - const daysInMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1).getDay(); - const days = Array.from({ length: daysInMonth }, (_, i) => i + 1); - const blanks = Array.from({ length: firstDayOfMonth }, (_, i) => i); - - return ( -
-
- -

- {habit.name} - {date.toLocaleString('default', { month: 'long' })} {date.getFullYear()} -

- -
-
- {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d, i) =>
{d}
)} -
-
- {blanks.map((b, i) =>
)} - {days.map(d => { - const dateKey = getLocalDateKey(new Date(date.getFullYear(), date.getMonth(), d)); - const isCompleted = completedDates.has(dateKey); - const isToday = dateKey === getLocalDateKey(new Date()); - return ( -
- {d} -
- ); - })} -
-
- ); -} - -function WeeklyReviewView({ tasks, projects, habits, entries }) { - const lastWeek = useMemo(() => { - const end = new Date(); - const start = new Date(); - start.setDate(start.getDate() - 7); - return { start, end }; - }, []); - - const completedTasks = tasks.filter(t => t.completed && t.completedAt && t.completedAt.toDate() >= lastWeek.start); - - const habitConsistency = useMemo(() => { - if (habits.length === 0) return 0; - const last7Days = Array.from({length: 7}, (_, i) => { - const d = new Date(); - d.setDate(d.getDate() - i); - return getLocalDateKey(d); - }); - - let totalPossible = habits.length * 7; - let totalCompleted = 0; - - last7Days.forEach(dateKey => { - habits.forEach(habit => { - if (entries.some(e => e.habitId === habit.id && e.date === dateKey && e.completed)) { - totalCompleted++; - } - }); - }); - return totalPossible > 0 ? Math.round((totalCompleted / totalPossible) * 100) : 0; - }, [habits, entries]); - - return ( -
-

Weekly Review

-

Summary of your activity from {formatDate(lastWeek.start)} to {formatDate(lastWeek.end)}.

- -
-
-

Tasks Completed

-

{completedTasks.length}

-
-
-

Habit Consistency

-

{habitConsistency}%

-
-
- -
-

Completed Tasks This Week

- {completedTasks.length > 0 ? ( -
- {completedTasks.map(task => )} -
- ) :

No tasks completed this week. Let's get to it!

} -
-
- ); -} - -function DailyReminder() { - const [showReminder, setShowReminder] = useState(true); - if (!showReminder) return null; - return ( -
- -
-

Daily Reminder

-

Don't forget to review your goals for the day and plan your tasks. A little planning goes a long way!

-
- -
- ); -} - -function ConfirmModal({ isOpen, onClose, onConfirm, title, message }) { - if (!isOpen) return null; - return ( -
-
-

{title}

-

{message}

-
- - -
-
-
- ); -} - -function AiContextModal({ isOpen, onClose, onConfirm }) { - const [context, setContext] = useState(''); - - const handleConfirm = () => { - onConfirm(context); - setContext(''); - }; - - if (!isOpen) return null; - - return ( -
-
-

Add Extra Context

-

Provide any additional details or instructions for the AI to generate more relevant tasks.

-