diff --git a/.replit b/.replit index 61099cd..ccafaa6 100644 --- a/.replit +++ b/.replit @@ -18,6 +18,10 @@ externalPort = 80 localPort = 35345 externalPort = 3002 +[[ports]] +localPort = 40293 +externalPort = 3001 + [[ports]] localPort = 41353 externalPort = 3000 diff --git a/client/src/App.tsx b/client/src/App.tsx index c8983ab..d79034b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -148,7 +148,7 @@ function App() { /> ); - case 'templates': + case 'settings': return (
- {currentTab === 'templates' && ( + {currentTab === 'settings' && (
- + - Templates + Projects Labels - - {/* Templates Section Header */} -
-

Project Templates

- - - - - - - Create New Template - -
- Template creation form would go here -
-
-
-
- - {/* Templates by Category */} - {getTemplatesByCategory().map(({ category, templates: categoryTemplates }) => ( -
-

- {category} -

- -
- {categoryTemplates.map((template) => ( - -
-
-
-

- {template.name} -

- {template.description && ( -

- {template.description} -

- )} -
- + + {/* Project Kanban Board */} +
+ {projectColumns.map((column) => { + const columnProjects = getProjectsByStatus(column.status); + + return ( + handleDrop(column.status)} + data-testid={`column-${column.id}`} + > +
+

+ {column.title} +

- {template.tasks.length} tasks + {columnProjects.length}
+ +
+ {columnProjects.map((project) => ( + handleDragStart(project.id)} + onDragEnd={handleDragEnd} + className={`p-4 cursor-move hover-elevate transition-all ${ + draggedProject === project.id ? 'opacity-50 scale-95' : '' + }`} + data-testid={`project-card-${project.id}`} + > +
+
+
+

+ {project.name} +

+ {project.description && ( +

+ {project.description} +

+ )} +
+ +
+ + +
+
-
-
- - {getTotalEstimatedHours(template)}h estimated -
-
- - {Math.abs(Math.min(...template.tasks.map(t => t.dayOffset)))} day timeline -
-
+
+
+ + {getTotalEstimatedHours(project)}h +
+
+ + {project.tasks.length} tasks + +
+
-
-
Sample tasks:
-
- {template.tasks.slice(0, 3).map((task) => ( -
- {task.title} - - {formatDayOffset(task.dayOffset)} - + {project.tasks.length > 0 && ( +
+
+ Progress + {getTaskCompletionRate(project)}% +
+
+
+
+
+ )} + + {project.startDate && ( +
+ Started: {project.startDate.toLocaleDateString()} +
+ )}
- ))} - {template.tasks.length > 3 && ( -
- +{template.tasks.length - 3} more tasks -
- )} -
-
- -
- + + ))} - + {columnProjects.length === 0 && ( +
+ No projects in {column.title.toLowerCase()} +
+ )}
-
- - ))} + + ); + })}
-
- ))} + - {/* Launch Project Dialog */} - - - - - - Launch Project from Template - - - - {selectedTemplate && ( -
-
-
{selectedTemplate.name}
-
- {selectedTemplate.tasks.length} tasks • {getTotalEstimatedHours(selectedTemplate)}h estimated -
-
- -
+ {/* Create Project Dialog */} + + + + Create New Project + +
+
+ setProjectName(e.target.value)} + value={newProjectName} + onChange={(e) => setNewProjectName(e.target.value)} + placeholder="Enter project name" data-testid="input-project-name" />
- -
- - - - - - { - setStartDate(date); - setIsCalendarOpen(false); - }} - disabled={(date) => date < new Date()} - initialFocus - /> - - -
- -
- - +
+ + setNewProjectDescription(e.target.value)} + placeholder="Enter project description" + data-testid="input-project-description" + />
- )} - -
- +
+ + +
+ +
- + {/* Edit Project Dialog */} + + + + Edit Project + + {selectedProject && ( +
+
+ + setSelectedProject({ ...selectedProject, name: e.target.value })} + placeholder="Enter project name" + data-testid="input-edit-project-name" + /> +
+
+ + setSelectedProject({ ...selectedProject, description: e.target.value })} + placeholder="Enter project description" + data-testid="input-edit-project-description" + /> +
+
+ + +
+
+ )} +
+ + +
+
+
+ + {/* Labels Section Header */}

Task Labels

diff --git a/client/src/components/TasksWithCalendar.tsx b/client/src/components/TasksWithCalendar.tsx index cdd0226..cbb8f89 100644 --- a/client/src/components/TasksWithCalendar.tsx +++ b/client/src/components/TasksWithCalendar.tsx @@ -5,8 +5,9 @@ import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Search, Filter, SortAsc, Calendar, ChevronLeft, ChevronRight } from 'lucide-react'; -import { Task } from '@shared/schema'; +import { Task, Label } from '@shared/schema'; import TaskCard from './TaskCard'; +import { useQuery } from '@tanstack/react-query'; import TimeCompletionModal from './TimeCompletionModal'; import { addDays, format, isSameDay, startOfToday } from 'date-fns'; @@ -31,6 +32,13 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T task: null }); + // Fetch labels to get label colors + const { data: labels = [] } = useQuery({ + queryKey: ['/api/labels'], + staleTime: 5 * 60 * 1000, // 5 minutes cache + refetchOnWindowFocus: false, + }); + // Get 7 days starting from today for calendar const dates = Array.from({ length: 7 }, (_, i) => addDays(calendarStartDate, i)); @@ -371,25 +379,35 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
- {dayTasks.slice(0, 2).map((task) => ( -
handleDragStart(task.id, e)} - onDragEnd={handleDragEnd} - className={`cursor-move ${draggedTask === task.id ? 'opacity-50' : ''}`} - data-testid={`calendar-task-${task.id}`} - > - { + // Find the label for this task + const taskLabel = task.labelId && labels.length > 0 ? labels.find(label => label.id === task.labelId) : null; + + return ( +
handleDragStart(task.id, e)} + onDragEnd={handleDragEnd} + className={`cursor-move ${draggedTask === task.id ? 'opacity-50' : ''}`} + data-testid={`calendar-task-${task.id}`} > -
- {task.title} -
- -
- ))} + +
+ {task.title} +
+
+
+ ); + })} {dayTasks.length > 2 && ( diff --git a/monthly_view.png b/monthly_view.png new file mode 100644 index 0000000..86d6d1f Binary files /dev/null and b/monthly_view.png differ diff --git a/traditional_view.png b/traditional_view.png new file mode 100644 index 0000000..9d6bb0b Binary files /dev/null and b/traditional_view.png differ diff --git a/weekly_view.png b/weekly_view.png new file mode 100644 index 0000000..7007b28 Binary files /dev/null and b/weekly_view.png differ