Skip to content

Commit 33395ff

Browse files
authored
Merge pull request #9 from uncazzy/dev
release: v0.6.0 - Project filtering on Dashboard
2 parents c2a7dd0 + e6e9e0d commit 33395ff

File tree

4 files changed

+179
-63
lines changed

4 files changed

+179
-63
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Todoist Dashboard
22

33
![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
4-
![Next.js](https://img.shields.io/badge/Next.js-13-black)
4+
![Next.js](https://img.shields.io/badge/Next.js-14-black)
55

66
A powerful dashboard for Todoist users that provides deep insights into task management and productivity patterns. Visualize your most productive days and times, track task completion trends over time, and gain insights into your focus areas. Built with Next.js, React, and Tailwind CSS.
77

components/Dashboard.tsx

Lines changed: 121 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { useSession } from 'next-auth/react';
33
import { Tooltip } from 'react-tooltip';
44
import ActiveTasksByProject from './ActiveTasksByProject';
@@ -20,9 +20,11 @@ import LoadingIndicator from './shared/LoadingIndicator';
2020
import QuickStats from './QuickStats/QuickStats';
2121
import { useDashboardData } from '../hooks/useDashboardData';
2222
import Layout from './layout/Layout';
23+
import ProjectPicker from './ProjectPicker';
2324

2425
export default function Dashboard(): JSX.Element {
2526
const { status } = useSession();
27+
const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);
2628
const {
2729
data,
2830
isLoading,
@@ -98,15 +100,26 @@ export default function Dashboard(): JSX.Element {
98100
<div className="min-h-screen rounded-lg bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-white">
99101
<div className="container mx-auto p-6">
100102
<header className="mb-8">
101-
<h1 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500 mb-2">
102-
Todoist Dashboard
103-
</h1>
104-
<p className="text-gray-400">
105-
<span>
106-
<SiTodoist className="inline text-red-500 ml-1 mr-2" />
107-
</span>
108-
Your productivity at a glance
109-
</p>
103+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4">
104+
<div>
105+
<h1 className="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500 mb-2">
106+
Todoist Dashboard
107+
</h1>
108+
<p className="text-gray-400">
109+
<span>
110+
<SiTodoist className="inline text-red-500 ml-1 mr-2" />
111+
</span>
112+
Your productivity at a glance
113+
</p>
114+
</div>
115+
{data?.projectData && (
116+
<ProjectPicker
117+
projects={data.projectData}
118+
selectedProjectIds={selectedProjectIds}
119+
onProjectSelect={setSelectedProjectIds}
120+
/>
121+
)}
122+
</div>
110123
</header>
111124

112125
{/* Always show loading indicator */}
@@ -121,9 +134,13 @@ export default function Dashboard(): JSX.Element {
121134

122135
{/* Quick Stats */}
123136
<QuickStats
124-
activeTasks={data?.activeTasks || []}
125-
projectCount={data?.projectData?.length || 0}
126-
totalCompletedTasks={data?.totalCompletedTasks || 0}
137+
activeTasks={selectedProjectIds.length > 0
138+
? data?.activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
139+
: data?.activeTasks || []}
140+
projectCount={selectedProjectIds.length || data?.projectData?.length || 0}
141+
totalCompletedTasks={selectedProjectIds.length > 0
142+
? data?.allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id))?.length || 0
143+
: data?.totalCompletedTasks || 0}
127144
karma={data?.karma || 0}
128145
karmaTrend={data?.karmaTrend || 'none'}
129146
karmaRising={data?.karmaRising || false}
@@ -134,7 +151,15 @@ export default function Dashboard(): JSX.Element {
134151
{/* Insights Section */}
135152
<div className="lg:col-span-3">
136153
<Insights
137-
allData={data}
154+
allData={{
155+
...data,
156+
activeTasks: selectedProjectIds.length > 0
157+
? data?.activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
158+
: data?.activeTasks || [],
159+
allCompletedTasks: selectedProjectIds.length > 0
160+
? data?.allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
161+
: data?.allCompletedTasks || []
162+
}}
138163
isLoading={isLoading}
139164
fullyLoaded={!needsFullData}
140165
/>
@@ -145,7 +170,14 @@ export default function Dashboard(): JSX.Element {
145170
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
146171
Recently Completed <span className="text-white"></span>
147172
</h2>
148-
<RecentlyCompletedList allData={data} />
173+
<RecentlyCompletedList
174+
allData={{
175+
...data,
176+
allCompletedTasks: selectedProjectIds.length > 0
177+
? data?.allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
178+
: data?.allCompletedTasks || []
179+
}}
180+
/>
149181
</div>
150182

151183
{/* Neglected Tasks Section */}
@@ -154,7 +186,12 @@ export default function Dashboard(): JSX.Element {
154186
Neglected Tasks
155187
<QuestionMark content="Tasks that have been on your list the longest without being completed. Consider reviewing these tasks to either complete them, reschedule, or remove if no longer relevant." />
156188
</h2>
157-
<NeglectedTasks activeTasks={activeTasks} projectData={projectData} />
189+
<NeglectedTasks
190+
activeTasks={selectedProjectIds.length > 0
191+
? activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
192+
: activeTasks || []}
193+
projectData={projectData}
194+
/>
158195
</div>
159196

160197
{/* Recurring Tasks Section */}
@@ -170,34 +207,45 @@ export default function Dashboard(): JSX.Element {
170207
</div>
171208
) : (
172209
<RecurringTasksPreview
173-
activeTasks={activeTasks}
174-
allCompletedTasks={allCompletedTasks}
210+
activeTasks={selectedProjectIds.length > 0
211+
? activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
212+
: activeTasks || []}
213+
allCompletedTasks={selectedProjectIds.length > 0
214+
? allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
215+
: allCompletedTasks || []}
175216
/>
176217
)}
177218
</div>
178219

179220
{/* Task Management Section */}
180221
<div className="lg:col-span-3 grid grid-cols-1 sm:grid-cols-2 gap-6">
181222
<div
182-
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''
183-
}`}
223+
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}
184224
>
185225
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
186226
Tasks by Priority
187227
</h2>
188-
<TaskPriority activeTasks={data?.activeTasks || []} loading={needsFullData} />
228+
<TaskPriority
229+
activeTasks={selectedProjectIds.length > 0
230+
? data?.activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
231+
: data?.activeTasks || []}
232+
loading={needsFullData}
233+
/>
189234
</div>
190235

191236
<div
192-
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''
193-
}`}
237+
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}
194238
>
195239
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
196240
Active Tasks by Project
197241
</h2>
198242
<ActiveTasksByProject
199-
projectData={data?.projectData || []}
200-
activeTasks={data?.activeTasks || []}
243+
projectData={selectedProjectIds.length > 0
244+
? projectData?.filter(project => selectedProjectIds.includes(project.id)) || []
245+
: projectData || []}
246+
activeTasks={selectedProjectIds.length > 0
247+
? activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []
248+
: activeTasks || []}
201249
loading={needsFullData}
202250
/>
203251
</div>
@@ -206,52 +254,77 @@ export default function Dashboard(): JSX.Element {
206254
{/* Completed Tasks over time and by project */}
207255
<div className="lg:col-span-3 grid grid-cols-1 sm:grid-cols-2 gap-6">
208256
<div
209-
className={`bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''
210-
}`}
257+
className={`bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}
211258
>
212259
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
213260
Completed Tasks Over Time
214261
</h2>
215-
<CompletedTasksOverTime allData={allCompletedTasks} loading={isLoading} />
262+
<CompletedTasksOverTime
263+
allData={selectedProjectIds.length > 0
264+
? allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
265+
: allCompletedTasks || []}
266+
loading={isLoading}
267+
/>
216268
</div>
217269

218270
<div className={`flex flex-col bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}>
219271
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
220272
Completed Tasks by Project
221273
</h2>
222274
<CompletedTasksByProject
223-
projectData={projectData.map((project) => ({
224-
...project,
225-
completedTasksCount: allCompletedTasks.filter(
226-
(task) => task.project_id === project.id
227-
).length,
228-
}))}
275+
projectData={selectedProjectIds.length > 0
276+
? projectData
277+
.filter(project => selectedProjectIds.includes(project.id))
278+
.map(project => ({
279+
...project,
280+
completedTasksCount: allCompletedTasks.filter(
281+
task => task.project_id === project.id
282+
).length,
283+
})) || []
284+
: projectData.map((project) => ({
285+
...project,
286+
completedTasksCount: allCompletedTasks.filter(
287+
(task) => task.project_id === project.id
288+
).length,
289+
})) || []}
229290
loading={needsFullData}
230291
/>
231292
</div>
232293
</div>
233294

234295
<div className="lg:col-span-3 grid grid-cols-1 sm:grid-cols-2 gap-6">
235296
<div
236-
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''
237-
}`}
297+
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}
238298
>
239299
<div className="flex items-center gap-2">
240300
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
241301
Daily Streak
242302
</h2>
243303
</div>
244-
<CompletionStreak allData={data} />
304+
<CompletionStreak
305+
allData={{
306+
allCompletedTasks: selectedProjectIds.length > 0
307+
? data?.allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
308+
: data?.allCompletedTasks || []
309+
}}
310+
/>
245311
</div>
246312

247313
<div
248-
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''
249-
}`}
314+
className={`lg:col-span-1 bg-gray-800 rounded-xl p-4 sm:p-6 shadow-lg ${needsFullData ? 'opacity-50' : ''}`}
250315
>
251316
<h2 className="text-lg sm:text-xl font-semibold mb-4 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-500">
252317
Daily Activity Pattern
253318
</h2>
254-
<CompletedByTimeOfDay allData={data} loading={needsFullData} />
319+
<CompletedByTimeOfDay
320+
allData={{
321+
...data,
322+
allCompletedTasks: selectedProjectIds.length > 0
323+
? data?.allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || []
324+
: data?.allCompletedTasks || []
325+
}}
326+
loading={needsFullData}
327+
/>
255328
</div>
256329
</div>
257330
</div>
@@ -273,7 +346,14 @@ export default function Dashboard(): JSX.Element {
273346
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
274347
</div>
275348
) : (
276-
<TaskWordCloud tasks={[...activeTasks, ...allCompletedTasks]} />
349+
<TaskWordCloud
350+
tasks={selectedProjectIds.length > 0
351+
? [
352+
...(activeTasks?.filter(task => selectedProjectIds.includes(task.projectId)) || []),
353+
...(allCompletedTasks?.filter(task => selectedProjectIds.includes(task.project_id)) || [])
354+
]
355+
: [...(activeTasks || []), ...(allCompletedTasks || [])]}
356+
/>
277357
)}
278358
</div>
279359
</div>

0 commit comments

Comments
 (0)