2025-09-17 09:46:58 +00:00
|
|
|
|
import React, { useState } from 'react'
|
|
|
|
|
|
import { PsProjectTask, TaskTypeEnum, TaskStatusEnum } from '../../../types/ps'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
import {
|
|
|
|
|
|
FaSearch,
|
|
|
|
|
|
FaPlus,
|
|
|
|
|
|
FaEdit,
|
|
|
|
|
|
FaEye,
|
|
|
|
|
|
FaUser,
|
|
|
|
|
|
FaBullseye,
|
|
|
|
|
|
FaTimesCircle,
|
|
|
|
|
|
FaSave,
|
|
|
|
|
|
FaCalendarAlt,
|
|
|
|
|
|
FaFlag,
|
|
|
|
|
|
FaProjectDiagram,
|
|
|
|
|
|
FaUserCog,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
} 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'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
import {
|
|
|
|
|
|
getTaskStatusColor,
|
|
|
|
|
|
getTaskStatusIcon,
|
|
|
|
|
|
getPriorityColor,
|
|
|
|
|
|
getTaskTypeColor,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
getPriorityText,
|
|
|
|
|
|
getTaskStatusText,
|
|
|
|
|
|
getTaskTypeText,
|
|
|
|
|
|
} from '../../../utils/erp'
|
|
|
|
|
|
import { Container } from '@/components/shared'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const ProjectTasks: React.FC = () => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
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)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Form state for creating/editing tasks
|
|
|
|
|
|
const [formData, setFormData] = useState({
|
2025-09-17 09:46:58 +00:00
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
projectId: '',
|
|
|
|
|
|
phaseId: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
taskType: TaskTypeEnum.Development,
|
|
|
|
|
|
status: TaskStatusEnum.NotStarted,
|
|
|
|
|
|
priority: PriorityEnum.Normal,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
assignedTo: '',
|
|
|
|
|
|
assigneeEmail: '',
|
|
|
|
|
|
startDate: '',
|
|
|
|
|
|
endDate: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
estimatedHours: 0,
|
|
|
|
|
|
progress: 0,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const filteredTasks = mockProjectTasks.filter((task) => {
|
|
|
|
|
|
const matchesSearch =
|
|
|
|
|
|
task.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
|
|
|
|
task.taskCode.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(task.description && task.description.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
|
|
|
|
const matchesStatus = statusFilter === '' || task.status === statusFilter
|
2025-09-15 09:31:47 +00:00
|
|
|
|
const matchesProject =
|
2025-09-17 09:46:58 +00:00
|
|
|
|
selectedProjectFilter === 'All' || task.projectId === selectedProjectFilter
|
|
|
|
|
|
return matchesSearch && matchesStatus && matchesProject
|
|
|
|
|
|
})
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const openModal = (task: PsProjectTask) => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
setSelectedTask(task)
|
|
|
|
|
|
setIsModalOpen(true)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const closeModal = () => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
setSelectedTask(null)
|
|
|
|
|
|
setIsModalOpen(false)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const openCreateModal = () => {
|
|
|
|
|
|
setFormData({
|
2025-09-17 09:46:58 +00:00
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
projectId: '',
|
|
|
|
|
|
phaseId: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
taskType: TaskTypeEnum.Development,
|
|
|
|
|
|
status: TaskStatusEnum.NotStarted,
|
|
|
|
|
|
priority: PriorityEnum.Normal,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
assignedTo: '',
|
|
|
|
|
|
assigneeEmail: '',
|
|
|
|
|
|
startDate: '',
|
|
|
|
|
|
endDate: '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
estimatedHours: 0,
|
|
|
|
|
|
progress: 0,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
})
|
|
|
|
|
|
setIsCreateModalOpen(true)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const closeCreateModal = () => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
setIsCreateModalOpen(false)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const openEditModal = (task: PsProjectTask) => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
setEditingTask(task)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
setFormData({
|
|
|
|
|
|
name: task.name,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
description: task.description || '',
|
|
|
|
|
|
projectId: task.projectId || '',
|
|
|
|
|
|
phaseId: task.phaseId || '',
|
2025-09-15 09:31:47 +00:00
|
|
|
|
taskType: task.taskType,
|
|
|
|
|
|
status: task.status,
|
|
|
|
|
|
priority: task.priority,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
assignedTo: task.assignedTo || '',
|
|
|
|
|
|
assigneeEmail: task.assignee?.email || '',
|
|
|
|
|
|
startDate: task.startDate.toISOString().split('T')[0],
|
|
|
|
|
|
endDate: task.endDate.toISOString().split('T')[0],
|
2025-09-15 09:31:47 +00:00
|
|
|
|
estimatedHours: task.estimatedHours,
|
|
|
|
|
|
progress: task.progress,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
})
|
|
|
|
|
|
setIsEditModalOpen(true)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const closeEditModal = () => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
setIsEditModalOpen(false)
|
|
|
|
|
|
setEditingTask(null)
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const handleInputChange = (
|
2025-09-17 09:46:58 +00:00
|
|
|
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
) => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const { name, value } = e.target
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
// Eğer atanan kişi seçimi yapılıyorsa, e-posta adresini otomatik doldur
|
2025-09-17 09:46:58 +00:00
|
|
|
|
if (name === 'assignedTo') {
|
|
|
|
|
|
const selectedEmployee = mockEmployees.find((emp) => emp.id === value)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[name]: value,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
assigneeEmail: selectedEmployee?.email || '',
|
|
|
|
|
|
}))
|
2025-09-15 09:31:47 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[name]: value,
|
2025-09-17 09:46:58 +00:00
|
|
|
|
}))
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
2025-09-17 09:46:58 +00:00
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
e.preventDefault()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
// Here you would normally save to backend
|
2025-09-17 09:46:58 +00:00
|
|
|
|
console.log('Saving task:', formData)
|
|
|
|
|
|
alert(isEditModalOpen ? 'Görev güncellendi!' : 'Yeni görev oluşturuldu!')
|
2025-09-15 09:31:47 +00:00
|
|
|
|
if (isEditModalOpen) {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
closeEditModal()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
} else {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
closeCreateModal()
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
2025-09-17 09:46:58 +00:00
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
const generateTaskCode = () => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const taskCount = mockProjectTasks.length + 1
|
|
|
|
|
|
return `TSK-${taskCount.toString().padStart(3, '0')}`
|
|
|
|
|
|
}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
|
|
|
|
|
|
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-17 09:46:58 +00:00
|
|
|
|
<p className="text-gray-600">Proje görevlerinizi oluşturun, düzenleyin ve takip edin</p>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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"
|
2025-09-17 09:46:58 +00:00
|
|
|
|
value={mockProjectTasks.filter((t) => t.status === TaskStatusEnum.InProgress).length}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
color="yellow"
|
|
|
|
|
|
icon="FaClock"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Widget
|
|
|
|
|
|
title="Tamamlanan"
|
2025-09-17 09:46:58 +00:00
|
|
|
|
value={mockProjectTasks.filter((t) => t.status === TaskStatusEnum.Completed).length}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
color="green"
|
|
|
|
|
|
icon="FaCheckCircle"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Widget
|
|
|
|
|
|
title="Hatalar"
|
|
|
|
|
|
value={
|
|
|
|
|
|
mockProjectTasks.length > 0
|
|
|
|
|
|
? `${Math.round(
|
|
|
|
|
|
mockProjectTasks.reduce((sum, t) => sum + t.progress, 0) /
|
2025-09-17 09:46:58 +00:00
|
|
|
|
mockProjectTasks.length,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}%`
|
2025-09-17 09:46:58 +00:00
|
|
|
|
: '0%'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
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}
|
2025-09-17 09:46:58 +00:00
|
|
|
|
onChange={(e) => setStatusFilter(e.target.value as TaskStatusEnum | '')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(TaskStatusEnum).map((status) => (
|
|
|
|
|
|
<option key={status} value={status}>
|
|
|
|
|
|
{getTaskStatusText(status)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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 ${
|
2025-09-17 09:46:58 +00:00
|
|
|
|
index % 2 === 0 ? 'bg-white' : 'bg-gray-50'
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<td className="px-2 py-1 whitespace-nowrap">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="text-sm font-medium text-blue-700">
|
|
|
|
|
|
{(() => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const project = mockProjects.find((p) => p.id === task.projectId)
|
|
|
|
|
|
return project?.name || `Proje ID: ${task.projectId}`
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
|
{(() => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const project = mockProjects.find((p) => p.id === task.projectId)
|
|
|
|
|
|
return project?.code || task.projectId
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td className="px-2 py-1 whitespace-nowrap">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div className="text-sm font-medium text-green-700">
|
|
|
|
|
|
{task.phase?.name ||
|
|
|
|
|
|
(() => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const phase = mockProjectPhases.find((p) => p.id === task.phaseId)
|
|
|
|
|
|
return phase?.name || `Aşama ID: ${task.phaseId}`
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
|
{task.phase?.code ||
|
|
|
|
|
|
(() => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
const phase = mockProjectPhases.find((p) => p.id === task.phaseId)
|
|
|
|
|
|
return phase?.code || task.phaseId
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
task.status,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
task.priority,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{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">
|
2025-12-03 21:01:00 +00:00
|
|
|
|
{task.assignee?.name || '-'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{task.endDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
<h3 className="text-base font-medium text-gray-900 mb-1">Görev bulunamadı</h3>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
<p className="text-gray-500">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
Arama kriterlerinizi değiştirmeyi deneyin veya yeni bir görev oluşturun.
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(p) => p.id === selectedTask.projectId,
|
|
|
|
|
|
)
|
|
|
|
|
|
return project?.name || `Proje ID: ${selectedTask.projectId}`
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-blue-600 mt-1">
|
|
|
|
|
|
{(() => {
|
|
|
|
|
|
const project = mockProjects.find(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(p) => p.id === selectedTask.projectId,
|
|
|
|
|
|
)
|
|
|
|
|
|
return project?.code || selectedTask.projectId
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(p) => p.id === selectedTask.phaseId,
|
|
|
|
|
|
)
|
|
|
|
|
|
return phase?.name || `Aşama ID: ${selectedTask.phaseId}`
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-xs text-green-600 mt-1">
|
|
|
|
|
|
{selectedTask.phase?.code ||
|
|
|
|
|
|
(() => {
|
|
|
|
|
|
const phase = mockProjectPhases.find(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(p) => p.id === selectedTask.phaseId,
|
|
|
|
|
|
)
|
|
|
|
|
|
return phase?.code || selectedTask.phaseId
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
|
|
|
|
Açıklama
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p className="text-sm text-gray-900 bg-white p-2 rounded-lg border">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.description || 'Açıklama bulunmuyor.'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
selectedTask.taskType,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
selectedTask.status,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
selectedTask.priority,
|
2025-09-15 09:31:47 +00:00
|
|
|
|
)}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{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">
|
2025-12-03 21:01:00 +00:00
|
|
|
|
{selectedTask.assignee.name}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</p>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
<p className="text-sm text-gray-500">{selectedTask.assignee.email}</p>
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.startDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.actualStartDate?.toLocaleDateString('tr-TR') ||
|
|
|
|
|
|
'Henüz başlanmadı'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.endDate.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.actualEndDate?.toLocaleDateString('tr-TR') ||
|
|
|
|
|
|
'Henüz tamamlanmadı'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.creationTime.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{selectedTask.lastModificationTime.toLocaleDateString('tr-TR')}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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={() => {
|
2025-09-17 09:46:58 +00:00
|
|
|
|
closeModal()
|
|
|
|
|
|
openEditModal(selectedTask!)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
}}
|
|
|
|
|
|
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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{isEditModalOpen ? 'Görev Düzenle' : 'Yeni Görev Oluştur'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-xs text-gray-600">
|
|
|
|
|
|
{isEditModalOpen
|
2025-09-17 09:46:58 +00:00
|
|
|
|
? 'Mevcut görev bilgilerini güncelleyin'
|
|
|
|
|
|
: 'Yeni bir görev tanımlayın'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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="">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{formData.projectId ? 'Aşama seçin...' : 'Önce proje seçin'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</option>
|
|
|
|
|
|
{formData.projectId &&
|
|
|
|
|
|
mockProjectPhases
|
2025-09-17 09:46:58 +00:00
|
|
|
|
.filter((phase) => phase.projectId === formData.projectId)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
.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">
|
|
|
|
|
|
Açı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"
|
|
|
|
|
|
>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(TaskTypeEnum).map((type) => (
|
|
|
|
|
|
<option key={type} value={type}>
|
|
|
|
|
|
{getTaskTypeText(type)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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"
|
|
|
|
|
|
>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(PriorityEnum).map((priority) => (
|
|
|
|
|
|
<option key={priority} value={priority}>
|
|
|
|
|
|
{getPriorityText(priority)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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"
|
|
|
|
|
|
>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{Object.values(TaskStatusEnum).map((status) => (
|
|
|
|
|
|
<option key={status} value={status}>
|
|
|
|
|
|
{getTaskStatusText(status)}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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}>
|
2025-12-03 21:01:00 +00:00
|
|
|
|
{employee.name} - {employee.jobPosition?.name}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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(
|
2025-09-17 09:46:58 +00:00
|
|
|
|
(emp) => emp.id === formData.assignedTo,
|
|
|
|
|
|
)
|
2025-09-15 09:31:47 +00:00
|
|
|
|
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">
|
2025-12-03 21:01:00 +00:00
|
|
|
|
{selectedEmployee.name}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
) : null
|
2025-09-15 09:31:47 +00:00
|
|
|
|
})()}
|
|
|
|
|
|
</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">
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{isEditModalOpen ? editingTask?.taskCode : generateTaskCode()}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</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" />
|
2025-09-17 09:46:58 +00:00
|
|
|
|
{isEditModalOpen ? 'Güncelle' : 'Oluştur'}
|
2025-09-15 09:31:47 +00:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-09-15 20:27:01 +00:00
|
|
|
|
</Container>
|
2025-09-17 09:46:58 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-09-15 20:27:01 +00:00
|
|
|
|
|
2025-09-17 09:46:58 +00:00
|
|
|
|
export default ProjectTasks
|