From 86f5622bddcdc544876417936252d3a558a90b36 Mon Sep 17 00:00:00 2001 From: Saeed Abadiyan Date: Sun, 31 Aug 2025 04:12:05 +0330 Subject: [PATCH] delete the useless files ,also update the chart and canvas --- ROUTER_SHADCN_IMPLEMENTATION.md | 4 - app/components/dashboard/d3-image-info.tsx | 304 +++----- app/components/dashboard/dashboard-home.tsx | 90 ++- .../dashboard/interactive-bar-chart.tsx | 119 ++-- .../dashboard/projects/project-detail.tsx | 354 ---------- .../dashboard/projects/projects-page.tsx | 663 ------------------ app/lib/api.ts | 16 - app/routes.ts | 11 +- app/routes/projects.tsx | 18 - public/abniro.png | Bin 0 -> 90677 bytes public/besparan.png | Bin 0 -> 95677 bytes public/faravash1.png | Bin 0 -> 98320 bytes public/faravash2.png | Bin 0 -> 98320 bytes public/khwarazmi.png | Bin 0 -> 90677 bytes public/kimia.png | Bin 0 -> 96924 bytes 15 files changed, 257 insertions(+), 1322 deletions(-) delete mode 100644 app/components/dashboard/projects/project-detail.tsx delete mode 100644 app/components/dashboard/projects/projects-page.tsx delete mode 100644 app/routes/projects.tsx create mode 100644 public/abniro.png create mode 100644 public/besparan.png create mode 100644 public/faravash1.png create mode 100644 public/faravash2.png create mode 100644 public/khwarazmi.png create mode 100644 public/kimia.png diff --git a/ROUTER_SHADCN_IMPLEMENTATION.md b/ROUTER_SHADCN_IMPLEMENTATION.md index 02d0fac..cd3cac1 100644 --- a/ROUTER_SHADCN_IMPLEMENTATION.md +++ b/ROUTER_SHADCN_IMPLEMENTATION.md @@ -125,7 +125,6 @@ export default [ index("routes/home.tsx"), // / route("login", "routes/login.tsx"), // /login route("dashboard", "routes/dashboard.tsx"), // /dashboard - route("dashboard/projects", "routes/dashboard.projects.tsx"), // /dashboard/projects route("404", "routes/404.tsx"), // /404 route("unauthorized", "routes/unauthorized.tsx"), // /unauthorized route("*", "routes/$.tsx"), // Catch-all for 404s @@ -257,8 +256,6 @@ useEffect(() => { ```tsx // app/components/dashboard/dashboard-layout.tsx ``` @@ -298,7 +295,6 @@ Both shadcn/ui components and React Router navigation are fully responsive: {/* Mobile-friendly navigation */} {/* Mobile logo for small screens */} diff --git a/app/components/dashboard/d3-image-info.tsx b/app/components/dashboard/d3-image-info.tsx index becf542..f14ede0 100644 --- a/app/components/dashboard/d3-image-info.tsx +++ b/app/components/dashboard/d3-image-info.tsx @@ -1,40 +1,26 @@ -"use client"; - import React, { useEffect, useRef, useState } from "react"; import * as d3 from "d3"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "~/components/ui/dialog"; +import { formatNumber } from "~/lib/utils"; -export type D3ImageInfoProps = { - imageUrl?: string; - title?: string; - description?: string; - width?: number; // fallback width if container size not measured yet - height?: number; // fallback height +export type CompanyInfo = { + id: string; + imageUrl: string; + name: string; + costReduction: number; // absolute value + revenue?: number; + capacity?: number; }; -/** - * D3ImageInfo - * - Renders an image and an information box beside it using D3 within an SVG. - * - Includes a clickable "show" chip that opens a popup dialog with more details. - */ -export function D3ImageInfo({ - imageUrl = "/placeholder.svg", - title = "عنوان آیتم", - description = "توضیحات تکمیلی در مورد این آیتم در این قسمت نمایش داده می‌شود.", - width = 800, - height = 360, -}: D3ImageInfoProps) { +export type D3ImageInfoProps = { + companies: CompanyInfo[]; // exactly 6 items + width?: number; + height?: number; +}; + +export function D3ImageInfo({ companies, width = 900, height = 400 }: D3ImageInfoProps) { const containerRef = useRef(null); const svgRef = useRef(null); - const [open, setOpen] = useState(false); - // Redraw helper const draw = () => { if (!containerRef.current || !svgRef.current) return; @@ -42,200 +28,130 @@ export function D3ImageInfo({ const svg = d3.select(svgRef.current); const W = Math.max(480, container.clientWidth || width); - const H = Math.max(260, height); + const H = Math.max(300, height); svg.attr("width", W).attr("height", H); - // Clear previous content svg.selectAll("*").remove(); - // Layout - const padding = 16; - const imageAreaWidth = Math.min(300, Math.max(220, W * 0.35)); - const infoAreaX = padding + imageAreaWidth + padding; - const infoAreaWidth = W - infoAreaX - padding; + const padding = 10; + const cols = 3; + const rows = 2; + const boxWidth = (W - padding * (cols + 1)) / cols; + const boxHeight = (H - padding * (rows + 1)) / rows; - // Image area (with rounded border) - const imgGroup = svg - .append("g") - .attr("transform", `translate(${padding}, ${padding})`); + const group = svg.append("g").attr("transform", `translate(${padding}, ${padding})`); - const imgW = imageAreaWidth; - const imgH = H - 2 * padding; + companies.forEach((company, i) => { + const col = i % cols; + const row = Math.floor(i / cols); + const x = col * (boxWidth + padding); + const y = row * (boxHeight + padding); - // Frame - imgGroup - .append("rect") - .attr("width", imgW) - .attr("height", imgH) - .attr("rx", 10) - .attr("ry", 10) - .attr("fill", "#1F2937") // gray-800 - .attr("stroke", "#4B5563") // gray-600 - .attr("stroke-width", 1.5); + const companyGroup = group.append("g").attr("transform", `translate(${x}, ${y})`); - // Image - imgGroup - .append("image") - .attr("href", imageUrl) - .attr("x", 4) - .attr("y", 4) - .attr("width", imgW - 8) - .attr("height", imgH - 8) - .attr("preserveAspectRatio", "xMidYMid slice") - .attr("clip-path", null); + // Draw background box + companyGroup + .append("rect") + .attr("width", boxWidth) + .attr("height", boxHeight) + .attr("rx", 10) + .attr("ry", 10) + .attr("fill", "transparent") - // Info area - const infoGroup = svg - .append("g") - .attr("transform", `translate(${infoAreaX}, ${padding})`); + // Draw image + const imgSize = Math.min(boxWidth, boxHeight) * 0.5; + companyGroup + .append("image") + .attr("href", company.imageUrl) + .attr("x", 10) + .attr("y", 10) + .attr("width", imgSize) + .attr("height", imgSize) + .attr("preserveAspectRatio", "xMidYMid slice") + .style("background", "transparent"); - // Info container - infoGroup - .append("rect") - .attr("width", Math.max(220, infoAreaWidth)) - .attr("height", imgH) - .attr("rx", 12) - .attr("ry", 12) - .attr("fill", "url(#infoGradient)") - .attr("stroke", "#6B7280") // gray-500 - .attr("stroke-width", 1); + // Adjust positions to match picture + // Position image slightly left and info box to right with spacing + const infoX = imgSize + 30; + const infoY = 10; + const infoWidth = 120; + const infoHeight = imgSize; - // Background gradient - const defs = svg.append("defs"); - const gradient = defs - .append("linearGradient") - .attr("id", "infoGradient") - .attr("x1", "0%") - .attr("y1", "0%") - .attr("x2", "0%") - .attr("y2", "100%"); + const infoGroup = companyGroup.append("g"); - gradient.append("stop").attr("offset", "0%").attr("stop-color", "#111827"); // gray-900 - gradient - .append("stop") - .attr("offset", "100%") - .attr("stop-color", "#374151"); // gray-700 + infoGroup + .append("rect") + .attr("x", infoX) + .attr("y", infoY) + .attr("width", infoWidth) + .attr("height", infoHeight) + .attr("rx", 10) + .attr("ry", 10) + .attr("fill", "transparent") + .attr("stroke", "#3F415A") + .attr("stroke-width", 1); - // Title - infoGroup - .append("text") - .attr("x", 16) - .attr("y", 36) - .attr("fill", "#F9FAFB") // gray-50 - .attr("font-weight", 700) - .attr("font-size", 18) - .text(title); - - // Description (wrapped) - const wrapText = (text: string, maxWidth: number) => { - const words = text.split(/\s+/).reverse(); - const lines: string[] = []; - let line: string[] = []; - let t = ""; - while (words.length) { - const word = words.pop()!; - const test = (t + " " + word).trim(); - // Approximate measure using character count - const tooLong = test.length * 8 > maxWidth; // 8px avg char width - if (tooLong && t.length) { - lines.push(t); - t = word; - } else { - t = test; - } - } - if (t) lines.push(t); - return lines; - }; - - const descMaxWidth = Math.max(200, infoAreaWidth - 32); - const descLines = wrapText(description, descMaxWidth); - - descLines.forEach((line, i) => { + // Add text inside info box + const lineHeight = 20; infoGroup .append("text") - .attr("x", 16) - .attr("y", 70 + i * 22) - .attr("fill", "#E5E7EB") // gray-200 + .attr("x", imgSize + 10 ) + .attr("y", infoY + imgSize + 10) + .attr("fill", "#FFFFFF") + .attr("font-weight", "700") .attr("font-size", 14) - .text(line); + .text(company.name); + + infoGroup + .append("text") + .attr("x", infoX + imgSize) + .attr("y", infoY + lineHeight ) + .attr("fill", "#FFFFFF") + .attr("font-size", 12) + .text(`درآمد: ${formatNumber(company?.revenue || "0")}`); +infoGroup + .append("text") + .attr("x", infoX + imgSize -20 ) + .attr("y", infoY + lineHeight +5 ) + .attr("fill", "#ACACAC") + .attr("font-size", 6) + .text(`میلیون ریال`); + + + infoGroup + .append("text") + .attr("x", infoX + imgSize) + .attr("y", infoY + lineHeight *2 ) + .attr("fill", "#FFFFFF") + .attr("font-size", 12) + .text(`هزینه: ${formatNumber(company?.cost || "0")} میلیون ریال`); + + infoGroup + .append("text") + .attr("x", infoX + imgSize) + .attr("y", infoY + lineHeight * 3 ) + .attr("fill", "#FFFFFF") + .attr("font-size", 12) + .text(`ظرفیت: ${formatNumber(company?.capacity || "0")} تن در سال`); + + // Remove click handlers and popup + companyGroup.style("cursor", "default"); }); - - // Show button-like chip - const chipY = Math.min(imgH - 48, 70 + descLines.length * 22 + 16); - const chip = infoGroup - .append("g") - .attr("class", "show-chip") - .style("cursor", "pointer"); - - const chipW = 120; - const chipH = 36; - - chip - .append("rect") - .attr("x", 16) - .attr("y", chipY) - .attr("width", chipW) - .attr("height", chipH) - .attr("rx", 8) - .attr("ry", 8) - .attr("fill", "#3B82F6") // blue-500 - .attr("stroke", "#60A5FA") - .attr("stroke-width", 1.5) - .attr("opacity", 0.95); - - chip - .append("text") - .attr("x", 16 + chipW / 2) - .attr("y", chipY + chipH / 2 + 5) - .attr("text-anchor", "middle") - .attr("fill", "#FFFFFF") - .attr("font-weight", 700) - .text("نمایش"); - - // Hover & click - chip - .on("mouseenter", function () { - d3.select(this).select("rect").attr("fill", "#2563EB"); // blue-600 - }) - .on("mouseleave", function () { - d3.select(this).select("rect").attr("fill", "#3B82F6"); // blue-500 - }) - .on("click", () => setOpen(true)); }; useEffect(() => { + draw(); const ro = new ResizeObserver(() => draw()); if (containerRef.current) ro.observe(containerRef.current); - draw(); return () => ro.disconnect(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [imageUrl, title, description]); + }, [companies, width, height]); return (
-
+
- - - - - {title} - - {description} - - -
- {title} -
-
-
); } diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index bcf8b47..f1ef356 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { DashboardLayout } from "./layout"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Progress } from "~/components/ui/progress"; @@ -47,6 +47,11 @@ export function DashboardHome() { const [dashboardData, setDashboardData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + // Chart and schematic data from select API + const [companyChartData, setCompanyChartData] = useState< + { category: string; capacity: number; revenue: number; cost: number }[] + >([]); + const [totalIncreasedCapacity, setTotalIncreasedCapacity] = useState(0); useEffect(() => { fetchDashboardData(); @@ -90,6 +95,63 @@ export function DashboardHome() { chartData: leftCardsResponseData?.chartData || [], }; setDashboardData(realData); + + // Fetch company aggregates for chart and schematic (select API) + const selectPayload = { + ProcessName: "project", + OutputFields: [ + "related_company", + "sum(pre_innovation_fee)", + "sum(innovation_cost_reduction)", + "sum(pre_project_production_capacity)", + "sum(increased_capacity_after_innovation)", + "sum(pre_project_income)", + "sum(increased_income_after_innovation)", + ], + GroupBy: ["related_company"], + }; + + const selectResp = await apiService.select(selectPayload); + const selectDataRaw = ((): any => { + try { + return typeof selectResp?.data === "string" + ? JSON.parse(selectResp.data) + : selectResp?.data; + } catch { + return []; + } + })(); + + const rows: any[] = Array.isArray(selectDataRaw) ? selectDataRaw : []; + let incCapacityTotal = 0; + const chartRows = rows.map((r) => { + const rel = r?.related_company ?? "-"; + const preFee = Number(r?.pre_innovation_fee_sum ?? 0); + const costRed = Number(r?.innovation_cost_reduction_sum ?? 0); + const preCap = Number(r?.pre_project_production_capacity_sum ?? 0); + const incCap = Number(r?.increased_capacity_after_innovation_sum ?? 0); + const preInc = Number(r?.pre_project_income_sum ?? 0); + const incInc = Number(r?.increased_income_after_innovation_sum ?? 0); + + incCapacityTotal += incCap; + + const capacityPct = preCap > 0 ? (incCap / preCap) * 100 : 0; + const revenuePct = preInc > 0 ? (incInc / preInc) * 100 : 0; + const costPct = preFee > 0 ? (costRed / preFee) * 100 : 0; + + return { + category: rel, + capacity: isFinite(capacityPct) ? capacityPct : 0, + revenue: isFinite(revenuePct) ? revenuePct : 0, + cost: isFinite(costPct) ? costPct : 0, + costI : costRed, + capacityI : incCap, + revenueI : incInc + }; + }); + + setCompanyChartData(chartRows); + setTotalIncreasedCapacity(incCapacityTotal); } catch (error) { console.error("Error fetching dashboard data:", error); const errorMessage = @@ -634,15 +696,33 @@ export function DashboardHome() {
- +
{ + const imageMap: Record = { + "بسپاران": "/besparan.png", + "خوارزمی": "/khwarazmi.png", + "فراورش 1": "/faravash1.png", + "فراورش 2": "/faravash2.png", + "کیمیا": "/kimia.png", + "آب نیرو": "/abniro.png", + }; + + return { + id: item.category, + name: item.category, + imageUrl: imageMap[item.category] || "/placeholder.png", + cost: item?.costI || 0, + capacity: item?.capacityI || 0, + revenue: item?.revenueI || 0, + }; + }) + } />
diff --git a/app/components/dashboard/interactive-bar-chart.tsx b/app/components/dashboard/interactive-bar-chart.tsx index 703e448..4632cf7 100644 --- a/app/components/dashboard/interactive-bar-chart.tsx +++ b/app/components/dashboard/interactive-bar-chart.tsx @@ -1,70 +1,52 @@ -import { Bar, BarChart, CartesianGrid, XAxis, YAxis, LabelList } from "recharts"; -import React, { useState, useEffect } from "react"; - import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "~/components/ui/card"; + Bar, + BarChart, + CartesianGrid, + XAxis, + YAxis, + LabelList, +} from "recharts"; +import { Card, CardContent } from "~/components/ui/card"; import { type ChartConfig, ChartContainer, - ChartTooltip, - ChartTooltipContent, } from "~/components/ui/chart"; -export const description = "An interactive bar chart"; - -const chartData = [ - { category: "کیمیا", ideas: 12, revenue: 850, cost: 320 }, - { category: "ُفرآروزش", ideas: 19, revenue: 1200, cost: 450 }, - { category: "خوارزمی", ideas: 15, revenue: 1400, cost: 520 }, -]; +export type CompanyChartDatum = { + category: string; // related_company + capacity: number; // percentage + revenue: number; // percentage + cost: number; // percentage +}; const chartConfig = { - ideas: { - label: "ایده‌ها", + capacity: { + label: "افزایش ظرفیت", color: "#60A5FA", // Blue-400 }, revenue: { - label: "درآمد (میلیون)", + label: "افزایش درآمد", color: "#4ADE80", // Green-400 }, cost: { - label: "کاهش هزینه (میلیون)", + label: "کاهش هزینه", color: "#F87171", // Red-400 }, } satisfies ChartConfig; -export function InteractiveBarChart() { - const [activeChart, setActiveChart] = - React.useState("ideas"); - - const total = React.useMemo( - () => ({ - ideas: chartData.reduce((acc, curr) => acc + curr.ideas, 0), - revenue: chartData.reduce((acc, curr) => acc + curr.revenue, 0), - cost: chartData.reduce((acc, curr) => acc + curr.cost, 0), - }), - [], - ); - +export function InteractiveBarChart({ + data, +}: { + data: CompanyChartDatum[]; +}) { return ( - + @@ -84,41 +66,58 @@ export function InteractiveBarChart() { tick={{ fill: "#94a3b8", fontSize: 12 }} tickFormatter={(value) => `${value}%`} /> - + `${Math.round(v)}%`} /> - + `${Math.round(v)}%`} /> - + `${Math.round(v)}%`} /> + + {/* Legend below chart */} +
+
+
+ {chartConfig.capacity.label} +
+
+
+ {chartConfig.cost.label} +
+
+
+ {chartConfig.revenue.label} +
+ +
); diff --git a/app/components/dashboard/projects/project-detail.tsx b/app/components/dashboard/projects/project-detail.tsx deleted file mode 100644 index e7c6802..0000000 --- a/app/components/dashboard/projects/project-detail.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import React from "react"; -import { DashboardLayout } from "../layout"; -import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; -import { Button } from "~/components/ui/button"; -import { Badge } from "~/components/ui/badge"; -import { - ArrowRight, - Calendar, - User, - Users, - DollarSign, - Clock, - FileText, - Edit, - Trash2, -} from "lucide-react"; - -interface ProjectDetailProps { - projectId: string; -} - -// Mock project data -const mockProject = { - id: 1, - name: "پروژه توسعه اپلیکیشن موبایل", - manager: "علی احمدی", - team: "تیم توسعه موبایل", - status: "در حال انجام", - priority: "بالا", - startDate: "1403/01/15", - endDate: "1403/06/30", - budget: "500,000,000", - progress: 65, - description: "این پروژه شامل توسعه یک اپلیکیشن موبایل کراس پلتفرم برای مدیریت پروژه‌ها و وظایف می‌باشد. اپلیکیشن باید قابلیت‌های مختلفی از جمله مدیریت کاربران، گزارش‌گیری و نوتیفیکیشن را داشته باشد.", - teamMembers: [ - { id: 1, name: "علی احمدی", role: "مدیر پروژه", avatar: "AA" }, - { id: 2, name: "سارا کریمی", role: "توسعه‌دهنده فرانت‌اند", avatar: "SK" }, - { id: 3, name: "محمد رضایی", role: "توسعه‌دهنده بک‌اند", avatar: "MR" }, - { id: 4, name: "فاطمه موسوی", role: "طراح UI/UX", avatar: "FM" }, - ], - milestones: [ - { id: 1, title: "تحلیل نیازمندی‌ها", status: "تکمیل شده", date: "1403/01/30" }, - { id: 2, title: "طراحی رابط کاربری", status: "تکمیل شده", date: "1403/02/15" }, - { id: 3, title: "توسعه بک‌اند", status: "در حال انجام", date: "1403/04/01" }, - { id: 4, title: "توسعه فرانت‌اند", status: "در حال انجام", date: "1403/05/01" }, - { id: 5, title: "تست و رفع باگ", status: "در انتظار", date: "1403/06/01" }, - { id: 6, title: "انتشار نهایی", status: "در انتظار", date: "1403/06/30" }, - ], - tasks: [ - { id: 1, title: "پیاده‌سازی سیستم احراز هویت", assignee: "محمد رضایی", status: "در حال انجام", priority: "بالا" }, - { id: 2, title: "طراحی صفحه داشبورد", assignee: "سارا کریمی", status: "تکمیل شده", priority: "متوسط" }, - { id: 3, title: "توسعه API گزارش‌گیری", assignee: "محمد رضایی", status: "در انتظار", priority: "بالا" }, - { id: 4, title: "طراحی آیکون‌های اپلیکیشن", assignee: "فاطمه موسوی", status: "در حال انجام", priority: "پایین" }, - ], -}; - -const statusColors = { - "در حال انجام": "info", - "تکمیل شده": "success", - "در انتظار": "warning", - "لغو شده": "destructive", -} as const; - -const priorityColors = { - "بالا": "destructive", - "متوسط": "warning", - "پایین": "secondary", -} as const; - -export function ProjectDetail({ projectId }: ProjectDetailProps) { - const project = mockProject; // In real app, fetch by projectId - - return ( - -
- {/* Breadcrumb */} -
- - - - جزئیات پروژه - -
- - {/* Project Header */} -
-
-

- {project.name} -

-

- {project.description} -

-
- -
- - -
-
- - {/* Project Stats */} -
- - -
-
- -
-
-

تاریخ شروع

-

- {project.startDate} -

-
-
-
-
- - - -
-
- -
-
-

تاریخ پایان

-

- {project.endDate} -

-
-
-
-
- - - -
-
- -
-
-

بودجه

-

- {project.budget} ریال -

-
-
-
-
- - - -
-
- -
-
-

پیشرفت

-

- {project.progress}% -

-
-
-
-
-
- -
- {/* Project Details */} -
- {/* Progress Bar */} - - - پیشرفت پروژه - - -
-
- - {project.progress}% تکمیل شده - -
- - {project.status} - - - اولویت {project.priority} - -
-
-
-
-
-
-
-
- - {/* Milestones */} - - - مراحل پروژه - - -
- {project.milestones.map((milestone) => ( -
-
-
-
-

- {milestone.title} -

-

- {milestone.date} -

-
-
- - {milestone.status} - -
- ))} -
-
-
- - {/* Recent Tasks */} - - - وظایف اخیر - - -
- {project.tasks.map((task) => ( -
-
-

- {task.title} -

-

- مسئول: {task.assignee} -

-
-
- - {task.status} - - - {task.priority} - -
-
- ))} -
-
-
-
- - {/* Sidebar */} -
- {/* Project Info */} - - - اطلاعات پروژه - - -
- -
-

مدیر پروژه

-

- {project.manager} -

-
-
- -
- -
-

تیم

-

- {project.team} -

-
-
- -
- -
-

مدت زمان

-

- {project.startDate} تا {project.endDate} -

-
-
-
-
- - {/* Team Members */} - - - اعضای تیم - - -
- {project.teamMembers.map((member) => ( -
-
- {member.avatar} -
-
-

- {member.name} -

-

- {member.role} -

-
-
- ))} -
-
-
-
-
-
-
- ); -} - -export default ProjectDetail; diff --git a/app/components/dashboard/projects/projects-page.tsx b/app/components/dashboard/projects/projects-page.tsx deleted file mode 100644 index fd75675..0000000 --- a/app/components/dashboard/projects/projects-page.tsx +++ /dev/null @@ -1,663 +0,0 @@ -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(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 ( - -
- {/* Page Header */} -
-
-

- مدیریت پروژه‌ها -

-

- مدیریت و پیگیری پروژه‌های فناوری و نوآوری -

-
- - - - - - - - - {editingProject ? "ویرایش پروژه" : "پروژه جدید"} - - - {editingProject - ? "اطلاعات پروژه را ویرایش کنید." - : "اطلاعات پروژه جدید را وارد کنید."} - - - -
-
- - - setNewProject({ ...newProject, name: e.target.value }) - } - className="font-persian" - placeholder="نام پروژه را وارد کنید" - /> -
- -
- - - setNewProject({ ...newProject, manager: e.target.value }) - } - className="font-persian" - placeholder="نام مدیر پروژه" - /> -
- -
- - - setNewProject({ ...newProject, team: e.target.value }) - } - className="font-persian" - placeholder="نام تیم" - /> -
- -
-
- - -
- -
- - -
-
- -
-
- - - setNewProject({ - ...newProject, - startDate: e.target.value, - }) - } - className="font-persian" - placeholder="1403/01/01" - /> -
- -
- - - setNewProject({ - ...newProject, - endDate: e.target.value, - }) - } - className="font-persian" - placeholder="1403/06/01" - /> -
-
- -
- - - setNewProject({ ...newProject, budget: e.target.value }) - } - className="font-persian" - placeholder="500,000,000" - /> -
- -
- -