PrithviGuardian / client /src /pages /ProjectManager.tsx
Varad Bakshi
Fresh upload
e7427b5
Raw
History Blame Contribute Delete
11.5 kB
import { useState, useEffect } from "react";
import { Plus, Building2, MapPin, Calendar, Users, Settings, Trash2, Eye, Edit3 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useToast } from "@/hooks/use-toast";
import { offlineStorage } from "@/lib/offlineStorage";
import { projectSchema } from "@/lib/validationSchemas";
import { errorHandler, withErrorHandling } from "@/lib/errorHandler";
export default function ProjectManager() {
const [selectedProject, setSelectedProject] = useState<any>(null);
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [projects, setProjects] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isCreating, setIsCreating] = useState(false);
const { toast } = useToast();
const form = useForm({
resolver: zodResolver(projectSchema),
defaultValues: {
name: "",
description: "",
type: "",
location: "",
status: "active" as const,
},
});
// Load projects on component mount
useEffect(() => {
loadProjects();
}, []);
const loadProjects = async () => {
const result = await withErrorHandling.async(
() => offlineStorage.getProjects(),
'ProjectManager',
'loading projects'
);
if (result.success) {
setProjects(result.data || []);
} else {
toast({
title: "Error loading projects",
description: "Could not load projects from storage",
variant: "destructive"
});
}
setIsLoading(false);
};
const onSubmit = async (data: any) => {
setIsCreating(true);
const result = await withErrorHandling.async(
() => offlineStorage.createProject(data),
'ProjectManager',
'creating project'
);
if (result.success) {
setProjects(prev => [...prev, result.data]);
setIsCreateDialogOpen(false);
form.reset();
toast({ title: "Project created successfully" });
} else {
const errorLog = errorHandler.getRecentErrors(1)[0];
toast({
title: "Error creating project",
description: errorLog?.recoveryAction || "Please try again",
variant: "destructive"
});
}
setIsCreating(false);
};
const getProjectTypeIcon = (type: string) => {
switch (type) {
case "university": return <Building2 className="w-4 h-4" />;
case "residential": return <Users className="w-4 h-4" />;
case "commercial": return <Building2 className="w-4 h-4" />;
case "infrastructure": return <MapPin className="w-4 h-4" />;
default: return <Building2 className="w-4 h-4" />;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case "active": return "bg-green-100 text-green-800";
case "completed": return "bg-blue-100 text-blue-800";
case "archived": return "bg-gray-100 text-gray-800";
default: return "bg-gray-100 text-gray-800";
}
};
if (isLoading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading projects...</p>
</div>
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">परियोजना प्रबंधक</h1>
<p className="text-lg text-gray-600">Integrated Project Management Dashboard</p>
<p className="text-sm text-gray-500 mt-1">Create projects and link engineering modules for comprehensive analysis</p>
</div>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogTrigger asChild>
<Button className="mt-4 md:mt-0">
<Plus className="w-4 h-4 mr-2" />
New Project
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
<DialogDescription>
Start a new engineering project to organize your calculations and reports
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Project Name</FormLabel>
<FormControl>
<Input placeholder="e.g., New University Campus Development" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Project Type</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select project type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="university">University/Educational</SelectItem>
<SelectItem value="residential">Residential Complex</SelectItem>
<SelectItem value="commercial">Commercial Development</SelectItem>
<SelectItem value="infrastructure">Infrastructure Project</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="location"
render={({ field }) => (
<FormItem>
<FormLabel>Location</FormLabel>
<FormControl>
<Input placeholder="e.g., Mumbai, Maharashtra" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Describe your project objectives, scope, and requirements"
className="min-h-[100px]"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end space-x-3">
<Button type="button" variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
Cancel
</Button>
<Button type="submit" disabled={createProjectMutation.isPending}>
{createProjectMutation.isPending ? "Creating..." : "Create Project"}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
</div>
{/* Projects Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{projects?.map((project: any) => (
<Card key={project.id} className="hover:shadow-lg transition-shadow cursor-pointer">
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex items-center space-x-2">
{getProjectTypeIcon(project.type)}
<CardTitle className="text-lg">{project.name}</CardTitle>
</div>
<Badge className={getStatusColor(project.status)}>
{project.status}
</Badge>
</div>
<CardDescription className="line-clamp-2">
{project.description || "No description provided"}
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex items-center text-sm text-gray-600">
<MapPin className="w-4 h-4 mr-2" />
{project.location || "No location specified"}
</div>
<div className="flex items-center text-sm text-gray-600">
<Calendar className="w-4 h-4 mr-2" />
Created {new Date(project.createdAt).toLocaleDateString()}
</div>
<div className="flex items-center text-sm text-gray-600">
<Building2 className="w-4 h-4 mr-2" />
{project.type.charAt(0).toUpperCase() + project.type.slice(1)} Project
</div>
</div>
<div className="flex space-x-2 mt-4">
<Button variant="outline" size="sm" onClick={() => setSelectedProject(project)}>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
<Button variant="outline" size="sm">
<Edit3 className="w-4 h-4 mr-1" />
Edit
</Button>
</div>
</CardContent>
</Card>
))}
</div>
{projects?.length === 0 && (
<div className="text-center py-12">
<Building2 className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-gray-900 mb-2">No Projects Yet</h3>
<p className="text-gray-600 mb-6">Create your first project to start organizing your engineering work</p>
<Button onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Create First Project
</Button>
</div>
)}
</div>
);
}