Add ability to create projects and copy tasks from templates
Adds functionality to create new projects, copying existing project tasks into the global task list. Includes a helper function to convert project-specific tasks to a general format and integrates this into the project creation process. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/P9UdTaE
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ function App() {
|
||||
return (
|
||||
<ProjectTemplate
|
||||
onCreateFromTemplate={handleCreateFromTemplate}
|
||||
onCreateTasks={(newTasks) => setTasks(prev => [...prev, ...newTasks])}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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<Project[]>(defaultProjects);
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(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({
|
||||
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Project</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new project with tasks, timeline, and organize your work efficiently.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Project Details */}
|
||||
|
||||
Reference in New Issue
Block a user