inogen/app/components/dashboard/projects/projects-page.tsx

664 lines
24 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from "react";
import { DashboardLayout } from "../layout";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Badge } from "~/components/ui/badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { Label } from "~/components/ui/label";
import { Textarea } from "~/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import {
Plus,
Search,
Filter,
MoreHorizontal,
Edit,
Trash2,
Eye,
Calendar,
User,
DollarSign,
Clock,
} from "lucide-react";
// Mock data for projects
const mockProjects = [
{
id: 1,
name: "پروژه توسعه اپلیکیشن موبایل",
manager: "علی احمدی",
team: "تیم توسعه موبایل",
status: "در حال انجام",
priority: "بالا",
startDate: "1403/01/15",
endDate: "1403/06/30",
budget: "500,000,000",
progress: 65,
description: "توسعه اپلیکیشن موبایل برای مدیریت پروژه‌ها",
},
{
id: 2,
name: "پیاده‌سازی سیستم مدیریت محتوا",
manager: "فاطمه کریمی",
team: "تیم بک‌اند",
status: "تکمیل شده",
priority: "متوسط",
startDate: "1402/10/01",
endDate: "1403/02/15",
budget: "750,000,000",
progress: 100,
description: "توسعه سیستم مدیریت محتوای وب",
},
{
id: 3,
name: "بهینه‌سازی پایگاه داده",
manager: "محمد رضایی",
team: "تیم دیتابیس",
status: "در انتظار",
priority: "بالا",
startDate: "1403/03/01",
endDate: "1403/05/30",
budget: "300,000,000",
progress: 0,
description: "بهینه‌سازی عملکرد پایگاه داده‌های موجود",
},
{
id: 4,
name: "راه‌اندازی سیستم مانیتورینگ",
manager: "سارا موسوی",
team: "تیم DevOps",
status: "در حال انجام",
priority: "متوسط",
startDate: "1403/02/01",
endDate: "1403/04/15",
budget: "400,000,000",
progress: 30,
description: "پیاده‌سازی سیستم نظارت و مانیتورینگ",
},
{
id: 5,
name: "توسعه پنل مدیریت",
manager: "رضا نوری",
team: "تیم فرانت‌اند",
status: "لغو شده",
priority: "پایین",
startDate: "1402/12/01",
endDate: "1403/03/01",
budget: "200,000,000",
progress: 25,
description: "توسعه پنل مدیریت برای ادمین‌ها",
},
];
const statusColors = {
"در حال انجام": "info",
"تکمیل شده": "success",
"در انتظار": "warning",
"لغو شده": "destructive",
} as const;
const priorityColors = {
بالا: "destructive",
متوسط: "warning",
پایین: "secondary",
} as const;
export function ProjectsPage() {
const [projects, setProjects] = useState(mockProjects);
const [searchTerm, setSearchTerm] = useState("");
const [filterStatus, setFilterStatus] = useState("همه");
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [editingProject, setEditingProject] = useState<any>(null);
const [newProject, setNewProject] = useState({
name: "",
manager: "",
team: "",
status: "در انتظار",
priority: "متوسط",
startDate: "",
endDate: "",
budget: "",
description: "",
});
const filteredProjects = projects.filter((project) => {
const matchesSearch =
project.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.manager.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
filterStatus === "همه" || project.status === filterStatus;
return matchesSearch && matchesStatus;
});
const handleAddProject = () => {
const id = Math.max(...projects.map((p) => p.id)) + 1;
setProjects([...projects, { ...newProject, id, progress: 0 }]);
setNewProject({
name: "",
manager: "",
team: "",
status: "در انتظار",
priority: "متوسط",
startDate: "",
endDate: "",
budget: "",
description: "",
});
setIsAddDialogOpen(false);
};
const handleEditProject = (project: any) => {
setEditingProject(project);
setNewProject(project);
setIsAddDialogOpen(true);
};
const handleUpdateProject = () => {
setProjects(
projects.map((p) =>
p.id === editingProject.id
? {
...newProject,
id: editingProject.id,
progress: editingProject.progress,
}
: p,
),
);
setEditingProject(null);
setNewProject({
name: "",
manager: "",
team: "",
status: "در انتظار",
priority: "متوسط",
startDate: "",
endDate: "",
budget: "",
description: "",
});
setIsAddDialogOpen(false);
};
const handleDeleteProject = (id: number) => {
setProjects(projects.filter((p) => p.id !== id));
};
return (
<DashboardLayout>
<div className="p-6 space-y-6">
{/* Page Header */}
<div className="flex justify-between items-start">
<div>
<h1 className="text-2xl font-bold text-white font-persian">
مدیریت پروژهها
</h1>
<p className="text-gray-300 font-persian mt-1">
مدیریت و پیگیری پروژههای فناوری و نوآوری
</p>
</div>
<Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
<DialogTrigger asChild>
<Button className="font-persian">
<Plus className="w-4 h-4 ml-2" />
پروژه جدید
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="font-persian">
{editingProject ? "ویرایش پروژه" : "پروژه جدید"}
</DialogTitle>
<DialogDescription className="font-persian">
{editingProject
? "اطلاعات پروژه را ویرایش کنید."
: "اطلاعات پروژه جدید را وارد کنید."}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name" className="font-persian">
نام پروژه
</Label>
<Input
id="name"
value={newProject.name}
onChange={(e) =>
setNewProject({ ...newProject, name: e.target.value })
}
className="font-persian"
placeholder="نام پروژه را وارد کنید"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="manager" className="font-persian">
مدیر پروژه
</Label>
<Input
id="manager"
value={newProject.manager}
onChange={(e) =>
setNewProject({ ...newProject, manager: e.target.value })
}
className="font-persian"
placeholder="نام مدیر پروژه"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="team" className="font-persian">
تیم
</Label>
<Input
id="team"
value={newProject.team}
onChange={(e) =>
setNewProject({ ...newProject, team: e.target.value })
}
className="font-persian"
placeholder="نام تیم"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label className="font-persian">وضعیت</Label>
<Select
value={newProject.status}
onValueChange={(value) =>
setNewProject({ ...newProject, status: value })
}
>
<SelectTrigger className="font-persian">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="در انتظار">در انتظار</SelectItem>
<SelectItem value="در حال انجام">
در حال انجام
</SelectItem>
<SelectItem value="تکمیل شده">تکمیل شده</SelectItem>
<SelectItem value="لغو شده">لغو شده</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label className="font-persian">اولویت</Label>
<Select
value={newProject.priority}
onValueChange={(value) =>
setNewProject({ ...newProject, priority: value })
}
>
<SelectTrigger className="font-persian">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="بالا">بالا</SelectItem>
<SelectItem value="متوسط">متوسط</SelectItem>
<SelectItem value="پایین">پایین</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="startDate" className="font-persian">
تاریخ شروع
</Label>
<Input
id="startDate"
value={newProject.startDate}
onChange={(e) =>
setNewProject({
...newProject,
startDate: e.target.value,
})
}
className="font-persian"
placeholder="1403/01/01"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="endDate" className="font-persian">
تاریخ پایان
</Label>
<Input
id="endDate"
value={newProject.endDate}
onChange={(e) =>
setNewProject({
...newProject,
endDate: e.target.value,
})
}
className="font-persian"
placeholder="1403/06/01"
/>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="budget" className="font-persian">
بودجه (ریال)
</Label>
<Input
id="budget"
value={newProject.budget}
onChange={(e) =>
setNewProject({ ...newProject, budget: e.target.value })
}
className="font-persian"
placeholder="500,000,000"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="description" className="font-persian">
توضیحات
</Label>
<Textarea
id="description"
value={newProject.description}
onChange={(e) =>
setNewProject({
...newProject,
description: e.target.value,
})
}
className="font-persian"
placeholder="توضیحات پروژه"
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsAddDialogOpen(false)}
className="font-persian"
>
انصراف
</Button>
<Button
onClick={
editingProject ? handleUpdateProject : handleAddProject
}
className="font-persian"
>
{editingProject ? "ویرایش" : "ایجاد"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-2">
<div className="p-2 bg-blue-100 rounded-lg dark:bg-blue-900">
<Calendar className="w-5 h-5 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex-1 text-right">
<p className="text-sm text-gray-600 dark:text-gray-400 font-persian">
کل پروژهها
</p>
<p className="text-2xl font-bold text-gray-900 dark:text-white">
{projects.length}
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-2">
<div className="p-2 bg-green-100 rounded-lg dark:bg-green-900">
<Clock className="w-5 h-5 text-green-600 dark:text-green-400" />
</div>
<div className="flex-1 text-right">
<p className="text-sm text-gray-600 dark:text-gray-400 font-persian">
در حال انجام
</p>
<p className="text-2xl font-bold text-gray-900 dark:text-white">
{projects.filter((p) => p.status === "در حال انجام").length}
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-2">
<div className="p-2 bg-teal-100 rounded-lg dark:bg-teal-900">
<User className="w-5 h-5 text-teal-600 dark:text-teal-400" />
</div>
<div className="flex-1 text-right">
<p className="text-sm text-gray-600 dark:text-gray-400 font-persian">
تکمیل شده
</p>
<p className="text-2xl font-bold text-gray-900 dark:text-white">
{projects.filter((p) => p.status === "تکمیل شده").length}
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="flex items-center space-x-2">
<div className="p-2 bg-yellow-100 rounded-lg dark:bg-yellow-900">
<DollarSign className="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
</div>
<div className="flex-1 text-right">
<p className="text-sm text-gray-600 dark:text-gray-400 font-persian">
در انتظار
</p>
<p className="text-2xl font-bold text-gray-900 dark:text-white">
{projects.filter((p) => p.status === "در انتظار").length}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Filters and Search */}
<Card>
<CardHeader>
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between">
<CardTitle className="font-persian">لیست پروژهها</CardTitle>
<div className="flex flex-col sm:flex-row gap-2 w-full sm:w-auto">
<div className="relative">
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="جستجو در پروژه‌ها..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pr-10 font-persian w-full sm:w-64"
/>
</div>
<Select value={filterStatus} onValueChange={setFilterStatus}>
<SelectTrigger className="w-full sm:w-40">
<Filter className="w-4 h-4 ml-2" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="همه">همه وضعیتها</SelectItem>
<SelectItem value="در حال انجام">در حال انجام</SelectItem>
<SelectItem value="تکمیل شده">تکمیل شده</SelectItem>
<SelectItem value="در انتظار">در انتظار</SelectItem>
<SelectItem value="لغو شده">لغو شده</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="font-persian">نام پروژه</TableHead>
<TableHead className="font-persian">مدیر پروژه</TableHead>
<TableHead className="font-persian">تیم</TableHead>
<TableHead className="font-persian">وضعیت</TableHead>
<TableHead className="font-persian">اولویت</TableHead>
<TableHead className="font-persian">تاریخ شروع</TableHead>
<TableHead className="font-persian">پیشرفت</TableHead>
<TableHead className="font-persian">عملیات</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredProjects.map((project) => (
<TableRow key={project.id}>
<TableCell className="font-medium font-persian">
{project.name}
</TableCell>
<TableCell className="font-persian">
{project.manager}
</TableCell>
<TableCell className="font-persian">
{project.team}
</TableCell>
<TableCell>
<Badge
variant={
statusColors[
project.status as keyof typeof statusColors
]
}
className="font-persian"
>
{project.status}
</Badge>
</TableCell>
<TableCell>
<Badge
variant={
priorityColors[
project.priority as keyof typeof priorityColors
]
}
className="font-persian"
>
{project.priority}
</Badge>
</TableCell>
<TableCell className="font-persian">
{project.startDate}
</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<div className="w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{ width: `${project.progress}%` }}
></div>
</div>
<span className="text-sm text-gray-600 dark:text-gray-400 min-w-[3rem]">
{project.progress}%
</span>
</div>
</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel className="font-persian">
عملیات
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem className="font-persian">
<Eye className="ml-2 h-4 w-4" />
مشاهده جزئیات
</DropdownMenuItem>
<DropdownMenuItem
className="font-persian"
onClick={() => handleEditProject(project)}
>
<Edit className="ml-2 h-4 w-4" />
ویرایش
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="font-persian text-red-600"
onClick={() => handleDeleteProject(project.id)}
>
<Trash2 className="ml-2 h-4 w-4" />
حذف
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{filteredProjects.length === 0 && (
<div className="text-center py-8">
<p className="text-gray-500 dark:text-gray-400 font-persian">
هیچ پروژهای یافت نشد.
</p>
</div>
)}
</CardContent>
</Card>
</div>
</DashboardLayout>
);
}
export default ProjectsPage;