Spaces:
Configuration error
Configuration error
| 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> | |
| ); | |
| } |