erp-platform/ui/src/views/project/components/ProjectTasks.tsx

1166 lines
51 KiB
TypeScript
Raw Normal View History

2025-09-15 09:31:47 +00:00
import React, { useState } from "react";
import { PsProjectTask, TaskTypeEnum, TaskStatusEnum } from "../../../types/ps";
import {
FaSearch,
FaPlus,
FaEdit,
FaEye,
FaUser,
FaBullseye,
FaTimesCircle,
FaSave,
FaCalendarAlt,
FaFlag,
FaProjectDiagram,
FaUserCog,
} from "react-icons/fa";
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { mockProjects } from "../../../mocks/mockProjects";
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
import Widget from "../../../components/common/Widget";
import { PriorityEnum } from "../../../types/common";
import {
getTaskStatusColor,
getTaskStatusIcon,
getPriorityColor,
getTaskTypeColor,
} from "../../../utils/erp";
2025-09-15 20:27:01 +00:00
import { Container } from "@/components/shared";
2025-09-15 09:31:47 +00:00
const ProjectTasks: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState<TaskStatusEnum | "">("");
const [selectedProjectFilter, setSelectedProjectFilter] = useState("All");
const [selectedTask, setSelectedTask] = useState<PsProjectTask | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [editingTask, setEditingTask] = useState<PsProjectTask | null>(null);
// Form state for creating/editing tasks
const [formData, setFormData] = useState({
name: "",
description: "",
projectId: "",
phaseId: "",
taskType: TaskTypeEnum.Development,
status: TaskStatusEnum.NotStarted,
priority: PriorityEnum.Normal,
assignedTo: "",
assigneeEmail: "",
startDate: "",
endDate: "",
estimatedHours: 0,
progress: 0,
});
const filteredTasks = mockProjectTasks.filter((task) => {
const matchesSearch =
task.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.taskCode.toLowerCase().includes(searchTerm.toLowerCase()) ||
(task.description &&
task.description.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesStatus = statusFilter === "" || task.status === statusFilter;
const matchesProject =
selectedProjectFilter === "All" ||
task.projectId === selectedProjectFilter;
return matchesSearch && matchesStatus && matchesProject;
});
const openModal = (task: PsProjectTask) => {
setSelectedTask(task);
setIsModalOpen(true);
};
const closeModal = () => {
setSelectedTask(null);
setIsModalOpen(false);
};
const openCreateModal = () => {
setFormData({
name: "",
description: "",
projectId: "",
phaseId: "",
taskType: TaskTypeEnum.Development,
status: TaskStatusEnum.NotStarted,
priority: PriorityEnum.Normal,
assignedTo: "",
assigneeEmail: "",
startDate: "",
endDate: "",
estimatedHours: 0,
progress: 0,
});
setIsCreateModalOpen(true);
};
const closeCreateModal = () => {
setIsCreateModalOpen(false);
};
const openEditModal = (task: PsProjectTask) => {
setEditingTask(task);
setFormData({
name: task.name,
description: task.description || "",
projectId: task.projectId || "",
phaseId: task.phaseId || "",
taskType: task.taskType,
status: task.status,
priority: task.priority,
assignedTo: task.assignedTo || "",
assigneeEmail: task.assignee?.email || "",
startDate: task.startDate.toISOString().split("T")[0],
endDate: task.endDate.toISOString().split("T")[0],
estimatedHours: task.estimatedHours,
progress: task.progress,
});
setIsEditModalOpen(true);
};
const closeEditModal = () => {
setIsEditModalOpen(false);
setEditingTask(null);
};
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => {
const { name, value } = e.target;
// Eğer atanan kişi seçimi yapılıyorsa, e-posta adresini otomatik doldur
if (name === "assignedTo") {
const selectedEmployee = mockEmployees.find((emp) => emp.id === value);
setFormData((prev) => ({
...prev,
[name]: value,
assigneeEmail: selectedEmployee?.email || "",
}));
} else {
setFormData((prev) => ({
...prev,
[name]: value,
}));
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Here you would normally save to backend
console.log("Saving task:", formData);
alert(isEditModalOpen ? "Görev güncellendi!" : "Yeni görev oluşturuldu!");
if (isEditModalOpen) {
closeEditModal();
} else {
closeCreateModal();
}
};
const generateTaskCode = () => {
const taskCount = mockProjectTasks.length + 1;
return `TSK-${taskCount.toString().padStart(3, "0")}`;
};
return (
2025-09-15 20:27:01 +00:00
<Container>
<div className="space-y-2">
2025-09-15 09:31:47 +00:00
<div className="flex items-center justify-between mb-4">
<div>
2025-09-15 21:02:48 +00:00
<h2 className="text-2xl font-bold text-gray-900">Görev Yönetimi</h2>
2025-09-15 20:27:01 +00:00
<p className="text-gray-600">
2025-09-15 09:31:47 +00:00
Proje görevlerinizi oluşturun, düzenleyin ve takip edin
</p>
</div>
<button
onClick={openCreateModal}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2 transition-colors shadow-sm"
>
<FaPlus className="w-4 h-4" />
Yeni Görev
</button>
</div>
{/* Summary Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 mb-3">
<Widget
title="Toplam Görev"
value={mockProjectTasks.length}
color="blue"
icon="FaBullseye"
/>
<Widget
title="Devam Eden"
value={
mockProjectTasks.filter(
(t) => t.status === TaskStatusEnum.InProgress
).length
}
color="yellow"
icon="FaClock"
/>
<Widget
title="Tamamlanan"
value={
mockProjectTasks.filter(
(t) => t.status === TaskStatusEnum.Completed
).length
}
color="green"
icon="FaCheckCircle"
/>
<Widget
title="Hatalar"
value={
mockProjectTasks.length > 0
? `${Math.round(
mockProjectTasks.reduce((sum, t) => sum + t.progress, 0) /
mockProjectTasks.length
)}%`
: "0%"
}
color="red"
icon="FaBullseye"
/>
</div>
{/* Filters */}
<div className="flex flex-wrap items-center gap-2">
<div className="relative flex-1 min-w-64">
<FaSearch className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Görev ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 px-3 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="flex items-center gap-2">
<select
value={selectedProjectFilter}
onChange={(e) => setSelectedProjectFilter(e.target.value)}
className="px-2.5 py-1 text-sm border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
>
<option value="All">Tüm Projeler</option>
{mockProjects.map((op) => (
<option key={op.id} value={op.id}>
{op.code} - {op.name}
</option>
))}
</select>
<select
value={statusFilter}
onChange={(e) =>
setStatusFilter(e.target.value as TaskStatusEnum | "")
}
className="w-full px-2.5 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">Tüm Durumlar</option>
<option value={TaskStatusEnum.NotStarted}>Başlanmadı</option>
<option value={TaskStatusEnum.InProgress}>Devam Ediyor</option>
<option value={TaskStatusEnum.Completed}>Tamamlandı</option>
<option value={TaskStatusEnum.OnHold}>Beklemede</option>
<option value={TaskStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
</div>
</div>
{/* Task List */}
<div className="bg-white rounded-lg shadow-sm overflow-hidden border border-gray-200">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gradient-to-r from-gray-50 to-gray-100">
<tr>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Proje
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Aşama
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Görev
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Öncelik
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Atanan
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
İlerleme
</th>
<th className="px-2 py-1 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
Bitiş Tarihi
</th>
<th className="px-2 py-1 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredTasks.map((task, index) => (
<tr
key={task.id}
className={`hover:bg-blue-50 transition-colors duration-200 ${
index % 2 === 0 ? "bg-white" : "bg-gray-50"
}`}
>
<td className="px-2 py-1 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-blue-700">
{(() => {
const project = mockProjects.find(
(p) => p.id === task.projectId
);
return project?.name || `Proje ID: ${task.projectId}`;
})()}
</div>
<div className="text-xs text-gray-500">
{(() => {
const project = mockProjects.find(
(p) => p.id === task.projectId
);
return project?.code || task.projectId;
})()}
</div>
</div>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-green-700">
{task.phase?.name ||
(() => {
const phase = mockProjectPhases.find(
(p) => p.id === task.phaseId
);
return phase?.name || `Aşama ID: ${task.phaseId}`;
})()}
</div>
<div className="text-xs text-gray-500">
{task.phase?.code ||
(() => {
const phase = mockProjectPhases.find(
(p) => p.id === task.phaseId
);
return phase?.code || task.phaseId;
})()}
</div>
</div>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<div>
<div className="text-sm font-semibold text-gray-900 hover:text-blue-600 transition-colors">
{task.name}
</div>
<div className="text-xs text-gray-500 font-mono bg-gray-100 px-1.5 py-0.5 rounded inline-block mt-1">
{task.taskCode}
</div>
</div>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<span
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-semibold ${getTaskStatusColor(
task.status
)}`}
>
{getTaskStatusIcon(task.status)}
{task.status}
</span>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-semibold ${getPriorityColor(
task.priority
)}`}
>
{task.priority}
</span>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<div className="flex items-center">
<div className="w-6 h-6 bg-gradient-to-br from-blue-100 to-blue-200 rounded-full flex items-center justify-center mr-2">
<FaUser className="w-4 h-4 text-blue-600" />
</div>
<div className="text-sm text-gray-900 font-medium">
{task.assignee?.fullName || "-"}
</div>
</div>
</td>
<td className="px-2 py-1 whitespace-nowrap">
<div className="flex items-center">
<div className="w-20 bg-gray-200 rounded-full h-2 mr-3 shadow-inner">
<div
className="bg-gradient-to-r from-blue-500 to-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${task.progress}%` }}
/>
</div>
<span className="text-sm text-gray-900 font-semibold min-w-[3rem]">
{task.progress}%
</span>
</div>
</td>
<td className="px-2 py-1 whitespace-nowrap text-sm text-gray-900 font-medium">
{task.endDate.toLocaleDateString("tr-TR")}
</td>
<td className="px-2 py-1 whitespace-nowrap text-sm font-medium">
<div className="flex gap-1 justify-center">
<button
onClick={() => openModal(task)}
className="text-blue-600 hover:text-blue-900 p-1 rounded-lg hover:bg-blue-50 transition-colors duration-200"
title="Görüntüle"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => openEditModal(task)}
className="text-green-600 hover:text-green-900 p-1 rounded-lg hover:bg-green-50 transition-colors duration-200"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
{filteredTasks.length === 0 && (
<div className="text-center py-10">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-2">
<FaBullseye className="w-6 h-6 text-gray-400" />
</div>
<h3 className="text-base font-medium text-gray-900 mb-1">
Görev bulunamadı
</h3>
<p className="text-gray-500">
Arama kriterlerinizi değiştirmeyi deneyin veya yeni bir görev
oluşturun.
</p>
</div>
)}
</div>
{/* Task Detail Modal */}
{isModalOpen && selectedTask && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-3xl mx-4 max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-3 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-t-xl">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<FaEye className="w-4 h-4 text-white" />
</div>
<div>
<h3 className="text-lg font-bold text-gray-900">
{selectedTask.taskCode} - {selectedTask.name}
</h3>
2025-09-15 21:02:48 +00:00
<p className="text-gray-600">Görev detayları</p>
2025-09-15 09:31:47 +00:00
</div>
</div>
<button
onClick={closeModal}
className="text-gray-400 hover:text-gray-600 p-1 hover:bg-gray-100 rounded-lg transition-colors"
>
<FaTimesCircle className="w-4 h-4" />
</button>
</div>
<div className="p-3">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* Sol Kolon - Temel Bilgiler */}
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-xl">
<h4 className="text-base font-semibold mb-2 flex items-center gap-2 text-gray-800">
<FaProjectDiagram className="w-5 h-5 text-blue-600" />
Görev Bilgileri
</h4>
<div className="space-y-2.5">
<div className="grid grid-cols-1 gap-2.5">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Proje
</label>
<div className="bg-white p-2.5 rounded-lg border border-blue-200">
<div className="text-sm font-semibold text-blue-800">
{(() => {
const project = mockProjects.find(
(p) => p.id === selectedTask.projectId
);
return (
project?.name ||
`Proje ID: ${selectedTask.projectId}`
);
})()}
</div>
<div className="text-xs text-blue-600 mt-1">
{(() => {
const project = mockProjects.find(
(p) => p.id === selectedTask.projectId
);
return project?.code || selectedTask.projectId;
})()}
</div>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Aşama
</label>
<div className="bg-white p-2.5 rounded-lg border border-green-200">
<div className="text-sm font-semibold text-green-800">
{selectedTask.phase?.name ||
(() => {
const phase = mockProjectPhases.find(
(p) => p.id === selectedTask.phaseId
);
return (
phase?.name ||
`Aşama ID: ${selectedTask.phaseId}`
);
})()}
</div>
<div className="text-xs text-green-600 mt-1">
{selectedTask.phase?.code ||
(() => {
const phase = mockProjectPhases.find(
(p) => p.id === selectedTask.phaseId
);
return phase?.code || selectedTask.phaseId;
})()}
</div>
</div>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
ıklama
</label>
<p className="text-sm text-gray-900 bg-white p-2 rounded-lg border">
{selectedTask.description || "Açıklama bulunmuyor."}
</p>
</div>
<div className="grid grid-cols-2 gap-2.5">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Görev Türü
</label>
<span
className={`inline-flex items-center px-2 py-1 rounded-lg text-xs font-semibold ${getTaskTypeColor(
selectedTask.taskType
)}`}
>
{selectedTask.taskType}
</span>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Durum
</label>
<span
className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-lg text-xs font-semibold ${getTaskStatusColor(
selectedTask.status
)}`}
>
{getTaskStatusIcon(selectedTask.status)}
{selectedTask.status}
</span>
</div>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Öncelik
</label>
<span
className={`inline-flex items-center px-2 py-1 rounded-lg text-xs font-semibold ${getPriorityColor(
selectedTask.priority
)}`}
>
{selectedTask.priority}
</span>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Atanan Kişi
</label>
{selectedTask.assignee ? (
<div className="flex items-center space-x-2 bg-white p-2 rounded-lg border">
<div className="w-8 h-8 bg-gradient-to-br from-blue-100 to-blue-200 rounded-full flex items-center justify-center">
<FaUser className="w-6 h-6 text-blue-600" />
</div>
<div>
<p className="text-sm font-medium text-gray-900">
{selectedTask.assignee.fullName}
</p>
<p className="text-sm text-gray-500">
{selectedTask.assignee.email}
</p>
</div>
</div>
) : (
<p className="text-sm text-gray-500 bg-white p-2 rounded-lg border">
Atanmamış
</p>
)}
</div>
</div>
</div>
</div>
{/* Sağ Kolon - Tarih ve İlerleme */}
<div className="space-y-3">
<div className="bg-gray-50 p-3 rounded-xl">
<h4 className="text-base font-semibold mb-2 flex items-center gap-2 text-gray-800">
<FaCalendarAlt className="w-5 h-5 text-green-600" />
Tarih ve İlerleme
</h4>
<div className="space-y-2.5">
<div className="grid grid-cols-2 gap-2.5">
<div className="bg-white p-2 rounded-lg border">
<label className="block text-xs font-medium text-gray-700 mb-1">
Planlanan Başlangıç
</label>
<p className="text-sm text-gray-900 font-medium">
{selectedTask.startDate.toLocaleDateString("tr-TR")}
</p>
</div>
<div className="bg-white p-2 rounded-lg border">
<label className="block text-xs font-medium text-gray-700 mb-1">
Gerçek Başlangıç
</label>
<p className="text-sm text-gray-900 font-medium">
{selectedTask.actualStartDate?.toLocaleDateString(
"tr-TR"
) || "Henüz başlanmadı"}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-2.5">
<div className="bg-white p-2 rounded-lg border">
<label className="block text-xs font-medium text-gray-700 mb-1">
Planlanan Bitiş
</label>
<p className="text-sm text-gray-900 font-medium">
{selectedTask.endDate.toLocaleDateString("tr-TR")}
</p>
</div>
<div className="bg-white p-2 rounded-lg border">
<label className="block text-xs font-medium text-gray-700 mb-1">
Gerçek Bitiş
</label>
<p className="text-sm text-gray-900 font-medium">
{selectedTask.actualEndDate?.toLocaleDateString(
"tr-TR"
) || "Henüz tamamlanmadı"}
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-2.5">
<div className="bg-blue-50 p-2 rounded-lg border border-blue-200">
<label className="block text-xs font-medium text-blue-700 mb-1">
Tahmini Süre
</label>
<p className="text-base font-bold text-blue-600">
{selectedTask.estimatedHours} saat
</p>
</div>
<div className="bg-orange-50 p-2 rounded-lg border border-orange-200">
<label className="block text-xs font-medium text-orange-700 mb-1">
Gerçekleşen Süre
</label>
<p className="text-base font-bold text-orange-600">
{selectedTask.actualHours} saat
</p>
</div>
</div>
<div className="bg-white p-2 rounded-lg border">
<label className="block text-xs font-medium text-gray-700 mb-2">
İlerleme
</label>
<div className="flex items-center">
<div className="flex-1 bg-gray-200 rounded-full h-3 mr-3">
<div
className="bg-gradient-to-r from-blue-500 to-blue-600 h-4 rounded-full flex items-center justify-center transition-all duration-300"
style={{ width: `${selectedTask.progress}%` }}
>
{selectedTask.progress > 20 && (
<span className="text-xs font-medium text-white">
{selectedTask.progress}%
</span>
)}
</div>
</div>
{selectedTask.progress <= 20 && (
<span className="text-sm font-medium text-gray-900">
{selectedTask.progress}%
</span>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-2.5 pt-2 border-t">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Oluşturulma
</label>
<p className="text-sm text-gray-500">
{selectedTask.creationTime.toLocaleDateString(
"tr-TR"
)}
</p>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">
Son Güncelleme
</label>
<p className="text-sm text-gray-500">
{selectedTask.lastModificationTime.toLocaleDateString(
"tr-TR"
)}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-2 p-3 border-t border-gray-200 bg-gray-50 rounded-b-xl">
<button
onClick={closeModal}
className="px-3 py-1 text-sm text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors flex items-center gap-1.5"
>
<FaTimesCircle className="w-4 h-4" />
Kapat
</button>
<button
onClick={() => {
closeModal();
openEditModal(selectedTask!);
}}
className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-1.5 shadow-sm"
>
<FaEdit className="w-4 h-4" />
Düzenle
</button>
</div>
</div>
</div>
)}
{/* Create/Edit Task Modal */}
{(isCreateModalOpen || isEditModalOpen) && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex items-center justify-between p-2.5 border-b border-gray-200 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-t-xl">
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-blue-600 rounded-md flex items-center justify-center">
{isEditModalOpen ? (
<FaEdit className="w-3.5 h-3.5 text-white" />
) : (
<FaPlus className="w-5 h-5 text-white" />
)}
</div>
<div>
<h3 className="text-lg font-bold text-gray-900">
{isEditModalOpen ? "Görev Düzenle" : "Yeni Görev Oluştur"}
</h3>
<p className="text-xs text-gray-600">
{isEditModalOpen
? "Mevcut görev bilgilerini güncelleyin"
: "Yeni bir görev tanımlayın"}
</p>
</div>
</div>
<button
onClick={isEditModalOpen ? closeEditModal : closeCreateModal}
className="text-gray-400 hover:text-gray-600 p-1 hover:bg-gray-100 rounded-lg transition-colors"
>
<FaTimesCircle className="w-4 h-4" />
</button>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="p-2.5">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-2.5">
{/* Sol Kolon */}
<div className="space-y-2.5">
<div className="bg-gray-50 p-2.5 rounded-lg">
<h4 className="text-base font-semibold text-gray-800 mb-2 flex items-center gap-2">
<FaProjectDiagram className="w-5 h-5 text-blue-600" />
Temel Bilgiler
</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Proje <span className="text-red-500">*</span>
</label>
<select
name="projectId"
value={formData.projectId}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
required
>
<option value="">Proje seçin...</option>
{mockProjects.map((project) => (
<option key={project.id} value={project.id}>
{project.code} - {project.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Aşama <span className="text-red-500">*</span>
</label>
<select
name="phaseId"
value={formData.phaseId}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
required
disabled={!formData.projectId}
>
<option value="">
{formData.projectId
? "Aşama seçin..."
: "Önce proje seçin"}
</option>
{formData.projectId &&
mockProjectPhases
.filter(
(phase) =>
phase.projectId === formData.projectId
)
.map((phase) => (
<option key={phase.id} value={phase.id}>
{phase.code} - {phase.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Görev Adı <span className="text-red-500">*</span>
</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
placeholder="Görev adını girin..."
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama
</label>
<textarea
name="description"
value={formData.description}
onChange={handleInputChange}
rows={2}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
placeholder="Görev açıklamasını girin..."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Görev Türü
</label>
<select
name="taskType"
value={formData.taskType}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
>
<option value={TaskTypeEnum.Development}>
Geliştirme
</option>
<option value={TaskTypeEnum.Testing}>Test</option>
<option value={TaskTypeEnum.Documentation}>
Dokümantasyon
</option>
<option value={TaskTypeEnum.Review}>İnceleme</option>
<option value={TaskTypeEnum.Deployment}>
Dağıtım
</option>
<option value={TaskTypeEnum.Meeting}>Toplantı</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Öncelik
</label>
<select
name="priority"
value={formData.priority}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
>
<option value={PriorityEnum.Low}>Düşük</option>
<option value={PriorityEnum.Normal}>Normal</option>
<option value={PriorityEnum.High}>Yüksek</option>
<option value={PriorityEnum.Urgent}>Acil</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Durum
</label>
<select
name="status"
value={formData.status}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
>
<option value={TaskStatusEnum.NotStarted}>
Başlanmadı
</option>
<option value={TaskStatusEnum.InProgress}>
Devam Ediyor
</option>
<option value={TaskStatusEnum.Completed}>
Tamamlandı
</option>
<option value={TaskStatusEnum.OnHold}>
Beklemede
</option>
<option value={TaskStatusEnum.Cancelled}>
İptal Edildi
</option>
</select>
</div>
</div>
</div>
</div>
{/* Sağ Kolon */}
<div className="space-y-2.5">
<div className="bg-gray-50 p-2.5 rounded-lg">
<h4 className="text-base font-semibold text-gray-800 mb-2 flex items-center gap-2">
<FaUserCog className="w-5 h-5 text-green-600" />
Atama ve Zaman
</h4>
<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Atanan Kişi <span className="text-red-500">*</span>
</label>
<select
name="assignedTo"
value={formData.assignedTo}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
>
<option value="">Çalışan seçin...</option>
{mockEmployees
.filter((emp) => emp.isActive)
.map((employee) => (
<option key={employee.id} value={employee.id}>
{employee.fullName} -{" "}
{employee.jobPosition?.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
E-posta
</label>
<input
type="email"
name="assigneeEmail"
value={formData.assigneeEmail}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors bg-gray-50"
placeholder="E-posta adresi otomatik doldurulacak..."
readOnly
/>
</div>
{/* Seçili Çalışan Bilgileri */}
{formData.assignedTo && (
<div className="bg-blue-50 p-2 rounded-lg border border-blue-200">
{(() => {
const selectedEmployee = mockEmployees.find(
(emp) => emp.id === formData.assignedTo
);
return selectedEmployee ? (
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gradient-to-br from-blue-100 to-blue-200 rounded-full flex items-center justify-center">
<FaUser className="w-6 h-6 text-blue-600" />
</div>
<div>
<h4 className="font-semibold text-blue-900">
{selectedEmployee.fullName}
</h4>
<p className="text-xs text-blue-700">
{selectedEmployee.jobPosition?.name}
</p>
<p className="text-xs text-blue-600">
{selectedEmployee.department?.name}
</p>
</div>
</div>
) : null;
})()}
</div>
)}
<div className="grid grid-cols-2 gap-2.5">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<FaCalendarAlt className="inline w-4 h-4 mr-1" />
Başlangıç Tarihi
</label>
<input
type="date"
name="startDate"
value={formData.startDate}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<FaCalendarAlt className="inline w-4 h-4 mr-1" />
Bitiş Tarihi
</label>
<input
type="date"
name="endDate"
value={formData.endDate}
onChange={handleInputChange}
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Tahmini Süre (Saat)
</label>
<input
type="number"
name="estimatedHours"
value={formData.estimatedHours}
onChange={handleInputChange}
min="0"
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
placeholder="0"
/>
</div>
{isEditModalOpen && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İlerleme (%)
</label>
<div className="flex items-center gap-2">
<input
type="range"
name="progress"
value={formData.progress}
onChange={handleInputChange}
min="0"
max="100"
className="flex-1"
/>
<span className="w-12 text-sm font-medium text-gray-700">
{formData.progress}%
</span>
</div>
<div className="mt-2 w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${formData.progress}%` }}
/>
</div>
</div>
)}
</div>
</div>
{/* Görev Kodu Gösterimi */}
<div className="bg-blue-50 p-2.5 rounded-lg border border-blue-200">
<div className="flex items-center gap-1.5 text-blue-800">
<FaFlag className="w-4 h-4" />
<span className="text-sm font-medium">Görev Kodu:</span>
<span className="font-bold">
{isEditModalOpen
? editingTask?.taskCode
: generateTaskCode()}
</span>
</div>
</div>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-2 mt-3 pt-2.5 border-t border-gray-200">
<button
type="button"
onClick={isEditModalOpen ? closeEditModal : closeCreateModal}
className="px-3 py-1 text-sm text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors flex items-center gap-1.5"
>
<FaTimesCircle className="w-4 h-4" />
İptal
</button>
<button
type="submit"
className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-1.5 shadow-md"
>
<FaSave className="w-4 h-4" />
{isEditModalOpen ? "Güncelle" : "Oluştur"}
</button>
</div>
</form>
</div>
</div>
)}
2025-09-15 20:27:01 +00:00
</Container>
2025-09-15 09:31:47 +00:00
);
};
export default ProjectTasks;