From fe573a5b3e3f7e673c8a05399a8784bebe58f9ea Mon Sep 17 00:00:00 2001 From: trueberryless Date: Sat, 6 Jul 2024 18:24:11 +0200 Subject: [PATCH] task view version-patch --- src/app/test_data.json | 682 ++++++++++++++---- src/components/navbar.tsx | 50 +- src/components/projects/columns.tsx | 30 +- .../data-table-column-header.tsx | 2 +- .../data-table-faceted-filter.tsx | 10 +- .../data-table-pagination.tsx | 4 +- .../data-table-row-actions.tsx | 4 +- .../{ui => projects}/data-table-toolbar.tsx | 0 .../data-table-view-options.tsx | 0 .../{ui => projects}/data-table.tsx | 0 src/components/tasks/columns.tsx | 444 ++++++++++++ .../tasks/data-table-column-header.tsx | 65 ++ .../data-table-faceted-filter-simple.tsx | 137 ++++ .../tasks/data-table-faceted-filter.tsx | 144 ++++ .../tasks/data-table-pagination.tsx | 87 +++ .../tasks/data-table-row-actions.tsx | 41 ++ src/components/tasks/data-table-toolbar.tsx | 74 ++ .../tasks/data-table-view-options.tsx | 52 ++ src/components/tasks/data-table.tsx | 140 ++++ src/models/index.ts | 3 +- src/models/project.ts | 2 +- src/models/task.ts | 69 +- src/models/timespan.ts | 4 + src/pages/projects.tsx | 2 +- src/pages/{project => projects}/[id].tsx | 0 src/pages/{project => projects}/[id]/edit.tsx | 0 src/pages/tasks.tsx | 149 ++++ 27 files changed, 2038 insertions(+), 157 deletions(-) rename src/components/{ui => projects}/data-table-column-header.tsx (98%) rename src/components/{ui => projects}/data-table-faceted-filter.tsx (96%) rename src/components/{ui => projects}/data-table-pagination.tsx (98%) rename src/components/{ui => projects}/data-table-row-actions.tsx (91%) rename src/components/{ui => projects}/data-table-toolbar.tsx (100%) rename src/components/{ui => projects}/data-table-view-options.tsx (100%) rename src/components/{ui => projects}/data-table.tsx (100%) create mode 100644 src/components/tasks/columns.tsx create mode 100644 src/components/tasks/data-table-column-header.tsx create mode 100644 src/components/tasks/data-table-faceted-filter-simple.tsx create mode 100644 src/components/tasks/data-table-faceted-filter.tsx create mode 100644 src/components/tasks/data-table-pagination.tsx create mode 100644 src/components/tasks/data-table-row-actions.tsx create mode 100644 src/components/tasks/data-table-toolbar.tsx create mode 100644 src/components/tasks/data-table-view-options.tsx create mode 100644 src/components/tasks/data-table.tsx create mode 100644 src/models/timespan.ts rename src/pages/{project => projects}/[id].tsx (100%) rename src/pages/{project => projects}/[id]/edit.tsx (100%) create mode 100644 src/pages/tasks.tsx diff --git a/src/app/test_data.json b/src/app/test_data.json index 9b3dd6c..4c3f53e 100644 --- a/src/app/test_data.json +++ b/src/app/test_data.json @@ -16,15 +16,33 @@ { "id": "1720012345678", "name": "Task 1", - "startTime": "2024-07-02T09:00:00.000Z", - "endTime": "2024-07-02T10:00:00.000Z", - "description": "Complete the first task." + "description": "Complete the first task.", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ] }, { "id": "1720012345679", "name": "Task 2", - "startTime": "2024-07-02T10:30:00.000Z", - "endTime": "2024-07-02T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -37,20 +55,38 @@ "lastUpdatedAt": "2024-06-30T12:00:00.000Z", "deletedAt": "2024-06-30T12:00:00.000Z", "status": "in progress", - "priority": "medium", + "priority": "low", "tasks": [ { "id": "1720012345680", "name": "Task 1", - "startTime": "2024-07-01T09:00:00.000Z", - "endTime": "2024-07-01T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { "id": "1720012345681", "name": "Task 2", - "startTime": "2024-07-01T10:30:00.000Z", - "endTime": "2024-07-01T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -68,15 +104,33 @@ { "id": "1720012345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { "id": "1720012345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -92,17 +146,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720032345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012365683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -118,17 +190,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012344682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012345283", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -144,17 +234,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1723012345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012355683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -170,17 +278,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012342682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012345183", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -196,17 +322,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1721012345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012345693", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -222,17 +366,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720022345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012385683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -248,17 +410,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012347682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012945683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -274,17 +454,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012341682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012445683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -300,17 +498,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1770012345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012325683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -326,17 +542,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012345782", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012345183", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -352,17 +586,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720017345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720016345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -378,17 +630,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012345782", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720312345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -404,17 +674,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012545682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1790012345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -430,17 +718,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012345672", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012345783", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -456,17 +762,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720014345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720032345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -482,17 +806,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720082345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012355683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -508,17 +850,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012345082", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720013345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -534,17 +894,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720012349682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1724012345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "high", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -560,17 +938,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1729012345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720010345683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] @@ -586,17 +982,35 @@ "priority": "low", "tasks": [ { - "id": "1720012345682", + "id": "1720032345682", "name": "Task 1", - "startTime": "2024-07-03T09:00:00.000Z", - "endTime": "2024-07-03T10:00:00.000Z", + "priority": "medium", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T10:30:00.000Z" + }, + { + "start": "2024-07-02T11:00:00.000Z", + "end": "2024-07-02T12:30:00.000Z" + } + ], "description": "Complete the first task." }, { - "id": "1720012345683", + "id": "1720012305683", "name": "Task 2", - "startTime": "2024-07-03T10:30:00.000Z", - "endTime": "2024-07-03T11:30:00.000Z", + "priority": "low", + "timeSpans": [ + { + "start": "2024-07-01T08:30:00.000Z", + "end": "2024-07-01T11:30:00.000Z" + }, + { + "start": "2024-07-02T12:00:00.000Z", + "end": "2024-07-02T14:30:00.000Z" + } + ], "description": "Complete the second task." } ] diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index f0ec6c9..7ad1241 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -25,6 +25,7 @@ import { useUser } from "./UserContext"; export default function Navbar() { const { user, setUser } = useUser(); const router = useRouter(); + const [isSheetOpen, setIsSheetOpen] = useState(false); useEffect(() => { const data = loadData(); @@ -58,13 +59,23 @@ export default function Navbar() { Projects + + Tasks + - + - + View - + Edit diff --git a/src/components/ui/data-table-toolbar.tsx b/src/components/projects/data-table-toolbar.tsx similarity index 100% rename from src/components/ui/data-table-toolbar.tsx rename to src/components/projects/data-table-toolbar.tsx diff --git a/src/components/ui/data-table-view-options.tsx b/src/components/projects/data-table-view-options.tsx similarity index 100% rename from src/components/ui/data-table-view-options.tsx rename to src/components/projects/data-table-view-options.tsx diff --git a/src/components/ui/data-table.tsx b/src/components/projects/data-table.tsx similarity index 100% rename from src/components/ui/data-table.tsx rename to src/components/projects/data-table.tsx diff --git a/src/components/tasks/columns.tsx b/src/components/tasks/columns.tsx new file mode 100644 index 0000000..e3c8f98 --- /dev/null +++ b/src/components/tasks/columns.tsx @@ -0,0 +1,444 @@ +"use client"; + +import { Project, Task } from "@/models"; +import { ColumnDef } from "@tanstack/react-table"; +import { DataTableColumnHeader } from "../tasks/data-table-column-header"; +import { priorities, statuses } from "@/models/project"; +import { Badge } from "../ui/badge"; +import { format } from "date-fns"; +import { DataTableRowActions } from "../tasks/data-table-row-actions"; +import { calculateTotalTime, msToTime } from "@/models/task"; +import { useEffect } from "react"; +import { loadData } from "@/utils/load"; + +export const columnsXl: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("name")} + +
+ ); + }, + }, + { + accessorKey: "description", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("description")} + +
+ ); + }, + }, + { + accessorKey: "projectName", + header: ({ column }) => , + cell: ({ row }) => { + const user = loadData(); + const projects = user?.projects || []; + + // Find the project that contains the current task + const project = projects.find((project) => { + return project.tasks.some((task: Task) => task.id === row.original.id); + }); + + return ( +
+ {/* Display project name if found */} + + {project ? project.name : "Project Not Found"} + +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + accessorKey: "timeSpent", + header: ({ column }) => , + cell: ({ row }) => { + const task: Task = row.original; // Assuming row.original contains the Task object + const totalTime = calculateTotalTime(task); + const formattedTime = msToTime(totalTime); + + return
{formattedTime}
; + }, + }, + { + accessorKey: "timeSpansCount", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const task: Task = row.original; + + return ( +
+ {task.timeSpans.length} Slot{task.timeSpans.length > 1 ? "s" : ""} +
+ ); + }, + }, + { + accessorKey: "priority", + header: ({ column }) => , + cell: ({ row }) => { + const priority = priorities.find( + (priority) => priority.value === row.getValue("priority") + ); + + if (!priority) { + return null; + } + + return ( +
+ {priority.icon && ( + + )} + {priority.label} +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + id: "actions", + cell: ({ row }) => ( +
+ +
+ ), + }, +]; + +export const columnsLg: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("name")} + +
+ ); + }, + }, + { + accessorKey: "description", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("description")} + +
+ ); + }, + }, + { + accessorKey: "projectName", + header: ({ column }) => , + cell: ({ row }) => { + const user = loadData(); + const projects = user?.projects || []; + + // Find the project that contains the current task + const project = projects.find((project) => { + return project.tasks.some((task: Task) => task.id === row.original.id); + }); + + return ( +
+ {/* Display project name if found */} + + {project ? project.name : "Project Not Found"} + +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + accessorKey: "timeSpent", + header: ({ column }) => , + cell: ({ row }) => { + const task: Task = row.original; // Assuming row.original contains the Task object + const totalTime = calculateTotalTime(task); + const formattedTime = msToTime(totalTime); + + return
{formattedTime}
; + }, + }, + { + accessorKey: "priority", + header: ({ column }) => , + cell: ({ row }) => { + const priority = priorities.find( + (priority) => priority.value === row.getValue("priority") + ); + + if (!priority) { + return null; + } + + return ( +
+ {priority.icon && ( + + )} + {priority.label} +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + id: "actions", + cell: ({ row }) => ( +
+ +
+ ), + }, +]; + +export const columnsMd: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("name")} + +
+ ); + }, + }, + { + accessorKey: "projectName", + header: ({ column }) => , + cell: ({ row }) => { + const user = loadData(); + const projects = user?.projects || []; + + // Find the project that contains the current task + const project = projects.find((project) => { + return project.tasks.some((task: Task) => task.id === row.original.id); + }); + + return ( +
+ {/* Display project name if found */} + + {project ? project.name : "Project Not Found"} + +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + accessorKey: "timeSpent", + header: ({ column }) => , + cell: ({ row }) => { + const task: Task = row.original; // Assuming row.original contains the Task object + const totalTime = calculateTotalTime(task); + const formattedTime = msToTime(totalTime); + + return
{formattedTime}
; + }, + }, + { + accessorKey: "priority", + header: ({ column }) => , + cell: ({ row }) => { + const priority = priorities.find( + (priority) => priority.value === row.getValue("priority") + ); + + if (!priority) { + return null; + } + + return ( +
+ {priority.icon && ( + + )} + {priority.label} +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + id: "actions", + cell: ({ row }) => ( +
+ +
+ ), + }, +]; + +export const columnsSm: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("name")} + +
+ ); + }, + }, + { + accessorKey: "projectName", + header: ({ column }) => , + cell: ({ row }) => { + const user = loadData(); + const projects = user?.projects || []; + + // Find the project that contains the current task + const project = projects.find((project) => { + return project.tasks.some((task: Task) => task.id === row.original.id); + }); + + return ( +
+ {/* Display project name if found */} + + {project ? project.name : "Project Not Found"} + +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + accessorKey: "priority", + header: ({ column }) => , + cell: ({ row }) => { + const priority = priorities.find( + (priority) => priority.value === row.getValue("priority") + ); + + if (!priority) { + return null; + } + + return ( +
+ {priority.icon && ( + + )} + {priority.label} +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, + { + id: "actions", + cell: ({ row }) => ( +
+ +
+ ), + }, +]; + +export const columnsMobile: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => , + cell: ({ row }) => { + // const label = labels.find((label) => label.value === row.original.label) + + return ( +
+ {/* {label && {label.label}} */} + + {row.getValue("name")} + +
+ ); + }, + }, + { + accessorKey: "priority", + header: ({ column }) => , + cell: ({ row }) => { + const priority = priorities.find( + (priority) => priority.value === row.getValue("priority") + ); + + if (!priority) { + return null; + } + + return ( +
+ {priority.icon && ( + + )} + {priority.label} +
+ ); + }, + filterFn: (row, id, value) => { + return value.includes(row.getValue(id)); + }, + }, +]; diff --git a/src/components/tasks/data-table-column-header.tsx b/src/components/tasks/data-table-column-header.tsx new file mode 100644 index 0000000..f387db7 --- /dev/null +++ b/src/components/tasks/data-table-column-header.tsx @@ -0,0 +1,65 @@ +import { ArrowDownIcon, ArrowUpIcon, EyeOffIcon, ArrowUpDownIcon } from "lucide-react"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/lib/utils"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Button } from "../ui/button"; + +interface DataTableColumnHeaderProps extends React.HTMLAttributes { + column: Column; + title: string; +} + +export function DataTableColumnHeader({ + column, + title, + className, +}: DataTableColumnHeaderProps) { + if (!column.getCanSort()) { + return
{title}
; + } + + return ( +
+ + + + + + column.toggleSorting(false)}> + + Asc + + column.toggleSorting(true)}> + + Desc + + + column.toggleVisibility(false)}> + + Hide + + + +
+ ); +} diff --git a/src/components/tasks/data-table-faceted-filter-simple.tsx b/src/components/tasks/data-table-faceted-filter-simple.tsx new file mode 100644 index 0000000..dd4e25e --- /dev/null +++ b/src/components/tasks/data-table-faceted-filter-simple.tsx @@ -0,0 +1,137 @@ +import * as React from "react"; +import { CheckIcon, CirclePlusIcon } from "lucide-react"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/lib/utils"; +import { Button } from "../ui/button"; +import { Separator } from "../ui/separator"; +import { Badge } from "../ui/badge"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "../ui/command"; +import { Project } from "@/models"; + +interface DataTableFacetedFilterProps { + column?: Column; + title?: string; + options: string[]; +} + +export function DataTableFacetedFilterSimple({ + column, + title, + options, +}: DataTableFacetedFilterProps) { + const facets = column?.getFacetedUniqueValues(); + const selectedValues = new Set(column?.getFilterValue() as string[]); + + return ( + + + + + + + + + No results found. + + {options.map((option) => { + const isSelected = selectedValues.has(option); + return ( + { + if (isSelected) { + selectedValues.delete(option); + } else { + selectedValues.add(option); + } + const filterValues = Array.from(selectedValues); + column?.setFilterValue( + filterValues.length ? filterValues : undefined + ); + }} + > +
+ +
+ {option} + {facets?.get(option) && ( + + {facets.get(option)} + + )} +
+ ); + })} +
+ {selectedValues.size > 0 && ( + <> + + + column?.setFilterValue(undefined)} + className="justify-center text-center" + > + Clear filters + + + + )} +
+
+
+
+ ); +} diff --git a/src/components/tasks/data-table-faceted-filter.tsx b/src/components/tasks/data-table-faceted-filter.tsx new file mode 100644 index 0000000..23ec387 --- /dev/null +++ b/src/components/tasks/data-table-faceted-filter.tsx @@ -0,0 +1,144 @@ +import * as React from "react"; +import { CheckIcon, CirclePlusIcon } from "lucide-react"; +import { Column } from "@tanstack/react-table"; + +import { cn } from "@/lib/utils"; +import { Button } from "../ui/button"; +import { Separator } from "../ui/separator"; +import { Badge } from "../ui/badge"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "../ui/command"; + +interface DataTableFacetedFilterProps { + column?: Column; + title?: string; + options: { + label: string; + value: string; + icon?: React.ComponentType<{ className?: string }>; + }[]; +} + +export function DataTableFacetedFilter({ + column, + title, + options, +}: DataTableFacetedFilterProps) { + const facets = column?.getFacetedUniqueValues(); + const selectedValues = new Set(column?.getFilterValue() as string[]); + console.log(facets); + + return ( + + + + + + + + + No results found. + + {options.map((option) => { + const isSelected = selectedValues.has(option.value); + return ( + { + if (isSelected) { + selectedValues.delete(option.value); + } else { + selectedValues.add(option.value); + } + const filterValues = Array.from(selectedValues); + column?.setFilterValue( + filterValues.length ? filterValues : undefined + ); + }} + > +
+ +
+ {option.icon && ( + + )} + {option.label} + {facets?.get(option.value) && ( + + {facets.get(option.value)} + + )} +
+ ); + })} +
+ {selectedValues.size > 0 && ( + <> + + + column?.setFilterValue(undefined)} + className="justify-center text-center" + > + Clear filters + + + + )} +
+
+
+
+ ); +} diff --git a/src/components/tasks/data-table-pagination.tsx b/src/components/tasks/data-table-pagination.tsx new file mode 100644 index 0000000..8420f12 --- /dev/null +++ b/src/components/tasks/data-table-pagination.tsx @@ -0,0 +1,87 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + ArrowLeftToLineIcon, + ArrowRightToLineIcon, +} from "lucide-react"; +import { Table } from "@tanstack/react-table"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; +import { Button } from "../ui/button"; + +interface DataTablePaginationProps { + table: Table; +} + +export function DataTablePagination({ table }: DataTablePaginationProps) { + return ( +
+
+ {/* {table.getFilteredSelectedRowModel().rows.length} of{" "} + {table.getFilteredRowModel().rows.length} row(s) selected. */} +
+
+
+

Rows per page

+ +
+
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()} +
+
+ + + + +
+
+
+ ); +} diff --git a/src/components/tasks/data-table-row-actions.tsx b/src/components/tasks/data-table-row-actions.tsx new file mode 100644 index 0000000..129a6c6 --- /dev/null +++ b/src/components/tasks/data-table-row-actions.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { EllipsisIcon, Edit2Icon } from "lucide-react"; +import { Row } from "@tanstack/react-table"; + +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Project, Task } from "@/models"; + +interface DataTableRowActionsProps { + row: Row; +} + +export function DataTableRowActions({ row }: DataTableRowActionsProps) { + const taskId = row.original.id; + + return ( + + + + + + + View + + + Edit + + + + ); +} diff --git a/src/components/tasks/data-table-toolbar.tsx b/src/components/tasks/data-table-toolbar.tsx new file mode 100644 index 0000000..e5ed918 --- /dev/null +++ b/src/components/tasks/data-table-toolbar.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { XIcon } from "lucide-react"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +import Project, { priorities, statuses } from "@/models/project"; +import { DataTableFacetedFilter } from "./data-table-faceted-filter"; +import { DataTableViewOptions } from "./data-table-view-options"; +import { loadData } from "@/utils/load"; +import { DataTableFacetedFilterSimple } from "./data-table-faceted-filter-simple"; +import { Task } from "@/models"; + +interface DataTableToolbarProps { + table: Table; +} + +export function DataTableToolbar({ table }: DataTableToolbarProps) { + const isFiltered = table.getState().columnFilters.length > 0; + + const user = loadData(); + + let projects: string[] = []; + if (user && user.projects) { + const projectNames = user.projects.map((project: Project) => project.name); + for (const name of projectNames) { + if (!projects.includes(name)) { + projects.push(name); + } + } + } + + return ( +
+
+ + table.getColumn("name")?.setFilterValue(event.target.value) + } + className="h-8 w-[150px] lg:w-[250px]" + /> + {table.getColumn("projectName") && ( + + )} + {table.getColumn("priority") && ( + + )} + {isFiltered && ( + + )} +
+ +
+ ); +} diff --git a/src/components/tasks/data-table-view-options.tsx b/src/components/tasks/data-table-view-options.tsx new file mode 100644 index 0000000..971a01f --- /dev/null +++ b/src/components/tasks/data-table-view-options.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { SlidersHorizontalIcon } from "lucide-react"; +import { Table } from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, +} from "@/components/ui/dropdown-menu"; + +interface DataTableViewOptionsProps { + table: Table; +} + +export function DataTableViewOptions({ table }: DataTableViewOptionsProps) { + return ( + + + + + + Toggle columns + + {table + .getAllColumns() + .filter( + (column) => typeof column.accessorFn !== "undefined" && column.getCanHide() + ) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ); + })} + + + ); +} diff --git a/src/components/tasks/data-table.tsx b/src/components/tasks/data-table.tsx new file mode 100644 index 0000000..c4ab4af --- /dev/null +++ b/src/components/tasks/data-table.tsx @@ -0,0 +1,140 @@ +"use client"; + +import * as React from "react"; +import { useRouter } from "next/navigation"; + +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +import { DataTablePagination } from "./data-table-pagination"; +import { DataTableToolbar } from "./data-table-toolbar"; + +interface TEntity { + id: number; +} + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + pagination?: boolean; + clickableRows?: boolean; +} + +export function DataTable({ + columns, + data, + pagination, + clickableRows, +}: DataTableProps) { + const router = useRouter(); + + const [rowSelection, setRowSelection] = React.useState({}); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [columnFilters, setColumnFilters] = React.useState([]); + const [sorting, setSorting] = React.useState([]); + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: pagination ? getPaginationRowModel() : undefined, + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + }); + + function handleRowClick(id: number): void { + if (clickableRows) { + router.push(`/tasks/${id}`); + } + } + + return ( +
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + handleRowClick(row.original.id)} + className={clickableRows ? "cursor-pointer" : ""} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ {pagination && } +
+ ); +} diff --git a/src/models/index.ts b/src/models/index.ts index 92a50cd..f89127c 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,3 +1,4 @@ +export type { default as User } from "./user"; export type { default as Project } from "./project"; export type { default as Task } from "./task"; -export type { default as User } from "./user"; +export type { default as TimeSpan } from "./timespan"; diff --git a/src/models/project.ts b/src/models/project.ts index 495ab1e..5984de8 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -1,4 +1,4 @@ -import { Task } from "./task"; +import Task from "./task"; import { ArrowDown, diff --git a/src/models/task.ts b/src/models/task.ts index 725f1fc..6584be3 100644 --- a/src/models/task.ts +++ b/src/models/task.ts @@ -1,7 +1,72 @@ +import { loadData } from "@/utils/load"; +import TimeSpan from "./timespan"; + +import { ArrowDown, ArrowRight, ArrowUp } from "lucide-react"; + +export type Priority = (typeof priorities)[number]["value"]; + export default interface Task { id: string; name: string; - startTime: Date; - endTime: Date | null; description?: string; + priority: Priority; + timeSpans: TimeSpan[]; } + +export const priorities = [ + { + value: "low", + label: "Low", + icon: ArrowDown, + }, + { + value: "medium", + label: "Medium", + icon: ArrowRight, + }, + { + value: "high", + label: "High", + icon: ArrowUp, + }, +]; + +export const getProjectValue = (task: Task) => { + const user = loadData(); + const projects = user?.projects || []; + const project = projects.find((project) => project.tasks.some((t: Task) => t.id === task.id)); + return project ? project.name : "Unknown"; +}; + +export const calculateTotalTime = (task: Task): number => { + return task.timeSpans.reduce((total, timeSpan) => { + if (timeSpan.end !== null) { + const start = Date.parse(String(timeSpan.start)); + const end = Date.parse(String(timeSpan.end)); + return total + (end - start); + } + return total; + }, 0); +}; + +export const msToTime = (duration: number) => { + let seconds = Math.floor((duration / 1000) % 60), + minutes = Math.floor((duration / (1000 * 60)) % 60), + hours = Math.floor((duration / (1000 * 60 * 60)) % 24); + + // Initialize an array to store non-zero time components + let timeComponents: string[] = []; + + if (hours > 0) { + timeComponents.push(`${hours} hour${hours > 1 ? "s" : ""}`); + } + if (minutes > 0) { + timeComponents.push(`${minutes} minute${minutes > 1 ? "s" : ""}`); + } + if (seconds > 0 || timeComponents.length === 0) { + timeComponents.push(`${seconds} second${seconds > 1 ? "s" : ""}`); + } + + // Join time components with commas and return as a single string + return timeComponents.join(", "); +}; diff --git a/src/models/timespan.ts b/src/models/timespan.ts new file mode 100644 index 0000000..96b83bc --- /dev/null +++ b/src/models/timespan.ts @@ -0,0 +1,4 @@ +export default interface TimeSpan { + start: Date; + end: Date | null; +} diff --git a/src/pages/projects.tsx b/src/pages/projects.tsx index a588ecb..08391ea 100644 --- a/src/pages/projects.tsx +++ b/src/pages/projects.tsx @@ -40,7 +40,7 @@ import { } from "@/components/ui/table"; import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; -import { DataTable } from "@/components/ui/data-table"; +import { DataTable } from "@/components/projects/data-table"; import { columnsXl, columnsLg, diff --git a/src/pages/project/[id].tsx b/src/pages/projects/[id].tsx similarity index 100% rename from src/pages/project/[id].tsx rename to src/pages/projects/[id].tsx diff --git a/src/pages/project/[id]/edit.tsx b/src/pages/projects/[id]/edit.tsx similarity index 100% rename from src/pages/project/[id]/edit.tsx rename to src/pages/projects/[id]/edit.tsx diff --git a/src/pages/tasks.tsx b/src/pages/tasks.tsx new file mode 100644 index 0000000..fa5a42c --- /dev/null +++ b/src/pages/tasks.tsx @@ -0,0 +1,149 @@ +import { useState, useEffect } from "react"; +import Link from "next/link"; + +import { Project, User } from "../models"; +import { saveData } from "../utils/save"; +import { loadData } from "../utils/load"; +import { importData } from "../utils/import"; +import { exportData } from "../utils/export"; +import { setSessionStorageItem, getSessionStorageItem } from "../utils/sessionStorage"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { toast } from "@/components/ui/use-toast"; +import { useUser } from "@/components/UserContext"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"; +import { DataTable } from "@/components/tasks/data-table"; +import { + columnsXl, + columnsLg, + columnsMd, + columnsSm, + columnsMobile, +} from "@/components/tasks/columns"; + +export default function Settings() { + const { user, setUser } = useUser(); + + useEffect(() => { + const data = loadData(); + if (data) { + setUser(data); + } + }, []); + + if (!user) { + return
Loading...
; + } + + const data = user.projects + .flatMap((project) => project.tasks) + .map((task) => { + const project = user.projects.find((project) => + project.tasks.some((t: { id: any }) => t.id === task.id) + ); + return { + ...task, + projectName: project ? project.name : "Project Not Found", + }; + }); + + return ( +
+
+
+ + Tasks + + + + +
+
+ + Tasks + + + + +
+
+ + Tasks + + + + +
+
+ + Tasks + + + + +
+
+ + Tasks + + + + +
+
+
+ ); +}