diff --git a/.replit b/.replit index fdec5e9..9e4f6e9 100644 --- a/.replit +++ b/.replit @@ -18,14 +18,14 @@ externalPort = 80 localPort = 35345 externalPort = 3002 -[[ports]] -localPort = 40247 -externalPort = 3001 - [[ports]] localPort = 41353 externalPort = 3000 +[[ports]] +localPort = 44261 +externalPort = 3003 + [env] PORT = "5000" diff --git a/client/src/App.tsx b/client/src/App.tsx index d068e92..3788273 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -194,6 +194,7 @@ function App() { return ( setTasks(prev => [...prev, ...newTasks])} /> ); diff --git a/client/src/components/ProjectTemplate.tsx b/client/src/components/ProjectTemplate.tsx index e8e28a9..5fe3926 100644 --- a/client/src/components/ProjectTemplate.tsx +++ b/client/src/components/ProjectTemplate.tsx @@ -3,13 +3,13 @@ import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Calendar } from '@/components/ui/calendar'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Plus, Calendar as CalendarIcon, Clock, Copy, Play, Tag, Edit, Trash2, Palette, FileText } from 'lucide-react'; -import { Label } from '@shared/schema'; +import { Label, Task } from '@shared/schema'; import TaskDetailsModal from '@/components/TaskDetailsModal'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiRequest } from '@/lib/queryClient'; @@ -20,6 +20,23 @@ const getStartOfToday = () => { return new Date(today.getFullYear(), today.getMonth(), today.getDate()); }; +// Helper function to convert ProjectTask to global Task format +const convertProjectTaskToTask = (projectTask: ProjectTask, projectId: string): Task => { + return { + id: `project-${projectId}-task-${projectTask.id}`, // Generate unique ID + title: projectTask.title, + description: projectTask.description || null, + status: projectTask.status, + priority: projectTask.priority, + dueDate: null, // Projects tasks don't have due dates by default + timeTracked: projectTask.timeTracked || 0, + isTracking: false, + projectId: projectId, + notes: projectTask.notes || null, + labelId: projectTask.labelId || null + }; +}; + interface TimeEntry { id: string; date: Date; @@ -55,6 +72,7 @@ interface Project { interface ProjectTemplateProps { onCreateFromTemplate?: (templateId: string, startDate: Date, projectName: string) => void; onCreateTemplate?: (template: any) => void; + onCreateTasks?: (tasks: Task[]) => void; } const defaultProjects: Project[] = [ @@ -103,7 +121,8 @@ const defaultProjects: Project[] = [ export default function ProjectTemplate({ onCreateFromTemplate, - onCreateTemplate + onCreateTemplate, + onCreateTasks }: ProjectTemplateProps) { const [projects, setProjects] = useState(defaultProjects); const [selectedProject, setSelectedProject] = useState(null); @@ -180,8 +199,9 @@ export default function ProjectTemplate({ const handleCreateProject = () => { if (newProjectName.trim()) { + const newProjectId = Date.now().toString(); const newProject: Project = { - id: Date.now().toString(), + id: newProjectId, name: newProjectName.trim(), description: newProjectDescription.trim() || undefined, status: 'planning', @@ -190,7 +210,19 @@ export default function ProjectTemplate({ createdAt: new Date() }; - setProjects([...projects, newProject]); + // Add project to projects list + setProjects(currentProjects => [...currentProjects, newProject]); + + // Convert project tasks to global tasks and add them to global state + if (projectTasks.length > 0 && onCreateTasks) { + const globalTasks = projectTasks.map(task => + convertProjectTaskToTask(task, newProjectId) + ); + onCreateTasks(globalTasks); + console.log('Added', globalTasks.length, 'tasks to global state for project:', newProject.name); + } + + // Reset form state setNewProjectName(''); setNewProjectDescription(''); setNewPlannedEndDate(undefined); @@ -208,12 +240,12 @@ export default function ProjectTemplate({ const handleDeleteProject = (projectId: string) => { if (confirm('Are you sure you want to delete this project?')) { - setProjects(projects.filter(p => p.id !== projectId)); + setProjects(currentProjects => currentProjects.filter(p => p.id !== projectId)); } }; const handleProjectStatusChange = (projectId: string, newStatus: Project['status']) => { - setProjects(projects.map(project => + setProjects(currentProjects => currentProjects.map(project => project.id === projectId ? { ...project, status: newStatus } : project )); }; @@ -247,6 +279,14 @@ export default function ProjectTemplate({ // Create a new project as a duplicate/copy of the closed project const newProjectId = Date.now().toString(); + const duplicatedTasks = clearHistory + ? [] + : restartingProject.tasks.map(task => ({ + ...task, + id: `${newProjectId}-task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, // Generate new task ID + status: 'todo' as const // Reset all tasks to todo status + })); + const duplicatedProject: Project = { ...restartingProject, id: newProjectId, // New unique ID @@ -255,17 +295,20 @@ export default function ProjectTemplate({ endDate: undefined, // Clear end date since it's active again plannedEndDate: newEndDate, // Set the new planned end date createdAt: new Date(), // Set new creation date - tasks: clearHistory - ? [] - : restartingProject.tasks.map(task => ({ - ...task, - id: `${newProjectId}-task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, // Generate new task ID - status: 'todo' as const // Reset all tasks to todo status - })) + tasks: duplicatedTasks }; // Add the new project to the projects array without removing the original - setProjects([...projects, duplicatedProject]); + setProjects(currentProjects => [...currentProjects, duplicatedProject]); + + // Convert project tasks to global tasks and add them to global state + if (duplicatedTasks.length > 0 && onCreateTasks) { + const globalTasks = duplicatedTasks.map(task => + convertProjectTaskToTask(task, newProjectId) + ); + onCreateTasks(globalTasks); + console.log('Added', globalTasks.length, 'tasks to global state for duplicated project:', duplicatedProject.name); + } setIsRestartProjectOpen(false); setRestartingProject(null); @@ -407,7 +450,7 @@ export default function ProjectTemplate({ setSelectedProject(updatedProject); // Update the projects array - setProjects(projects.map(p => + setProjects(currentProjects => currentProjects.map(p => p.id === selectedProject.id ? updatedProject : p )); } @@ -421,7 +464,7 @@ export default function ProjectTemplate({ tasks: projectTasks }; - setProjects(projects.map(p => + setProjects(currentProjects => currentProjects.map(p => p.id === selectedProject.id ? updatedProject : p )); @@ -784,6 +827,9 @@ export default function ProjectTemplate({ Create New Project + + Create a new project with tasks, timeline, and organize your work efficiently. +
{/* Project Details */}