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
|
localPort = 35345
|
||||||
externalPort = 3002
|
externalPort = 3002
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 40247
|
|
||||||
externalPort = 3001
|
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 41353
|
localPort = 41353
|
||||||
externalPort = 3000
|
externalPort = 3000
|
||||||
|
|
||||||
|
[[ports]]
|
||||||
|
localPort = 44261
|
||||||
|
externalPort = 3003
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
PORT = "5000"
|
PORT = "5000"
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<ProjectTemplate
|
<ProjectTemplate
|
||||||
onCreateFromTemplate={handleCreateFromTemplate}
|
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 { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Badge } from '@/components/ui/badge';
|
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { Calendar } from '@/components/ui/calendar';
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
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 { 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 TaskDetailsModal from '@/components/TaskDetailsModal';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiRequest } from '@/lib/queryClient';
|
import { apiRequest } from '@/lib/queryClient';
|
||||||
@@ -20,6 +20,23 @@ const getStartOfToday = () => {
|
|||||||
return new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
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 {
|
interface TimeEntry {
|
||||||
id: string;
|
id: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
@@ -55,6 +72,7 @@ interface Project {
|
|||||||
interface ProjectTemplateProps {
|
interface ProjectTemplateProps {
|
||||||
onCreateFromTemplate?: (templateId: string, startDate: Date, projectName: string) => void;
|
onCreateFromTemplate?: (templateId: string, startDate: Date, projectName: string) => void;
|
||||||
onCreateTemplate?: (template: any) => void;
|
onCreateTemplate?: (template: any) => void;
|
||||||
|
onCreateTasks?: (tasks: Task[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProjects: Project[] = [
|
const defaultProjects: Project[] = [
|
||||||
@@ -103,7 +121,8 @@ const defaultProjects: Project[] = [
|
|||||||
|
|
||||||
export default function ProjectTemplate({
|
export default function ProjectTemplate({
|
||||||
onCreateFromTemplate,
|
onCreateFromTemplate,
|
||||||
onCreateTemplate
|
onCreateTemplate,
|
||||||
|
onCreateTasks
|
||||||
}: ProjectTemplateProps) {
|
}: ProjectTemplateProps) {
|
||||||
const [projects, setProjects] = useState<Project[]>(defaultProjects);
|
const [projects, setProjects] = useState<Project[]>(defaultProjects);
|
||||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
|
||||||
@@ -180,8 +199,9 @@ export default function ProjectTemplate({
|
|||||||
|
|
||||||
const handleCreateProject = () => {
|
const handleCreateProject = () => {
|
||||||
if (newProjectName.trim()) {
|
if (newProjectName.trim()) {
|
||||||
|
const newProjectId = Date.now().toString();
|
||||||
const newProject: Project = {
|
const newProject: Project = {
|
||||||
id: Date.now().toString(),
|
id: newProjectId,
|
||||||
name: newProjectName.trim(),
|
name: newProjectName.trim(),
|
||||||
description: newProjectDescription.trim() || undefined,
|
description: newProjectDescription.trim() || undefined,
|
||||||
status: 'planning',
|
status: 'planning',
|
||||||
@@ -190,7 +210,19 @@ export default function ProjectTemplate({
|
|||||||
createdAt: new Date()
|
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('');
|
setNewProjectName('');
|
||||||
setNewProjectDescription('');
|
setNewProjectDescription('');
|
||||||
setNewPlannedEndDate(undefined);
|
setNewPlannedEndDate(undefined);
|
||||||
@@ -208,12 +240,12 @@ export default function ProjectTemplate({
|
|||||||
|
|
||||||
const handleDeleteProject = (projectId: string) => {
|
const handleDeleteProject = (projectId: string) => {
|
||||||
if (confirm('Are you sure you want to delete this project?')) {
|
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']) => {
|
const handleProjectStatusChange = (projectId: string, newStatus: Project['status']) => {
|
||||||
setProjects(projects.map(project =>
|
setProjects(currentProjects => currentProjects.map(project =>
|
||||||
project.id === projectId ? { ...project, status: newStatus } : 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
|
// Create a new project as a duplicate/copy of the closed project
|
||||||
const newProjectId = Date.now().toString();
|
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 = {
|
const duplicatedProject: Project = {
|
||||||
...restartingProject,
|
...restartingProject,
|
||||||
id: newProjectId, // New unique ID
|
id: newProjectId, // New unique ID
|
||||||
@@ -255,17 +295,20 @@ export default function ProjectTemplate({
|
|||||||
endDate: undefined, // Clear end date since it's active again
|
endDate: undefined, // Clear end date since it's active again
|
||||||
plannedEndDate: newEndDate, // Set the new planned end date
|
plannedEndDate: newEndDate, // Set the new planned end date
|
||||||
createdAt: new Date(), // Set new creation date
|
createdAt: new Date(), // Set new creation date
|
||||||
tasks: clearHistory
|
tasks: duplicatedTasks
|
||||||
? []
|
|
||||||
: 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
|
|
||||||
}))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the new project to the projects array without removing the original
|
// 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);
|
setIsRestartProjectOpen(false);
|
||||||
setRestartingProject(null);
|
setRestartingProject(null);
|
||||||
@@ -407,7 +450,7 @@ export default function ProjectTemplate({
|
|||||||
setSelectedProject(updatedProject);
|
setSelectedProject(updatedProject);
|
||||||
|
|
||||||
// Update the projects array
|
// Update the projects array
|
||||||
setProjects(projects.map(p =>
|
setProjects(currentProjects => currentProjects.map(p =>
|
||||||
p.id === selectedProject.id ? updatedProject : p
|
p.id === selectedProject.id ? updatedProject : p
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -421,7 +464,7 @@ export default function ProjectTemplate({
|
|||||||
tasks: projectTasks
|
tasks: projectTasks
|
||||||
};
|
};
|
||||||
|
|
||||||
setProjects(projects.map(p =>
|
setProjects(currentProjects => currentProjects.map(p =>
|
||||||
p.id === selectedProject.id ? updatedProject : 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">
|
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create New Project</DialogTitle>
|
<DialogTitle>Create New Project</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Create a new project with tasks, timeline, and organize your work efficiently.
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-6 py-4">
|
<div className="space-y-6 py-4">
|
||||||
{/* Project Details */}
|
{/* Project Details */}
|
||||||
|
|||||||
Reference in New Issue
Block a user