Build a unified productivity workspace in Lovable combining tasks, notes, time tracking, and a daily planner in one app. Features a Pomodoro timer that logs focus sessions to a time_entries table, cross-referenced notes and tasks, and Tabs-based navigation between modes — all backed by Supabase.
What you're building
The productivity app is structured around four modes in a Tabs component: Tasks, Notes, Timer, and Planner. Each mode is a separate React component but they share data via Supabase. A task can have associated notes (via a task_id FK on the notes table) and associated time entries (via a task_id FK on time_entries). This cross-referencing lets you see all work done on a task in one place.
The Pomodoro timer is a client-side countdown that starts at 25 minutes. When a session completes, it automatically inserts a row into time_entries (task_id, duration_minutes: 25, session_type: work|break, completed_at). The timer state lives in React state — only completed sessions are persisted. The time tracking dashboard then queries time_entries grouped by task and project to show weekly hours.
The daily planner queries tasks WHERE due_date = today ordered by priority. Users can drag tasks into a time block grid (1-hour slots from 6 AM to 10 PM) using a simple state array. Time block assignments are stored in a daily_plan table so the planner persists across page reloads.
Final result
A unified productivity workspace with task management, linked notes, Pomodoro time tracking, and a daily planner — all in one Lovable app.
Tech stack
Prerequisites
- Lovable Pro account for multi-mode app generation
- Supabase project with URL and anon key saved to Cloud tab → Secrets
- Basic familiarity with the Pomodoro technique (25-minute focus blocks)
- Optional: a list of your projects to set up in the app
Build steps
Set up the schema with tasks, notes, and time tracking
Prompt Lovable to create the complete database schema. The FK relationships between tasks, notes, and time entries are the core of the cross-reference system.
1Build a unified productivity app. Create these Supabase tables:23- projects: id, user_id, name, color (hex), description, is_archived (bool default false), created_at4- tasks: id, user_id, project_id (FK projects nullable), title, description, status (todo|in_progress|done), priority (low|medium|high|urgent), due_date (date nullable), completed_at (timestamptz nullable), created_at5- notes: id, user_id, task_id (FK tasks nullable), project_id (FK projects nullable), title, content (text), tags (text array), created_at, updated_at6- time_entries: id, user_id, task_id (FK tasks nullable), project_id (FK projects nullable), session_type (work|short_break|long_break), duration_minutes (int), started_at (timestamptz), completed_at (timestamptz)7- daily_plan: id, user_id, task_id (FK tasks), plan_date (date), time_slot (int, hour 0-23), created_at, UNIQUE(user_id, task_id, plan_date)89Create a view weekly_time_summary: user_id, project_id, project_name, week_start (date, Monday), total_minutes — groups time_entries by user+project+week.1011RLS: all tables require user_id = auth.uid() for all operations. Enable Realtime on tasks and time_entries.Pro tip: Ask Lovable to create an inbox project automatically for new users — a special project called 'Inbox' where unassigned tasks go. Use a Supabase trigger on auth.users INSERT to create this default project.
Expected result: All five tables and the weekly_time_summary view are created with RLS. The app loads with a Tabs navigation bar at the top showing Tasks, Notes, Timer, and Planner tabs.
Build the task management mode
Create the Tasks tab with a Kanban-style or list view, project filter, and quick-add functionality. Tasks are the central entity that notes and time entries reference.
1Build the Tasks tab content at src/components/productivity/TasksMode.tsx:231. Layout: sidebar with project list (Cards, each showing project color + name + task count), main area with task list42. Task list:5 - Group by status: To Do, In Progress, Done (three collapsible sections)6 - Each task row: checkbox (clicking sets status=done and completed_at=now), priority Badge (low=gray, medium=blue, high=orange, urgent=red), title, project Badge, due date (red if overdue), a Timer Button that opens the Pomodoro timer pre-linked to this task7 - Inline edit: clicking the title opens an edit Popover with all task fields83. Quick-add Input at top: type task title and press Enter to create a task in the selected project with status=todo94. Filter bar: status Select, priority Select, project Select, due date range105. Task detail Sheet (slides in from right when clicking a task): shows full description, linked notes count, total time logged, edit all fields116. Sort options: due date, priority, created date, manual drag (update sort_order column)Pro tip: Ask Lovable to add a 'Focus Mode' button that hides all UI except the current task title and the Pomodoro timer. A simple full-screen div with a blurred background works well for this.
Expected result: The Tasks tab shows tasks grouped by status. Checking a task marks it done with a strikethrough. The quick-add Input creates a new task instantly.
Build the Pomodoro timer with session logging
Create the Timer tab with a circular countdown display, interval controls, and automatic session logging to time_entries. The timer state lives in React but completed sessions are persisted.
1// src/components/productivity/PomodoroTimer.tsx2import { useState, useEffect, useCallback } from 'react'3import { Button } from '@/components/ui/button'4import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'5import { supabase } from '@/integrations/supabase/client'6import { useAuth } from '@/hooks/useAuth'78const SESSIONS = {9 work: 25 * 60,10 short_break: 5 * 60,11 long_break: 15 * 60,12} as const1314type SessionType = keyof typeof SESSIONS1516interface Props {17 defaultTaskId?: string18}1920export function PomodoroTimer({ defaultTaskId }: Props) {21 const { user } = useAuth()22 const [sessionType, setSessionType] = useState<SessionType>('work')23 const [timeLeft, setTimeLeft] = useState(SESSIONS.work)24 const [isRunning, setIsRunning] = useState(false)25 const [startedAt, setStartedAt] = useState<string | null>(null)26 const [selectedTaskId, setSelectedTaskId] = useState(defaultTaskId ?? null)2728 useEffect(() => {29 setTimeLeft(SESSIONS[sessionType])30 setIsRunning(false)31 }, [sessionType])3233 const logSession = useCallback(async () => {34 if (!user || !startedAt) return35 await supabase.from('time_entries').insert({36 user_id: user.id,37 task_id: selectedTaskId,38 session_type: sessionType,39 duration_minutes: SESSIONS[sessionType] / 60,40 started_at: startedAt,41 completed_at: new Date().toISOString(),42 })43 }, [user, startedAt, selectedTaskId, sessionType])4445 useEffect(() => {46 if (!isRunning) return47 if (timeLeft === 0) {48 setIsRunning(false)49 logSession()50 return51 }52 const interval = setInterval(() => setTimeLeft((t) => t - 1), 1000)53 return () => clearInterval(interval)54 }, [isRunning, timeLeft, logSession])5556 const minutes = Math.floor(timeLeft / 60).toString().padStart(2, '0')57 const seconds = (timeLeft % 60).toString().padStart(2, '0')58 const progress = ((SESSIONS[sessionType] - timeLeft) / SESSIONS[sessionType]) * 1005960 return (61 <div className="flex flex-col items-center gap-6 p-8">62 <div className="relative w-48 h-48">63 <svg className="w-full h-full -rotate-90" viewBox="0 0 100 100">64 <circle cx="50" cy="50" r="45" fill="none" stroke="currentColor" strokeWidth="4" className="text-muted" />65 <circle cx="50" cy="50" r="45" fill="none" stroke="currentColor" strokeWidth="4"66 className="text-primary transition-all duration-1000"67 strokeDasharray={`${2 * Math.PI * 45}`}68 strokeDashoffset={`${2 * Math.PI * 45 * (1 - progress / 100)}`} />69 </svg>70 <div className="absolute inset-0 flex items-center justify-center">71 <span className="text-4xl font-mono font-bold">{minutes}:{seconds}</span>72 </div>73 </div>74 <div className="flex gap-2">75 {(['work', 'short_break', 'long_break'] as SessionType[]).map((s) => (76 <Button key={s} variant={sessionType === s ? 'default' : 'outline'} size="sm" onClick={() => setSessionType(s)}>77 {s === 'work' ? 'Focus' : s === 'short_break' ? 'Short Break' : 'Long Break'}78 </Button>79 ))}80 </div>81 <Button size="lg" onClick={() => { if (!isRunning) setStartedAt(new Date().toISOString()); setIsRunning(!isRunning) }}>82 {isRunning ? 'Pause' : timeLeft === SESSIONS[sessionType] ? 'Start' : 'Resume'}83 </Button>84 </div>85 )86}Expected result: The Timer tab shows the circular countdown. Starting and completing a 25-minute work session inserts a row into time_entries. The session type buttons switch between Focus, Short Break, and Long Break.
Build the daily planner view
Create the Planner tab showing today's tasks in a time block grid. Users drag tasks into time slots and the assignments persist in the daily_plan table.
1Build the Planner tab at src/components/productivity/DailyPlanner.tsx:231. Header: today's date in large text, day navigation (< and > arrows to change the plan_date)42. Left column: 'Unscheduled Today' — fetches tasks WHERE due_date = plan_date AND task not in daily_plan for this date. Shows as a scrollable list of draggable task chips.53. Right area: time grid from 6 AM to 10 PM in 1-hour slots6 - Each slot is a drop zone. Dropping a task chip on a slot calls supabase.from('daily_plan').upsert({ user_id, task_id, plan_date, time_slot: hour })7 - Slots that have tasks show the task title as a colored chip (using project color)8 - Clicking a chip in the grid removes it from the slot (deletes from daily_plan)94. Drag and drop: use HTML5 drag and drop API (draggable attribute + onDragStart/onDrop handlers) — no external library needed105. 'Schedule remaining' button: assigns all unscheduled tasks to the next available slot starting from current hour116. Show current time indicator: a red horizontal line on the current hour slotExpected result: The Planner tab shows a time grid. Dragging a task from the unscheduled list to a time slot creates a daily_plan entry. Refreshing the page preserves all scheduled tasks.
Add the notes mode with task cross-references
Build the Notes tab with a note editor, task linking, tag filtering, and full-text search using Supabase's tsvector search on the notes content.
1Build the Notes tab at src/components/productivity/NotesMode.tsx:231. Three-panel layout (desktop): note list (left sidebar), note editor (center), linked items (right panel)42. Note list:5 - Search Input that filters via Supabase full-text search: .textSearch('content', searchTerm)6 - Tag filter pills using Badge components7 - Each list item: note title (first line of content), updated_at relative time, linked task count Badge8 - 'New Note' Button at top creates a blank note93. Note editor:10 - Title Input (auto-saves on blur)11 - Textarea for content (auto-saves 2 seconds after last keystroke using debounce)12 - Tags Input: type a tag and press Enter to add, click x on Badge to remove134. Linked items panel:14 - 'Link to Task' Combobox: search tasks and select one to link (sets task_id on this note)15 - 'Link to Project' Select: sets project_id on this note16 - Shows linked task title with a link to open the task in the Tasks tab175. Auto-save indicator: shows 'Saving...' then 'Saved' in the top-right of the editorExpected result: The Notes tab shows a note list, editor, and linked items panel. Creating a note, typing content, and linking it to a task all work. Search filters notes by content in real time.
Complete code
1import { useQuery } from '@tanstack/react-query'2import { supabase } from '@/integrations/supabase/client'3import { format, startOfWeek } from 'date-fns'45export interface WeeklyTimeSummary {6 project_id: string | null7 project_name: string8 week_start: string9 total_minutes: number10 total_hours: number11}1213export function useWeeklyTime(userId: string, weeksBack = 8) {14 return useQuery({15 queryKey: ['weekly-time', userId, weeksBack],16 queryFn: async () => {17 const since = format(18 startOfWeek(new Date(Date.now() - weeksBack * 7 * 86400000), { weekStartsOn: 1 }),19 'yyyy-MM-dd'20 )21 const { data, error } = await supabase22 .from('weekly_time_summary')23 .select('project_id, project_name, week_start, total_minutes')24 .eq('user_id', userId)25 .gte('week_start', since)26 .order('week_start', { ascending: true })2728 if (error) throw error2930 return (data ?? []).map((row) => ({31 ...row,32 total_hours: Math.round((row.total_minutes / 60) * 10) / 10,33 week_label: format(new Date(row.week_start), 'MMM d'),34 }))35 },36 staleTime: 60_000,37 })38}Customization ideas
Weekly review email
Add a Supabase Edge Function scheduled every Sunday evening that summarizes the week: tasks completed, total hours logged per project, and notes created. Send the summary via Resend to the user's email. Store the last_sent_at so you can skip users who haven't been active.
Habit tracker integration
Add a habits table with daily check-ins. Show a habit streak grid (GitHub-style) using a Recharts calendar heatmap. Link habits to projects so completing a habit can auto-log a time entry. This extends the productivity app into daily routine tracking.
Goal setting with milestones
Add a goals table with target dates and a milestones subtable. Link tasks to milestones and milestones to goals. Show a Recharts Gantt-style chart using BarChart with date-based x-axis showing goal timelines. Clicking a goal opens all its milestones and tasks.
Shared workspaces for teams
Add a workspaces table and invite system. Members can share projects, assign tasks to specific users, and see a team time tracking dashboard. Update all RLS policies to allow workspace members to access shared projects. Keep personal notes private regardless of workspace membership.
Keyboard shortcut system
Add a global keyboard shortcut handler using a custom useKeyboardShortcuts hook. Map shortcuts: N for new task, P for new note, T to start timer, D for daily planner. Show a shortcut reference in a Sheet opened with '?'. This dramatically speeds up power users.
Common pitfalls
Pitfall: Storing timer state in Supabase on every tick
How to avoid: Keep the timer countdown in React state only. Only write to the database once when a session completes (timeLeft reaches 0) or when the user manually stops. The time_entries INSERT happens at completion, not during the countdown.
Pitfall: Not enabling Realtime on the tasks table
How to avoid: Enable Supabase Realtime on the tasks and daily_plan tables. Subscribe to changes using supabase.channel('tasks').on('postgres_changes', ...) in the relevant components. Lovable sets this up automatically if you ask for real-time updates in your prompt.
Pitfall: Linking notes only to tasks and forgetting project context
How to avoid: Allow notes to have either task_id, project_id, or both. The schema in Step 1 includes both FKs as nullable. The UI should allow creating notes with just a project link, just a task link, or both.
Pitfall: Not debouncing note auto-save
How to avoid: Debounce the auto-save with a 2-second delay. Only save after the user stops typing for 2 seconds. Use the useDebounce hook from usehooks-ts or implement a simple setTimeout-based debounce in the note editor component.
Best practices
- Use optimistic updates for task completion toggling. Set the task status locally before the Supabase UPDATE completes so the UI responds instantly. Revert on error.
- Keep the Pomodoro timer's running state in React, not in Supabase. Only persist completed sessions. This avoids unnecessary database writes and keeps the timer responsive.
- Add a soft delete (is_deleted bool) to tasks instead of hard deleting. Show a trash/archive view. Users frequently delete tasks by mistake and want to recover them.
- Use the time_entries table as an immutable append-only log. Never update or delete time entries — only insert. This creates a reliable audit trail of all work sessions.
- Index the due_date column on the tasks table since the daily planner queries WHERE due_date = today on every page load. Add: CREATE INDEX idx_tasks_user_due ON tasks(user_id, due_date).
- Show empty states that guide users toward action. An empty task list should say 'No tasks yet — press N to add your first task' rather than showing nothing.
AI prompts to try
Copy these prompts to build this project faster.
I'm building a productivity app with a Pomodoro timer that logs sessions to a Supabase time_entries table. I want to show a weekly productivity report. Help me write a PostgreSQL query that returns total focus minutes per day for the last 30 days, grouped by day. Fill in days with no entries as 0 using generate_series. Return: day (date), focus_minutes (int), sessions_count (int). I'll use this in a Recharts AreaChart.
Add a 'Focus Mode' to the productivity app. When the user clicks 'Enter Focus Mode', hide all navigation and sidebars. Show only: the current task title in large text, a progress badge showing task position in today's plan, and the Pomodoro timer. Add a pulsing background animation that changes from blue (working) to green (break). Add an 'Exit Focus Mode' button in the top-right corner. Focus Mode should be a full-screen overlay that uses a React portal so it covers all other UI.
In Supabase, create a PostgreSQL function get_productivity_score(p_user_id uuid, p_date date) that calculates a daily productivity score from 0 to 100. Score formula: (tasks_completed * 20) + (focus_minutes / 3) capped at 100. Return: date, tasks_completed (int), focus_minutes (int), score (int). Use this function in a Recharts LineChart to show the user's productivity score over the last 30 days.
Frequently asked questions
Can I use this on mobile to log Pomodoro sessions throughout the day?
Yes. Lovable apps are responsive by default and work in mobile browsers. The Pomodoro timer uses React state with setInterval, which works in mobile browsers. The circular SVG progress indicator renders correctly on all screen sizes. For a native mobile experience, you'd need to export the code and build a React Native version separately.
What happens to my timer if I accidentally close the browser tab?
Timer state is in React memory only and is lost on page close. This is by design — only completed sessions are logged, not interrupted ones. If you need persistence across tab closes, ask Lovable to add localStorage.setItem calls to save the timer start time and session type. On component mount, check localStorage and resume the timer from where it left off.
Can notes have rich text formatting like bold and bullet points?
The base build uses a plain Textarea for notes. To add rich text formatting, ask Lovable to integrate a Tiptap editor — it's a React-based rich text editor that generates clean HTML or Markdown. Store the formatted content as a string in the notes.content column. Tiptap is compatible with Lovable's Vite+React setup.
How do I set up recurring daily tasks that automatically appear in my planner?
Add a recurring_tasks table similar to the finance tracker's recurring_transactions. A Supabase Edge Function scheduled with pg_cron runs each morning and creates today's task instances from recurring templates. Link the task generator to your daily planner so recurring tasks appear automatically in the unscheduled column.
Is there a way to sync tasks with Google Calendar or Notion?
Not out of the box, but you can add integrations via Edge Functions. For Google Calendar, create an OAuth connection that pushes tasks with due dates as calendar events when the due date is set. For Notion, use the Notion API from an Edge Function to pull pages as notes. Ask Lovable to add a 'Connect Integrations' settings page after completing the base build.
How do I share my daily plan with a colleague or manager?
Add a 'Share Daily Plan' button that generates a public read-only URL. Store a share_token (UUID) in the daily_plan table per date per user. Create a public Supabase view that allows anonymous SELECT on daily_plan rows where share_token matches. The public URL shows a read-only version of the planner for that day.
Can multiple people use this as a shared workspace?
The base build is single-user. To add team functionality, you'd need to add a workspaces table, invite system, and update all RLS policies. This is the 'Shared workspaces' customization idea in this guide — it's a significant extension. Prompt Lovable to add the workspace feature as a separate step after the base build is working.
How do I export my time tracking data to a spreadsheet?
Ask Lovable to add a CSV export button on the time tracking page. Query all time_entries for the selected date range, format them as CSV rows (date, project, task, duration, session type), and trigger a browser download using a Blob URL. This is a pure frontend feature that requires no backend changes.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation