erp-platform/ui/src/views/project/components/ProjectTasks.tsx
2025-12-04 00:01:00 +03:00

1102 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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 { 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,
getPriorityText,
getTaskStatusText,
getTaskTypeText,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
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 (
<Container>
<div className="space-y-2">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-2xl font-bold text-gray-900">Görev Yönetimi</h2>
<p className="text-gray-600">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>
{Object.values(TaskStatusEnum).map((status) => (
<option key={status} value={status}>
{getTaskStatusText(status)}
</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?.name || '-'}
</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>
<p className="text-gray-600">Görev detayları</p>
</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.name}
</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"
>
{Object.values(TaskTypeEnum).map((type) => (
<option key={type} value={type}>
{getTaskTypeText(type)}
</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"
>
{Object.values(PriorityEnum).map((priority) => (
<option key={priority} value={priority}>
{getPriorityText(priority)}
</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"
>
{Object.values(TaskStatusEnum).map((status) => (
<option key={status} value={status}>
{getTaskStatusText(status)}
</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.name} - {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.name}
</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>
)}
</Container>
)
}
export default ProjectTasks