Project Management

This commit is contained in:
Sedat Öztürk 2025-09-15 23:27:01 +03:00
parent 0cb87b8295
commit 2217722243
11 changed files with 4063 additions and 4713 deletions

View file

@ -4686,70 +4686,70 @@
{ {
"key": "admin.project.list", "key": "admin.project.list",
"path": "/admin/project", "path": "/admin/projects",
"componentPath": "@/views/project/components/ProjectList", "componentPath": "@/views/project/components/ProjectList",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.new", "key": "admin.project.new",
"path": "/admin/project/new", "path": "/admin/projects/new",
"componentPath": "@/views/project/components/ProjectForm", "componentPath": "@/views/project/components/ProjectForm",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.edit", "key": "admin.project.edit",
"path": "/admin/project/edit/:id", "path": "/admin/projects/edit/:id",
"componentPath": "@/views/project/components/ProjectForm", "componentPath": "@/views/project/components/ProjectForm",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.detail", "key": "admin.project.detail",
"path": "/admin/project/:id", "path": "/admin/projects/:id",
"componentPath": "@/views/project/components/ProjectView", "componentPath": "@/views/project/components/ProjectView",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.tasks", "key": "admin.project.tasks",
"path": "/admin/project/tasks", "path": "/admin/projects/tasks",
"componentPath": "@/views/project/components/ProjectTasks", "componentPath": "@/views/project/components/ProjectTasks",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.phases", "key": "admin.project.phases",
"path": "/admin/project/phases", "path": "/admin/projects/phases",
"componentPath": "@/views/project/components/ProjectPhases", "componentPath": "@/views/project/components/ProjectPhases",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.activities", "key": "admin.project.activities",
"path": "/admin/project/activities", "path": "/admin/projects/activities",
"componentPath": "@/views/project/components/ActivityTypes", "componentPath": "@/views/project/components/ActivityTypes",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.workload", "key": "admin.project.workload",
"path": "/admin/project/workload", "path": "/admin/projects/workload",
"componentPath": "@/views/project/components/ProjectGantt", "componentPath": "@/views/project/components/ProjectGantt",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.costTracking", "key": "admin.project.costTracking",
"path": "/admin/project/cost-tracking", "path": "/admin/projects/cost-tracking",
"componentPath": "@/views/project/components/CostTimeTracking", "componentPath": "@/views/project/components/CostTimeTracking",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null
}, },
{ {
"key": "admin.project.dailyUpdates", "key": "admin.project.dailyUpdates",
"path": "/admin/project/daily-updates", "path": "/admin/projects/daily-updates",
"componentPath": "@/views/project/components/TaskDailyUpdates", "componentPath": "@/views/project/components/TaskDailyUpdates",
"routeType": "protected", "routeType": "protected",
"authority": null "authority": null

View file

@ -1,13 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from 'react'
import { FaSave, FaTimes } from "react-icons/fa"; import { FaSave, FaTimes } from 'react-icons/fa'
import { PsActivityTypeEnum, PsActivity } from "../../../types/ps"; import { PsActivityTypeEnum, PsActivity } from '../../../types/ps'
import { getPsActivityTypeText } from "../../../utils/erp"; import { getPsActivityTypeText } from '../../../utils/erp'
interface ActivityTypeFormModalProps { interface ActivityTypeFormModalProps {
open: boolean; open: boolean
activityType: PsActivity | null; activityType: PsActivity | null
onClose: () => void; onClose: () => void
onSave: (activityType: Partial<PsActivity>) => void; onSave: (activityType: Partial<PsActivity>) => void
} }
const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
@ -17,277 +17,253 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
onSave, onSave,
}) => { }) => {
const [formData, setFormData] = useState<Partial<PsActivity>>({ const [formData, setFormData] = useState<Partial<PsActivity>>({
name: "", name: '',
description: "", description: '',
category: "", category: '',
defaultDuration: 1, defaultDuration: 1,
activityType: PsActivityTypeEnum.WorkLog, activityType: PsActivityTypeEnum.WorkLog,
isActive: true, isActive: true,
}); })
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({})
useEffect(() => { useEffect(() => {
if (activityType) { if (activityType) {
setFormData({ setFormData({
...activityType, ...activityType,
}); })
} else { } else {
setFormData({ setFormData({
name: "", name: '',
description: "", description: '',
category: "", category: '',
defaultDuration: 1, defaultDuration: 1,
activityType: PsActivityTypeEnum.WorkLog, activityType: PsActivityTypeEnum.WorkLog,
isActive: true, isActive: true,
}); })
} }
setErrors({}); setErrors({})
}, [activityType]); }, [activityType])
const handleInputChange = ( const handleInputChange = (
field: keyof PsActivity, field: keyof PsActivity,
value: string | number | boolean | PsActivityTypeEnum value: string | number | boolean | PsActivityTypeEnum,
) => { ) => {
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[field]: value, [field]: value,
})); }))
// Clear error when user starts typing // Clear error when user starts typing
if (errors[field]) { if (errors[field]) {
setErrors((prev) => ({ setErrors((prev) => ({
...prev, ...prev,
[field]: "", [field]: '',
})); }))
} }
}; }
const validateForm = (): boolean => { const validateForm = (): boolean => {
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {}
if (!formData.name?.trim()) { if (!formData.name?.trim()) {
newErrors.name = "Aktivite türü adı zorunludur"; newErrors.name = 'Aktivite türü adı zorunludur'
} }
if (!formData.description?.trim()) { if (!formData.description?.trim()) {
newErrors.description = "Açıklama zorunludur"; newErrors.description = 'Açıklama zorunludur'
} }
if (!formData.category?.trim()) { if (!formData.category?.trim()) {
newErrors.category = "Kategori zorunludur"; newErrors.category = 'Kategori zorunludur'
} }
if (!formData.defaultDuration || formData.defaultDuration <= 0) { if (!formData.defaultDuration || formData.defaultDuration <= 0) {
newErrors.defaultDuration = "Varsayılan süre pozitif bir sayı olmalıdır"; newErrors.defaultDuration = 'Varsayılan süre pozitif bir sayı olmalıdır'
} }
setErrors(newErrors); setErrors(newErrors)
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0
}; }
const handleSave = () => { const handleSave = () => {
if (validateForm()) { if (validateForm()) {
onSave(formData); onSave(formData)
onClose(); onClose()
} }
}; }
if (!open) return null; if (!open) return null
return ( return (
<div className="fixed inset-0 z-50 overflow-y-auto"> <div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-2 px-2 pb-8 text-center sm:block sm:p-0"> <div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={onClose} onClick={onClose}
></div> ></div>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
<div className="inline-block w-full align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-2 sm:align-middle sm:max-w-md sm:w-full"> <div className="space-y-3">
{/* Header */}
<div className="bg-white px-3 py-2 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-base font-medium text-gray-900">
{activityType ? "Aktivite Türü Düzenle" : "Yeni Aktivite Türü"}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<FaTimes className="w-4 h-4" />
</button>
</div>
</div>
{/* Form */}
<div className="p-3">
<div className="space-y-3"> <div className="space-y-3">
{/* Activity Type */} {/* Header */}
<div> <div className="bg-white px-3 py-2 border-b border-gray-200">
<label className="block text-sm font-medium text-gray-700 mb-1"> <div className="flex items-center justify-between">
Aktivite Türü * <h3 className="text-base font-medium text-gray-900">
</label> {activityType ? 'Aktivite Türü Düzenle' : 'Yeni Aktivite Türü'}
<select </h3>
value={formData.activityType || ""} <button
onChange={(e) => onClick={onClose}
handleInputChange( className="text-gray-400 hover:text-gray-600 transition-colors"
"activityType", >
e.target.value as PsActivityTypeEnum <FaTimes className="w-4 h-4" />
) </button>
} </div>
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${ </div>
errors.activityType ? "border-red-500" : "border-gray-300"
}`} {/* Form */}
<div className="p-3">
<div className="space-y-3">
{/* Activity Type */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Aktivite Türü *
</label>
<select
value={formData.activityType || ''}
onChange={(e) =>
handleInputChange('activityType', e.target.value as PsActivityTypeEnum)
}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.activityType ? 'border-red-500' : 'border-gray-300'
}`}
>
{Object.values(PsActivityTypeEnum).map((type) => (
<option key={type} value={type}>
{getPsActivityTypeText(type)}
</option>
))}
</select>
{errors.activityType && (
<p className="mt-1 text-sm text-red-600">{errors.activityType}</p>
)}
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Ad *</label>
<input
type="text"
value={formData.name || ''}
onChange={(e) => handleInputChange('name', e.target.value)}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.name ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="Aktivite türü adını girin"
/>
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama *
</label>
<textarea
value={formData.description || ''}
onChange={(e) => handleInputChange('description', e.target.value)}
rows={2}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.description ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="Aktivite türü açıklamasını girin"
/>
{errors.description && (
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
)}
</div>
{/* Category */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kategori *
</label>
<input
type="text"
value={formData.category || ''}
onChange={(e) => handleInputChange('category', e.target.value)}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.category ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="Kategori adını girin (örn: Görev Yönetimi)"
/>
{errors.category && (
<p className="mt-1 text-sm text-red-600">{errors.category}</p>
)}
</div>
{/* Default Duration */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan Süre (saat) *
</label>
<input
type="number"
min="0"
step="0.25"
value={formData.defaultDuration || ''}
onChange={(e) =>
handleInputChange('defaultDuration', parseFloat(e.target.value))
}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.defaultDuration ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="1.0"
/>
{errors.defaultDuration && (
<p className="mt-1 text-sm text-red-600">{errors.defaultDuration}</p>
)}
</div>
{/* Active Status */}
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive || false}
onChange={(e) => handleInputChange('isActive', e.target.checked)}
className="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
Aktif durumda
</label>
</div>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-end space-x-2 px-3 py-2 border-t border-gray-200">
<button
type="button"
onClick={onClose}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
> >
{Object.values(PsActivityTypeEnum).map((type) => ( İptal
<option key={type} value={type}> </button>
{getPsActivityTypeText(type)} <button
</option> type="button"
))} onClick={handleSave}
</select> className="flex items-center space-x-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
{errors.activityType && (
<p className="mt-1 text-sm text-red-600">
{errors.activityType}
</p>
)}
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Ad *
</label>
<input
type="text"
value={formData.name || ""}
onChange={(e) => handleInputChange("name", e.target.value)}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.name ? "border-red-500" : "border-gray-300"
}`}
placeholder="Aktivite türü adını girin"
/>
{errors.name && (
<p className="mt-1 text-sm text-red-600">{errors.name}</p>
)}
</div>
{/* Description */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
ıklama *
</label>
<textarea
value={formData.description || ""}
onChange={(e) =>
handleInputChange("description", e.target.value)
}
rows={2}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.description ? "border-red-500" : "border-gray-300"
}`}
placeholder="Aktivite türü açıklamasını girin"
/>
{errors.description && (
<p className="mt-1 text-sm text-red-600">
{errors.description}
</p>
)}
</div>
{/* Category */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Kategori *
</label>
<input
type="text"
value={formData.category || ""}
onChange={(e) =>
handleInputChange("category", e.target.value)
}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.category ? "border-red-500" : "border-gray-300"
}`}
placeholder="Kategori adını girin (örn: Görev Yönetimi)"
/>
{errors.category && (
<p className="mt-1 text-sm text-red-600">{errors.category}</p>
)}
</div>
{/* Default Duration */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Varsayılan Süre (saat) *
</label>
<input
type="number"
min="0"
step="0.25"
value={formData.defaultDuration || ""}
onChange={(e) =>
handleInputChange(
"defaultDuration",
parseFloat(e.target.value)
)
}
className={`w-full px-2.5 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${
errors.defaultDuration
? "border-red-500"
: "border-gray-300"
}`}
placeholder="1.0"
/>
{errors.defaultDuration && (
<p className="mt-1 text-sm text-red-600">
{errors.defaultDuration}
</p>
)}
</div>
{/* Active Status */}
<div className="flex items-center">
<input
type="checkbox"
id="isActive"
checked={formData.isActive || false}
onChange={(e) =>
handleInputChange("isActive", e.target.checked)
}
className="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
<label
htmlFor="isActive"
className="ml-2 text-sm text-gray-700"
> >
Aktif durumda <FaSave className="w-4 h-4" />
</label> <span>Kaydet</span>
</button>
</div> </div>
</div> </div>
</div> </div>
{/* Footer */}
<div className="flex items-center justify-end space-x-2 px-3 py-2 border-t border-gray-200">
<button
type="button"
onClick={onClose}
className="px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
>
İptal
</button>
<button
type="button"
onClick={handleSave}
className="flex items-center space-x-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<FaSave className="w-4 h-4" />
<span>Kaydet</span>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
); )
}; }
export default ActivityTypeFormModal; export default ActivityTypeFormModal

View file

@ -1,108 +1,85 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import { FaSearch, FaPlus, FaEdit, FaTrash, FaEye, FaTh, FaList } from 'react-icons/fa'
FaSearch, import { PsActivity } from '../../../types/ps'
FaPlus, import { activityTypes } from '../../../mocks/mockActivityTypes'
FaEdit, import ActivityTypeFormModal from './ActivityTypeFormModal'
FaTrash, import Widget from '../../../components/common/Widget'
FaEye, import { getPsActivityTypeColor, getPsActivityTypeIcon } from '../../../utils/erp'
FaTh, import { Container } from '@/components/shared'
FaList,
} from "react-icons/fa";
import { PsActivity } from "../../../types/ps";
import { activityTypes } from "../../../mocks/mockActivityTypes";
import ActivityTypeFormModal from "./ActivityTypeFormModal";
import Widget from "../../../components/common/Widget";
import {
getPsActivityTypeColor,
getPsActivityTypeIcon,
} from "../../../utils/erp";
const ActivityTypes: React.FC = () => { const ActivityTypes: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string>(""); const [categoryFilter, setCategoryFilter] = useState<string>('')
const [selectedActivity, setSelectedActivity] = useState<string>(""); const [selectedActivity, setSelectedActivity] = useState<string>('')
const [showActivityForm, setShowActivityForm] = useState(false); const [showActivityForm, setShowActivityForm] = useState(false)
const [editingActivity, setEditingActivity] = useState<PsActivity | null>( const [editingActivity, setEditingActivity] = useState<PsActivity | null>(null)
null const [viewMode, setViewMode] = useState<'card' | 'list'>('card')
);
const [viewMode, setViewMode] = useState<"card" | "list">("card");
// Helper functions // Helper functions
const handleEdit = (activity: PsActivity) => { const handleEdit = (activity: PsActivity) => {
setEditingActivity(activity); setEditingActivity(activity)
setShowActivityForm(true); setShowActivityForm(true)
}; }
const handleNew = () => { const handleNew = () => {
setEditingActivity(null); setEditingActivity(null)
setShowActivityForm(true); setShowActivityForm(true)
}; }
const handleCloseForm = () => { const handleCloseForm = () => {
setShowActivityForm(false); setShowActivityForm(false)
setEditingActivity(null); setEditingActivity(null)
}; }
const handleSave = (activityData: Partial<PsActivity>) => { const handleSave = (activityData: Partial<PsActivity>) => {
// TODO: In a real application, this would make an API call // TODO: In a real application, this would make an API call
console.log("Saving activity type:", activityData); console.log('Saving activity type:', activityData)
if (editingActivity) { if (editingActivity) {
console.log( console.log('Updating existing activity type with ID:', editingActivity.id)
"Updating existing activity type with ID:",
editingActivity.id
);
} else { } else {
console.log("Creating new activity type"); console.log('Creating new activity type')
} }
// For now, just close the modal // For now, just close the modal
handleCloseForm(); handleCloseForm()
}; }
const categories = Array.from(new Set(activityTypes.map((a) => a.category))); const categories = Array.from(new Set(activityTypes.map((a) => a.category)))
const filteredActivities = activityTypes.filter((activity) => { const filteredActivities = activityTypes.filter((activity) => {
const matchesSearch = const matchesSearch =
activity.name.toLowerCase().includes(searchTerm.toLowerCase()) || activity.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(activity.description && (activity.description &&
activity.description.toLowerCase().includes(searchTerm.toLowerCase())); activity.description.toLowerCase().includes(searchTerm.toLowerCase()))
const matchesCategory = const matchesCategory = categoryFilter === '' || activity.category === categoryFilter
categoryFilter === "" || activity.category === categoryFilter; return matchesSearch && matchesCategory
return matchesSearch && matchesCategory; })
});
const ActivityDetailModal = () => { const ActivityDetailModal = () => {
const activity = activityTypes.find((a) => a.id === selectedActivity); const activity = activityTypes.find((a) => a.id === selectedActivity)
if (!selectedActivity || !activity) return null; if (!selectedActivity || !activity) return null
const ActivityIcon = getPsActivityTypeIcon(activity.activityType); const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
return ( return (
<div className="fixed inset-0 z-50 overflow-y-auto"> <div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => setSelectedActivity("")} onClick={() => setSelectedActivity('')}
></div> ></div>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-2 sm:align-middle sm:max-w-xl sm:w-full"> <div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
<div className="bg-white px-3 pt-3 pb-3 sm:p-4 sm:pb-3"> <div className="bg-white px-3 pt-3 pb-3 sm:p-4 sm:pb-3">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-gray-900"> <h3 className="text-lg font-medium text-gray-900">Aktivite Türü Detayları</h3>
Aktivite Türü Detayları
</h3>
<button <button
onClick={() => setSelectedActivity("")} onClick={() => setSelectedActivity('')}
className="text-gray-400 hover:text-gray-600" className="text-gray-400 hover:text-gray-600"
> >
<svg <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
className="w-5 h-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@ -117,16 +94,12 @@ const ActivityTypes: React.FC = () => {
{/* Activity Header */} {/* Activity Header */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div <div
className={`p-2 rounded-lg ${getPsActivityTypeColor( className={`p-2 rounded-lg ${getPsActivityTypeColor(activity.activityType)}`}
activity.activityType
)}`}
> >
<ActivityIcon className="w-6 h-6" /> <ActivityIcon className="w-6 h-6" />
</div> </div>
<div> <div>
<h4 className="text-lg font-medium text-gray-900"> <h4 className="text-lg font-medium text-gray-900">{activity.name}</h4>
{activity.name}
</h4>
<p className="text-sm text-gray-500">{activity.category}</p> <p className="text-sm text-gray-500">{activity.category}</p>
</div> </div>
</div> </div>
@ -134,9 +107,7 @@ const ActivityTypes: React.FC = () => {
{/* Activity Details */} {/* Activity Details */}
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<div className="bg-gray-50 rounded-lg p-2"> <div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2"> <h5 className="font-medium text-gray-900 mb-2">Genel Bilgiler</h5>
Genel Bilgiler
</h5>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div> <div>
<strong>Aktivite Türü:</strong> {activity.activityType} <strong>Aktivite Türü:</strong> {activity.activityType}
@ -145,38 +116,33 @@ const ActivityTypes: React.FC = () => {
<strong>Kategori:</strong> {activity.category} <strong>Kategori:</strong> {activity.category}
</div> </div>
<div> <div>
<strong>Varsayılan Süre:</strong>{" "} <strong>Varsayılan Süre:</strong> {activity.defaultDuration} saat
{activity.defaultDuration} saat
</div> </div>
<div> <div>
<strong>Durum:</strong> <strong>Durum:</strong>
<span <span
className={`ml-2 px-2 py-1 text-xs rounded-full ${ className={`ml-2 px-2 py-1 text-xs rounded-full ${
activity.isActive activity.isActive
? "bg-green-100 text-green-800" ? 'bg-green-100 text-green-800'
: "bg-red-100 text-red-800" : 'bg-red-100 text-red-800'
}`} }`}
> >
{activity.isActive ? "Aktif" : "Pasif"} {activity.isActive ? 'Aktif' : 'Pasif'}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div className="bg-gray-50 rounded-lg p-2"> <div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2"> <h5 className="font-medium text-gray-900 mb-2">Zaman Bilgileri</h5>
Zaman Bilgileri
</h5>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div> <div>
<strong>Oluşturulma:</strong>{" "} <strong>Oluşturulma:</strong>{' '}
{activity.creationTime.toLocaleDateString("tr-TR")} {activity.creationTime.toLocaleDateString('tr-TR')}
</div> </div>
<div> <div>
<strong>Son Güncelleme:</strong>{" "} <strong>Son Güncelleme:</strong>{' '}
{activity.lastModificationTime.toLocaleDateString( {activity.lastModificationTime.toLocaleDateString('tr-TR')}
"tr-TR"
)}
</div> </div>
</div> </div>
</div> </div>
@ -184,312 +150,285 @@ const ActivityTypes: React.FC = () => {
<div className="bg-gray-50 rounded-lg p-2"> <div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2">ıklama</h5> <h5 className="font-medium text-gray-900 mb-2">ıklama</h5>
<p className="text-sm text-gray-700"> <p className="text-sm text-gray-700">{activity.description}</p>
{activity.description}
</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
}; }
return ( return (
<div className="space-y-2 pt-2"> <Container>
<div className="flex items-center justify-between"> <div className="space-y-2">
<div> <div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-gray-900">Aktivite Türleri</h2> <div>
<p className="text-gray-600 mt-1"> <h2 className="text-xl font-bold text-gray-900">Aktivite Türleri</h2>
Proje ve görev aktivitelerinin türlerini yönetin <p className="text-gray-600 mt-1">Proje ve görev aktivitelerinin türlerini yönetin</p>
</p> </div>
</div> <div className="flex gap-1.5">
<div className="flex gap-1.5"> <div className="flex border border-gray-300 rounded-lg overflow-hidden">
<div className="flex border border-gray-300 rounded-lg overflow-hidden"> <button
onClick={() => setViewMode('card')}
className={`px-2.5 py-1.5 text-sm ${
viewMode === 'card'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
<FaTh className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('list')}
className={`px-2.5 py-1.5 text-sm ${
viewMode === 'list'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
<FaList className="w-4 h-4" />
</button>
</div>
<button <button
onClick={() => setViewMode("card")} onClick={handleNew}
className={`px-2.5 py-1.5 text-sm ${ className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
viewMode === "card"
? "bg-blue-600 text-white"
: "bg-white text-gray-700 hover:bg-gray-50"
}`}
> >
<FaTh className="w-4 h-4" /> <FaPlus className="w-4 h-4" />
</button> Yeni Aktivite Türü
<button
onClick={() => setViewMode("list")}
className={`px-2.5 py-1.5 text-sm ${
viewMode === "list"
? "bg-blue-600 text-white"
: "bg-white text-gray-700 hover:bg-gray-50"
}`}
>
<FaList className="w-4 h-4" />
</button> </button>
</div> </div>
<button
onClick={handleNew}
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2"
>
<FaPlus className="w-4 h-4" />
Yeni Aktivite Türü
</button>
</div> </div>
</div>
{/* Stats Cards */} {/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2"> <div className="grid grid-cols-1 md:grid-cols-4 gap-2">
<Widget <Widget
title="Toplam Aktivite Türü" title="Toplam Aktivite Türü"
value={activityTypes.length} value={activityTypes.length}
color="blue" color="blue"
icon="FaCog" icon="FaCog"
/> />
<Widget <Widget
title="Aktif" title="Aktif"
value={activityTypes.filter((a) => a.isActive).length} value={activityTypes.filter((a) => a.isActive).length}
color="green" color="green"
icon="FaCog" icon="FaCog"
/> />
<Widget <Widget
title="Kategori Sayısı" title="Kategori Sayısı"
value={categories.length} value={categories.length}
color="purple" color="purple"
icon="FaFileAlt" icon="FaFileAlt"
/> />
<Widget <Widget
title="Ortalama Süre" title="Ortalama Süre"
value={`${( value={`${(
activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) / activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) / activityTypes.length
activityTypes.length ).toFixed(1)}h`}
).toFixed(1)}h`} color="orange"
color="orange" icon="FaFlask"
icon="FaFlask"
/>
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Aktivite türü ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 pr-3 py-1.5 text-sm w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/> />
</div> </div>
<select
value={categoryFilter} {/* Filters */}
onChange={(e) => setCategoryFilter(e.target.value)} <div className="flex flex-col sm:flex-row gap-2">
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" <div className="relative flex-1">
> <FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<option value="">Tüm Kategoriler</option> <input
{categories.map((category) => ( type="text"
<option key={category} value={category}> placeholder="Aktivite türü ara..."
{category} value={searchTerm}
</option> onChange={(e) => setSearchTerm(e.target.value)}
))} className="pl-10 pr-3 py-1.5 text-sm w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
</select> />
</div>
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
className="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="">Tüm Kategoriler</option>
{categories.map((category) => (
<option key={category} value={category}>
{category}
</option>
))}
</select>
</div>
{/* Activity Types - Card/List View */}
{viewMode === 'card' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
{filteredActivities.map((activity) => {
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
return (
<div
key={activity.id}
className="bg-white rounded-lg border border-gray-200 p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div
className={`p-2 rounded-lg ${getPsActivityTypeColor(
activity.activityType,
)}`}
>
<ActivityIcon className="w-4 h-4" />
</div>
<div>
<h3 className="font-medium text-gray-900">{activity.name}</h3>
<p className="text-sm text-gray-500">{activity.category}</p>
</div>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setSelectedActivity(activity.id)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Detayları Gör"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(activity)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="text-red-600 hover:text-red-900 p-1" title="Sil">
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
<p className="text-sm text-gray-600 mb-2">{activity.description}</p>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500">Süre: {activity.defaultDuration}h</span>
<span
className={`px-2 py-1 text-xs rounded-full ${
activity.isActive
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{activity.isActive ? 'Aktif' : 'Pasif'}
</span>
</div>
</div>
)
})}
</div>
) : (
<div className="bg-white rounded-lg border border-gray-200">
<div className="px-3 py-1.5 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900">Aktivite Türleri Listesi</h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Aktivite Türü
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kategori
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Varsayılan Süre
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Son Güncelleme
</th>
<th className="px-2 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredActivities.map((activity) => {
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
return (
<tr key={activity.id} className="hover:bg-gray-50 text-sm">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<div
className={`p-2 rounded-lg mr-3 ${getPsActivityTypeColor(
activity.activityType,
)}`}
>
<ActivityIcon className="w-4 h-4" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">
{activity.name}
</div>
<div className="text-sm text-gray-500">{activity.description}</div>
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-sm text-gray-900">{activity.category}</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-sm text-gray-900">
{activity.defaultDuration} saat
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-1 text-xs rounded-full ${
activity.isActive
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{activity.isActive ? 'Aktif' : 'Pasif'}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-sm text-gray-500">
{activity.lastModificationTime.toLocaleDateString('tr-TR')}
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setSelectedActivity(activity.id)}
className="text-blue-600 hover:text-blue-900"
title="Detayları Gör"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(activity)}
className="text-blue-600 hover:text-blue-900"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="text-red-600 hover:text-red-900" title="Sil">
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
)}
</div> </div>
{/* Activity Types - Card/List View */}
{viewMode === "card" ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
{filteredActivities.map((activity) => {
const ActivityIcon = getPsActivityTypeIcon(activity.activityType);
return (
<div
key={activity.id}
className="bg-white rounded-lg border border-gray-200 p-2.5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-3">
<div
className={`p-2 rounded-lg ${getPsActivityTypeColor(
activity.activityType
)}`}
>
<ActivityIcon className="w-4 h-4" />
</div>
<div>
<h3 className="font-medium text-gray-900">
{activity.name}
</h3>
<p className="text-sm text-gray-500">
{activity.category}
</p>
</div>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setSelectedActivity(activity.id)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Detayları Gör"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(activity)}
className="text-blue-600 hover:text-blue-900 p-1"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
<p className="text-sm text-gray-600 mb-2">
{activity.description}
</p>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500">
Süre: {activity.defaultDuration}h
</span>
<span
className={`px-2 py-1 text-xs rounded-full ${
activity.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{activity.isActive ? "Aktif" : "Pasif"}
</span>
</div>
</div>
);
})}
</div>
) : (
<div className="bg-white rounded-lg border border-gray-200">
<div className="px-3 py-1.5 border-b border-gray-200">
<h3 className="text-base font-medium text-gray-900">
Aktivite Türleri Listesi
</h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Aktivite Türü
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Kategori
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Varsayılan Süre
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Durum
</th>
<th className="px-2 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Son Güncelleme
</th>
<th className="px-2 py-1.5 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
İşlemler
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredActivities.map((activity) => {
const ActivityIcon = getPsActivityTypeIcon(
activity.activityType
);
return (
<tr key={activity.id} className="hover:bg-gray-50 text-sm">
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="flex items-center">
<div
className={`p-2 rounded-lg mr-3 ${getPsActivityTypeColor(
activity.activityType
)}`}
>
<ActivityIcon className="w-4 h-4" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">
{activity.name}
</div>
<div className="text-sm text-gray-500">
{activity.description}
</div>
</div>
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-sm text-gray-900">
{activity.category}
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<div className="text-sm text-gray-900">
{activity.defaultDuration} saat
</div>
</td>
<td className="px-2 py-1.5 whitespace-nowrap">
<span
className={`px-2 py-1 text-xs rounded-full ${
activity.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
}`}
>
{activity.isActive ? "Aktif" : "Pasif"}
</span>
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-sm text-gray-500">
{activity.lastModificationTime.toLocaleDateString(
"tr-TR"
)}
</td>
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => setSelectedActivity(activity.id)}
className="text-blue-600 hover:text-blue-900"
title="Detayları Gör"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEdit(activity)}
className="text-blue-600 hover:text-blue-900"
title="Düzenle"
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-red-600 hover:text-red-900"
title="Sil"
>
<FaTrash className="w-4 h-4" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
{/* Activity Detail Modal */} {/* Activity Detail Modal */}
<ActivityDetailModal /> <ActivityDetailModal />
@ -500,8 +439,8 @@ const ActivityTypes: React.FC = () => {
onClose={handleCloseForm} onClose={handleCloseForm}
onSave={handleSave} onSave={handleSave}
/> />
</div> </Container>
); )
}; }
export default ActivityTypes; export default ActivityTypes

File diff suppressed because it is too large Load diff

View file

@ -1,141 +1,128 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from 'react'
import { import {
FaUser, FaUser,
FaChevronDown, FaChevronDown,
FaChevronRight, FaChevronRight,
FaChevronLeft, FaChevronLeft,
FaChevronRight as FaArrowRight, FaChevronRight as FaArrowRight,
} from "react-icons/fa"; } from 'react-icons/fa'
import { PsGanttTask } from "../../../types/ps"; import { PsGanttTask } from '../../../types/ps'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockProjects } from "../../../mocks/mockProjects"; import { mockProjects } from '../../../mocks/mockProjects'
import { mockProjectPhases } from "../../../mocks/mockProjectPhases"; import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
import { mockProjectTasks } from "../../../mocks/mockProjectTasks"; import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
import { PriorityEnum } from "../../../types/common"; import { PriorityEnum } from '../../../types/common'
import { getPriorityColor, getProjectStatusColor } from "../../../utils/erp"; import { getPriorityColor, getProjectStatusColor } from '../../../utils/erp'
import { Container } from '@/components/shared'
interface ProjectGanttProps { interface ProjectGanttProps {
employeeId?: string; employeeId?: string
} }
const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => { const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
// İlk 2 projeyi ve fazlarını varsayılan olarak açık başlat // İlk 2 projeyi ve fazlarını varsayılan olarak açık başlat
const getInitialExpandedItems = () => { const getInitialExpandedItems = () => {
const expandedItems = new Set<string>(); const expandedItems = new Set<string>()
// İlk 2 projeyi aç // İlk 2 projeyi aç
const firstTwoProjects = mockProjects.slice(0, 2); const firstTwoProjects = mockProjects.slice(0, 2)
firstTwoProjects.forEach((project) => { firstTwoProjects.forEach((project) => {
expandedItems.add(`project-${project.id}`); expandedItems.add(`project-${project.id}`)
// Bu projelerin fazlarını da aç // Bu projelerin fazlarını da aç
const projectPhases = mockProjectPhases.filter( const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
(phase) => phase.projectId === project.id
);
projectPhases.forEach((phase) => { projectPhases.forEach((phase) => {
expandedItems.add(`phase-${phase.id}`); expandedItems.add(`phase-${phase.id}`)
}); })
}); })
return expandedItems; return expandedItems
}; }
const [expandedItems, setExpandedItems] = useState<Set<string>>( const [expandedItems, setExpandedItems] = useState<Set<string>>(getInitialExpandedItems())
getInitialExpandedItems()
);
const [selectedEmployee, setSelectedEmployee] = useState<string>( const [selectedEmployee, setSelectedEmployee] = useState<string>(
employeeId || mockEmployees[0]?.id || "" employeeId || mockEmployees[0]?.id || '',
); )
const [viewMode, setViewMode] = useState<"day" | "week" | "month" | "year">( const [viewMode, setViewMode] = useState<'day' | 'week' | 'month' | 'year'>('week')
"week" const [currentDate, setCurrentDate] = useState<Date>(new Date())
);
const [currentDate, setCurrentDate] = useState<Date>(new Date());
// Gantt chart için tarih aralığı oluştur // Gantt chart için tarih aralığı oluştur
const generateDateRange = () => { const generateDateRange = () => {
const startDate = new Date(currentDate); const startDate = new Date(currentDate)
if (viewMode === "day") { if (viewMode === 'day') {
// Günlük görünüm: seçili günden itibaren 24 saat // Günlük görünüm: seçili günden itibaren 24 saat
const hours = []; const hours = []
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
const hour = new Date(startDate); const hour = new Date(startDate)
hour.setHours(i, 0, 0, 0); hour.setHours(i, 0, 0, 0)
hours.push(hour); hours.push(hour)
} }
return hours; return hours
} else if (viewMode === "week") { } else if (viewMode === 'week') {
// Haftalık görünüm: hafta başından itibaren 7 gün // Haftalık görünüm: hafta başından itibaren 7 gün
const weekStart = new Date(startDate); const weekStart = new Date(startDate)
const dayOfWeek = weekStart.getDay(); const dayOfWeek = weekStart.getDay()
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Pazartesi = 0, Pazar = 6 const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1 // Pazartesi = 0, Pazar = 6
weekStart.setDate(weekStart.getDate() - daysToSubtract); weekStart.setDate(weekStart.getDate() - daysToSubtract)
weekStart.setHours(0, 0, 0, 0); weekStart.setHours(0, 0, 0, 0)
const dates = []; const dates = []
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
const date = new Date(weekStart); const date = new Date(weekStart)
date.setDate(weekStart.getDate() + i); date.setDate(weekStart.getDate() + i)
dates.push(date); dates.push(date)
} }
return dates; return dates
} else if (viewMode === "month") { } else if (viewMode === 'month') {
// Aylık görünüm: ay başından itibaren ay sonu // Aylık görünüm: ay başından itibaren ay sonu
const monthStart = new Date( const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
currentDate.getFullYear(),
currentDate.getMonth(),
1
);
const daysInMonth = new Date( const daysInMonth = new Date(
currentDate.getFullYear(), currentDate.getFullYear(),
currentDate.getMonth() + 1, currentDate.getMonth() + 1,
0 0,
).getDate(); ).getDate()
const dates = []; const dates = []
for (let i = 0; i < daysInMonth; i++) { for (let i = 0; i < daysInMonth; i++) {
const date = new Date(monthStart); const date = new Date(monthStart)
date.setDate(monthStart.getDate() + i); date.setDate(monthStart.getDate() + i)
dates.push(date); dates.push(date)
} }
return dates; return dates
} else { } else {
// Yıllık görünüm: yıl başından itibaren 12 ay // Yıllık görünüm: yıl başından itibaren 12 ay
const yearStart = new Date(currentDate.getFullYear(), 0, 1); const yearStart = new Date(currentDate.getFullYear(), 0, 1)
const months = []; const months = []
for (let i = 0; i < 12; i++) { for (let i = 0; i < 12; i++) {
const month = new Date(yearStart); const month = new Date(yearStart)
month.setMonth(i); month.setMonth(i)
months.push(month); months.push(month)
} }
return months; return months
} }
}; }
const dateRange = generateDateRange(); const dateRange = generateDateRange()
// Seçili çalışana göre görevleri filtrele // Seçili çalışana göre görevleri filtrele
const filteredTasks = useMemo(() => { const filteredTasks = useMemo(() => {
// Mock verilerden GanttTask formatına dönüştür // Mock verilerden GanttTask formatına dönüştür
const createGanttTasks = (): PsGanttTask[] => { const createGanttTasks = (): PsGanttTask[] => {
const ganttTasks: PsGanttTask[] = []; const ganttTasks: PsGanttTask[] = []
mockProjects.forEach((project) => { mockProjects.forEach((project) => {
const projectPhases = mockProjectPhases.filter( const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
(phase) => phase.projectId === project.id
);
const projectChildren: PsGanttTask[] = []; const projectChildren: PsGanttTask[] = []
projectPhases.forEach((phase) => { projectPhases.forEach((phase) => {
const phaseTasks = mockProjectTasks.filter( const phaseTasks = mockProjectTasks.filter((task) => task.phaseId === phase.id)
(task) => task.phaseId === phase.id
);
const phaseChildren: PsGanttTask[] = phaseTasks.map((task) => ({ const phaseChildren: PsGanttTask[] = phaseTasks.map((task) => ({
id: `task-${task.id}`, id: `task-${task.id}`,
name: task.name, name: task.name,
type: "task" as const, type: 'task' as const,
startDate: task.startDate, startDate: task.startDate,
endDate: task.endDate, endDate: task.endDate,
progress: task.progress, progress: task.progress,
@ -146,13 +133,13 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
children: [], children: [],
estimatedHours: task.estimatedHours, estimatedHours: task.estimatedHours,
hoursWorked: task.actualHours, hoursWorked: task.actualHours,
})); }))
if (phaseChildren.length > 0) { if (phaseChildren.length > 0) {
projectChildren.push({ projectChildren.push({
id: `phase-${phase.id}`, id: `phase-${phase.id}`,
name: phase.name, name: phase.name,
type: "phase" as const, type: 'phase' as const,
startDate: phase.startDate, startDate: phase.startDate,
endDate: phase.endDate, endDate: phase.endDate,
progress: phase.progress, progress: phase.progress,
@ -160,15 +147,15 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
priority: PriorityEnum.Normal, priority: PriorityEnum.Normal,
level: 1, level: 1,
children: phaseChildren, children: phaseChildren,
}); })
} }
}); })
if (projectChildren.length > 0) { if (projectChildren.length > 0) {
ganttTasks.push({ ganttTasks.push({
id: `project-${project.id}`, id: `project-${project.id}`,
name: project.name, name: project.name,
type: "project" as const, type: 'project' as const,
startDate: project.startDate, startDate: project.startDate,
endDate: project.endDate, endDate: project.endDate,
progress: project.progress, progress: project.progress,
@ -176,265 +163,254 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
priority: project.priority, priority: project.priority,
level: 0, level: 0,
children: projectChildren, children: projectChildren,
}); })
} }
}); })
return ganttTasks; return ganttTasks
}; }
const filterTasksByEmployee = (tasks: PsGanttTask[]): PsGanttTask[] => { const filterTasksByEmployee = (tasks: PsGanttTask[]): PsGanttTask[] => {
return tasks return tasks
.map((task) => { .map((task) => {
if (task.type === "task" && task.assignee?.id !== selectedEmployee) { if (task.type === 'task' && task.assignee?.id !== selectedEmployee) {
return null; return null
} }
if (task.children) { if (task.children) {
const filteredChildren = filterTasksByEmployee( const filteredChildren = filterTasksByEmployee(task.children).filter(
task.children Boolean,
).filter(Boolean) as PsGanttTask[]; ) as PsGanttTask[]
if (filteredChildren.length > 0) { if (filteredChildren.length > 0) {
return { ...task, children: filteredChildren }; return { ...task, children: filteredChildren }
} else if (task.type === "task") { } else if (task.type === 'task') {
return task; return task
} }
return null; return null
} }
return task; return task
}) })
.filter(Boolean) as PsGanttTask[]; .filter(Boolean) as PsGanttTask[]
}; }
const allGanttTasks = createGanttTasks(); const allGanttTasks = createGanttTasks()
return filterTasksByEmployee(allGanttTasks); return filterTasksByEmployee(allGanttTasks)
}, [selectedEmployee]); }, [selectedEmployee])
const toggleExpand = (id: string) => { const toggleExpand = (id: string) => {
const newExpanded = new Set(expandedItems); const newExpanded = new Set(expandedItems)
if (newExpanded.has(id)) { if (newExpanded.has(id)) {
newExpanded.delete(id); newExpanded.delete(id)
} else { } else {
newExpanded.add(id); newExpanded.add(id)
} }
setExpandedItems(newExpanded); setExpandedItems(newExpanded)
}; }
// Navigasyon fonksiyonları // Navigasyon fonksiyonları
const navigatePrevious = () => { const navigatePrevious = () => {
const newDate = new Date(currentDate); const newDate = new Date(currentDate)
if (viewMode === "day") { if (viewMode === 'day') {
newDate.setDate(newDate.getDate() - 1); newDate.setDate(newDate.getDate() - 1)
} else if (viewMode === "week") { } else if (viewMode === 'week') {
newDate.setDate(newDate.getDate() - 7); newDate.setDate(newDate.getDate() - 7)
} else if (viewMode === "month") { } else if (viewMode === 'month') {
newDate.setMonth(newDate.getMonth() - 1); newDate.setMonth(newDate.getMonth() - 1)
} else { } else {
newDate.setFullYear(newDate.getFullYear() - 1); newDate.setFullYear(newDate.getFullYear() - 1)
} }
setCurrentDate(newDate); setCurrentDate(newDate)
}; }
const navigateNext = () => { const navigateNext = () => {
const newDate = new Date(currentDate); const newDate = new Date(currentDate)
if (viewMode === "day") { if (viewMode === 'day') {
newDate.setDate(newDate.getDate() + 1); newDate.setDate(newDate.getDate() + 1)
} else if (viewMode === "week") { } else if (viewMode === 'week') {
newDate.setDate(newDate.getDate() + 7); newDate.setDate(newDate.getDate() + 7)
} else if (viewMode === "month") { } else if (viewMode === 'month') {
newDate.setMonth(newDate.getMonth() + 1); newDate.setMonth(newDate.getMonth() + 1)
} else { } else {
newDate.setFullYear(newDate.getFullYear() + 1); newDate.setFullYear(newDate.getFullYear() + 1)
} }
setCurrentDate(newDate); setCurrentDate(newDate)
}; }
const navigateToday = () => { const navigateToday = () => {
setCurrentDate(new Date()); setCurrentDate(new Date())
}; }
// Mevcut tarih bilgisi gösterimi // Mevcut tarih bilgisi gösterimi
const getCurrentDateInfo = () => { const getCurrentDateInfo = () => {
if (viewMode === "day") { if (viewMode === 'day') {
return currentDate.toLocaleDateString("tr-TR", { return currentDate.toLocaleDateString('tr-TR', {
weekday: "long", weekday: 'long',
year: "numeric", year: 'numeric',
month: "long", month: 'long',
day: "numeric", day: 'numeric',
}); })
} else if (viewMode === "week") { } else if (viewMode === 'week') {
const weekStart = new Date(currentDate); const weekStart = new Date(currentDate)
const dayOfWeek = weekStart.getDay(); const dayOfWeek = weekStart.getDay()
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1
weekStart.setDate(weekStart.getDate() - daysToSubtract); weekStart.setDate(weekStart.getDate() - daysToSubtract)
const weekEnd = new Date(weekStart); const weekEnd = new Date(weekStart)
weekEnd.setDate(weekStart.getDate() + 6); weekEnd.setDate(weekStart.getDate() + 6)
return `${weekStart.toLocaleDateString("tr-TR", { return `${weekStart.toLocaleDateString('tr-TR', {
day: "numeric", day: 'numeric',
month: "short", month: 'short',
})} - ${weekEnd.toLocaleDateString("tr-TR", { })} - ${weekEnd.toLocaleDateString('tr-TR', {
day: "numeric", day: 'numeric',
month: "short", month: 'short',
year: "numeric", year: 'numeric',
})}`; })}`
} else if (viewMode === "month") { } else if (viewMode === 'month') {
return currentDate.toLocaleDateString("tr-TR", { return currentDate.toLocaleDateString('tr-TR', {
year: "numeric", year: 'numeric',
month: "long", month: 'long',
}); })
} else { } else {
return currentDate.toLocaleDateString("tr-TR", { return currentDate.toLocaleDateString('tr-TR', {
year: "numeric", year: 'numeric',
}); })
} }
}; }
const calculateTaskPosition = (startDate: Date, endDate: Date) => { const calculateTaskPosition = (startDate: Date, endDate: Date) => {
const chartStart = dateRange[0]; const chartStart = dateRange[0]
const chartEnd = dateRange[dateRange.length - 1]; const chartEnd = dateRange[dateRange.length - 1]
const taskStart = new Date(startDate); const taskStart = new Date(startDate)
const taskEnd = new Date(endDate); const taskEnd = new Date(endDate)
if (viewMode === "day") { if (viewMode === 'day') {
// Günlük görünüm: saatlik hesaplama // Günlük görünüm: saatlik hesaplama
const dayStart = new Date(chartStart); const dayStart = new Date(chartStart)
dayStart.setHours(0, 0, 0, 0); dayStart.setHours(0, 0, 0, 0)
const dayEnd = new Date(chartStart); const dayEnd = new Date(chartStart)
dayEnd.setHours(23, 59, 59, 999); dayEnd.setHours(23, 59, 59, 999)
// Eğer görev bu gün içinde değilse, görünürde olup olmadığını kontrol et // Eğer görev bu gün içinde değilse, görünürde olup olmadığını kontrol et
if (taskEnd < dayStart || taskStart > dayEnd) { if (taskEnd < dayStart || taskStart > dayEnd) {
return { left: "100%", width: "0%", isVisible: false }; return { left: '100%', width: '0%', isVisible: false }
} }
// Görev başlangıcını hesapla // Görev başlangıcını hesapla
let startHour = 0; let startHour = 0
if (taskStart > dayStart) { if (taskStart > dayStart) {
startHour = taskStart.getHours() + taskStart.getMinutes() / 60; startHour = taskStart.getHours() + taskStart.getMinutes() / 60
} }
// Görev bitişini hesapla // Görev bitişini hesapla
let endHour = 24; let endHour = 24
if (taskEnd < dayEnd) { if (taskEnd < dayEnd) {
endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60; endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60
} }
const left = (startHour / 24) * 100; const left = (startHour / 24) * 100
const width = Math.max(1, ((endHour - startHour) / 24) * 100); const width = Math.max(1, ((endHour - startHour) / 24) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true }; return { left: `${left}%`, width: `${width}%`, isVisible: true }
} else if (viewMode === "week") { } else if (viewMode === 'week') {
// Haftalık görünüm: günlük hesaplama // Haftalık görünüm: günlük hesaplama
const weekStart = new Date(chartStart); const weekStart = new Date(chartStart)
const weekEnd = new Date(chartEnd); const weekEnd = new Date(chartEnd)
weekEnd.setHours(23, 59, 59, 999); weekEnd.setHours(23, 59, 59, 999)
// Görev hafta dışındaysa gizle // Görev hafta dışındaysa gizle
if (taskEnd < weekStart || taskStart > weekEnd) { if (taskEnd < weekStart || taskStart > weekEnd) {
return { left: "100%", width: "0%", isVisible: false }; return { left: '100%', width: '0%', isVisible: false }
} }
// Başlangıç gününü hesapla // Başlangıç gününü hesapla
let startDay = 0; let startDay = 0
if (taskStart > weekStart) { if (taskStart > weekStart) {
startDay = Math.floor( startDay = Math.floor((taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
(taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
);
} }
// Bitiş gününü hesapla // Bitiş gününü hesapla
let endDay = 7; let endDay = 7
if (taskEnd < weekEnd) { if (taskEnd < weekEnd) {
endDay = Math.ceil( endDay = Math.ceil((taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
(taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
);
} }
const left = (startDay / 7) * 100; const left = (startDay / 7) * 100
const width = Math.max(1, ((endDay - startDay) / 7) * 100); const width = Math.max(1, ((endDay - startDay) / 7) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true }; return { left: `${left}%`, width: `${width}%`, isVisible: true }
} else if (viewMode === "month") { } else if (viewMode === 'month') {
// Aylık görünüm: günlük hesaplama // Aylık görünüm: günlük hesaplama
const monthStart = new Date(chartStart); const monthStart = new Date(chartStart)
const monthEnd = new Date(chartEnd); const monthEnd = new Date(chartEnd)
monthEnd.setHours(23, 59, 59, 999); monthEnd.setHours(23, 59, 59, 999)
// Görev ay dışındaysa gizle // Görev ay dışındaysa gizle
if (taskEnd < monthStart || taskStart > monthEnd) { if (taskEnd < monthStart || taskStart > monthEnd) {
return { left: "100%", width: "0%", isVisible: false }; return { left: '100%', width: '0%', isVisible: false }
} }
const daysInMonth = dateRange.length; const daysInMonth = dateRange.length
// Başlangıç gününü hesapla // Başlangıç gününü hesapla
let startDay = 0; let startDay = 0
if (taskStart > monthStart) { if (taskStart > monthStart) {
startDay = Math.floor( startDay = Math.floor((taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
(taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
);
} }
// Bitiş gününü hesapla // Bitiş gününü hesapla
let endDay = daysInMonth; let endDay = daysInMonth
if (taskEnd < monthEnd) { if (taskEnd < monthEnd) {
endDay = Math.ceil( endDay = Math.ceil((taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
(taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
);
} }
const left = (startDay / daysInMonth) * 100; const left = (startDay / daysInMonth) * 100
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100); const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true }; return { left: `${left}%`, width: `${width}%`, isVisible: true }
} else { } else {
// Yıllık görünüm: aylık hesaplama // Yıllık görünüm: aylık hesaplama
const yearStart = new Date(chartStart); const yearStart = new Date(chartStart)
const yearEnd = new Date(chartEnd); const yearEnd = new Date(chartEnd)
yearEnd.setMonth(11, 31); yearEnd.setMonth(11, 31)
yearEnd.setHours(23, 59, 59, 999); yearEnd.setHours(23, 59, 59, 999)
// Görev yıl dışındaysa gizle // Görev yıl dışındaysa gizle
if (taskEnd < yearStart || taskStart > yearEnd) { if (taskEnd < yearStart || taskStart > yearEnd) {
return { left: "100%", width: "0%", isVisible: false }; return { left: '100%', width: '0%', isVisible: false }
} }
// Başlangıç ayını hesapla // Başlangıç ayını hesapla
let startMonth = 0; let startMonth = 0
if (taskStart > yearStart) { if (taskStart > yearStart) {
startMonth = taskStart.getMonth(); startMonth = taskStart.getMonth()
} }
// Bitiş ayını hesapla // Bitiş ayını hesapla
let endMonth = 12; let endMonth = 12
if (taskEnd < yearEnd) { if (taskEnd < yearEnd) {
endMonth = taskEnd.getMonth() + 1; endMonth = taskEnd.getMonth() + 1
} }
const left = (startMonth / 12) * 100; const left = (startMonth / 12) * 100
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100); const width = Math.max(1, ((endMonth - startMonth) / 12) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true }; return { left: `${left}%`, width: `${width}%`, isVisible: true }
} }
}; }
const renderTask = (task: PsGanttTask): React.ReactNode => { const renderTask = (task: PsGanttTask): React.ReactNode => {
const hasChildren = task.children && task.children.length > 0; const hasChildren = task.children && task.children.length > 0
const isExpanded = expandedItems.has(task.id); const isExpanded = expandedItems.has(task.id)
const indent = task.level * 20; const indent = task.level * 20
return ( return (
<React.Fragment key={task.id}> <React.Fragment key={task.id}>
<div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[32px] sm:min-h-[36px]"> <div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[32px] sm:min-h-[36px]">
{/* Task Info */} {/* Task Info */}
<div className="w-1/3 sm:w-1/4 p-0.5 border-r border-gray-200"> <div className="w-1/3 sm:w-1/4 p-0.5 border-r border-gray-200">
<div <div className="flex items-center" style={{ paddingLeft: `${Math.min(indent, 40)}px` }}>
className="flex items-center"
style={{ paddingLeft: `${Math.min(indent, 40)}px` }}
>
{hasChildren && ( {hasChildren && (
<button <button
onClick={() => toggleExpand(task.id)} onClick={() => toggleExpand(task.id)}
@ -453,11 +429,11 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span <span
className={`font-medium text-xs sm:text-sm truncate ${ className={`font-medium text-xs sm:text-sm truncate ${
task.type === "project" task.type === 'project'
? "text-blue-900 font-semibold" ? 'text-blue-900 font-semibold'
: task.type === "phase" : task.type === 'phase'
? "text-blue-700" ? 'text-blue-700'
: "text-gray-900" : 'text-gray-900'
}`} }`}
title={task.name} title={task.name}
> >
@ -465,14 +441,14 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
</span> </span>
<span <span
className={`px-1 py-0.5 text-xs border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor( className={`px-1 py-0.5 text-xs border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
task.priority task.priority,
)}`} )}`}
> >
{task.priority} {task.priority}
</span> </span>
</div> </div>
{task.type === "task" && task.assignee && ( {task.type === 'task' && task.assignee && (
<div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600"> <div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600">
<FaUser className="h-2 w-2 sm:h-2.5 sm:w-2.5 flex-shrink-0" /> <FaUser className="h-2 w-2 sm:h-2.5 sm:w-2.5 flex-shrink-0" />
<span className="truncate text-xs"> <span className="truncate text-xs">
@ -487,8 +463,8 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
)} )}
<div className="mt-1 text-xs text-gray-500 truncate hidden sm:block"> <div className="mt-1 text-xs text-gray-500 truncate hidden sm:block">
{task.startDate.toLocaleDateString("tr-TR")} -{" "} {task.startDate.toLocaleDateString('tr-TR')} -{' '}
{task.endDate.toLocaleDateString("tr-TR")} {task.endDate.toLocaleDateString('tr-TR')}
</div> </div>
</div> </div>
</div> </div>
@ -498,16 +474,13 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
<div className="w-2/3 sm:w-3/4 p-0.5 relative"> <div className="w-2/3 sm:w-3/4 p-0.5 relative">
<div className="relative h-5 sm:h-7 bg-gray-200 rounded"> <div className="relative h-5 sm:h-7 bg-gray-200 rounded">
{(() => { {(() => {
const position = calculateTaskPosition( const position = calculateTaskPosition(task.startDate, task.endDate)
task.startDate,
task.endDate
);
if (task.type === "task" && position.isVisible) { if (task.type === 'task' && position.isVisible) {
return ( return (
<div <div
className={`absolute h-4 sm:h-5 mt-0.5 rounded-sm ${getProjectStatusColor( className={`absolute h-4 sm:h-5 mt-0.5 rounded-sm ${getProjectStatusColor(
task.status task.status,
)} shadow-md group cursor-pointer border border-white border-opacity-20`} )} shadow-md group cursor-pointer border border-white border-opacity-20`}
style={{ left: position.left, width: position.width }} style={{ left: position.left, width: position.width }}
title={`${task.name} - ${task.progress}% tamamlandı`} title={`${task.name} - ${task.progress}% tamamlandı`}
@ -528,24 +501,16 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
{/* Tooltip */} {/* Tooltip */}
<div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block"> <div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
<div className="font-semibold">{task.name}</div> <div className="font-semibold">{task.name}</div>
<div className="mt-1"> <div className="mt-1">İlerleme: {task.progress}% tamamlandı</div>
İlerleme: {task.progress}% tamamlandı <div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
</div> <div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
<div>
Planlanan Başlangıç:{" "}
{task.startDate.toLocaleDateString("tr-TR")}
</div>
<div>
Planlanan Bitiş:{" "}
{task.endDate.toLocaleDateString("tr-TR")}
</div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div> <div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
</div> </div>
</div> </div>
); )
} }
if (task.type !== "task" && position.isVisible) { if (task.type !== 'task' && position.isVisible) {
return ( return (
<div <div
className="absolute h-2 sm:h-2.5 mt-1.5 sm:mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30" className="absolute h-2 sm:h-2.5 mt-1.5 sm:mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30"
@ -555,159 +520,142 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
{/* Tooltip for non-task items - no progress */} {/* Tooltip for non-task items - no progress */}
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block"> <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
<div className="font-semibold">{task.name}</div> <div className="font-semibold">{task.name}</div>
<div> <div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
Planlanan Başlangıç:{" "} <div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
{task.startDate.toLocaleDateString("tr-TR")}
</div>
<div>
Planlanan Bitiş:{" "}
{task.endDate.toLocaleDateString("tr-TR")}
</div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div> <div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
</div> </div>
</div> </div>
); )
} }
return null; return null
})()} })()}
</div> </div>
</div> </div>
</div> </div>
{/* Render children if expanded */} {/* Render children if expanded */}
{hasChildren && {hasChildren && isExpanded && task.children?.map((child) => renderTask(child))}
isExpanded &&
task.children?.map((child) => renderTask(child))}
</React.Fragment> </React.Fragment>
); )
}; }
return ( return (
<> <Container>
<div className="space-y-3 py-2"> <div className="space-y-2">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div> <div>
<h2 className="text-xl font-bold text-gray-900"> <h2 className="text-xl font-bold text-gray-900">İş Yükü ve Proje Takibi</h2>
İş Yükü ve Proje Takibi <p className="text-gray-600 mt-1">
</h2>
<p className="text-sm sm:text-base text-gray-600 mt-1">
Tüm görevlerinizi merkezi bir noktadan yönetin. Tüm görevlerinizi merkezi bir noktadan yönetin.
</p> </p>
</div> </div>
</div> </div>
</div>
<div className="bg-white rounded-lg shadow-sm border h-full flex flex-col"> <div className="bg-white rounded-lg shadow-sm border h-full flex flex-col">
{/* Header */} {/* Header */}
<div className="p-1.5 sm:p-2 border-b border-gray-200 flex-shrink-0"> <div className="p-1.5 sm:p-2 border-b border-gray-200 flex-shrink-0">
{/* Navigation and Date Info */} {/* Navigation and Date Info */}
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between"> <div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
{/* Navigation Controls */} {/* Navigation Controls */}
<div className="flex items-center justify-center gap-1 sm:gap-2"> <div className="flex items-center justify-center gap-1 sm:gap-2">
<button <button
onClick={navigatePrevious} onClick={navigatePrevious}
className="p-1 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors" className="p-1 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
>
<FaChevronLeft className="h-3.5 w-3.5" />
</button>
<div className="text-xs sm:text-sm font-semibold text-gray-900 min-w-[150px] sm:min-w-[170px] text-center">
{getCurrentDateInfo()}
</div>
<button
onClick={navigateNext}
className="p-1 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
>
<FaArrowRight className="h-3.5 w-3.5" />
</button>
<button
onClick={navigateToday}
className="px-1.5 py-0.5 text-xs sm:text-sm bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors"
>
Bugün
</button>
</div>
{/* Filters */}
<div className="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-2">
<select
value={selectedEmployee}
onChange={(e) => setSelectedEmployee(e.target.value)}
className="px-2 py-1.5 sm:px-3 sm:py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
>
<option value="">Tüm Çalışanlar</option>
{mockEmployees.map((emp) => (
<option key={emp.id} value={emp.id}>
{emp.firstName} {emp.lastName}
</option>
))}
</select>
<select
value={viewMode}
onChange={(e) =>
setViewMode(
e.target.value as "day" | "week" | "month" | "year"
)
}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
>
<option value="day">Günlük</option>
<option value="week">Haftalık</option>
<option value="month">Aylık</option>
<option value="year">Yıllık</option>
</select>
</div>
</div>
</div>
{/* Timeline Header */}
<div className="flex border-b border-gray-200 flex-shrink-0">
<div className="w-1/3 sm:w-1/4 p-1.5 border-r border-gray-200 bg-gray-50 font-semibold text-xs sm:text-sm">
Proje / Görev
</div>
<div className="w-2/3 sm:w-3/4 p-1.5 bg-gray-50">
<div className="flex justify-between text-xs font-semibold">
{dateRange.map((date) => (
<div
key={date.toISOString()}
className="text-center min-w-0 flex-1"
> >
<div className="truncate"> <FaChevronLeft className="h-3.5 w-3.5" />
{viewMode === "day" </button>
? date.toLocaleTimeString("tr-TR", { <div className="text-xs sm:text-sm font-semibold text-gray-900 min-w-[150px] sm:min-w-[170px] text-center">
hour: "2-digit", {getCurrentDateInfo()}
minute: "2-digit",
})
: viewMode === "week"
? date.toLocaleDateString("tr-TR", { day: "2-digit" })
: viewMode === "year"
? date.toLocaleDateString("tr-TR", { month: "short" })
: date.toLocaleDateString("tr-TR", { day: "2-digit" })}
</div>
{viewMode !== "day" && viewMode !== "year" && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.toLocaleDateString("tr-TR", { weekday: "short" })}
</div>
)}
{viewMode === "year" && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.getFullYear()}
</div>
)}
</div> </div>
))} <button
onClick={navigateNext}
className="p-1 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
>
<FaArrowRight className="h-3.5 w-3.5" />
</button>
<button
onClick={navigateToday}
className="px-1.5 py-0.5 text-xs sm:text-sm bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition-colors"
>
Bugün
</button>
</div>
{/* Filters */}
<div className="flex flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-2">
<select
value={selectedEmployee}
onChange={(e) => setSelectedEmployee(e.target.value)}
className="px-2 py-1.5 sm:px-3 sm:py-2 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
>
<option value="">Tüm Çalışanlar</option>
{mockEmployees.map((emp) => (
<option key={emp.id} value={emp.id}>
{emp.firstName} {emp.lastName}
</option>
))}
</select>
<select
value={viewMode}
onChange={(e) => setViewMode(e.target.value as 'day' | 'week' | 'month' | 'year')}
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
>
<option value="day">Günlük</option>
<option value="week">Haftalık</option>
<option value="month">Aylık</option>
<option value="year">Yıllık</option>
</select>
</div>
</div> </div>
</div> </div>
</div>
{/* Tasks */} {/* Timeline Header */}
<div className="flex-1 overflow-auto"> <div className="flex border-b border-gray-200 flex-shrink-0">
{filteredTasks.map((task) => renderTask(task))} <div className="w-1/3 sm:w-1/4 p-1.5 border-r border-gray-200 bg-gray-50 font-semibold text-xs sm:text-sm">
Proje / Görev
</div>
<div className="w-2/3 sm:w-3/4 p-1.5 bg-gray-50">
<div className="flex justify-between text-xs font-semibold">
{dateRange.map((date) => (
<div key={date.toISOString()} className="text-center min-w-0 flex-1">
<div className="truncate">
{viewMode === 'day'
? date.toLocaleTimeString('tr-TR', {
hour: '2-digit',
minute: '2-digit',
})
: viewMode === 'week'
? date.toLocaleDateString('tr-TR', { day: '2-digit' })
: viewMode === 'year'
? date.toLocaleDateString('tr-TR', { month: 'short' })
: date.toLocaleDateString('tr-TR', { day: '2-digit' })}
</div>
{viewMode !== 'day' && viewMode !== 'year' && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.toLocaleDateString('tr-TR', { weekday: 'short' })}
</div>
)}
{viewMode === 'year' && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.getFullYear()}
</div>
)}
</div>
))}
</div>
</div>
</div>
{/* Tasks */}
<div className="flex-1 overflow-auto">
{filteredTasks.map((task) => renderTask(task))}
</div>
</div> </div>
</div> </div>
</> </Container>
); )
}; }
export default ProjectGantt; export default ProjectGantt

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { import {
FaPlus, FaPlus,
FaSearch, FaSearch,
@ -11,190 +11,173 @@ import {
FaEdit, FaEdit,
FaSave, FaSave,
FaProjectDiagram, FaProjectDiagram,
} from "react-icons/fa"; } from 'react-icons/fa'
import { import { PhaseStatusEnum, PsProjectPhase, ProjectStatusEnum } from '../../../types/ps'
PhaseStatusEnum, import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
PsProjectPhase, import { mockProjects } from '../../../mocks/mockProjects'
ProjectStatusEnum, import MultiSelectTeam from '../../../components/common/MultiSelectTeam'
} from "../../../types/ps"; import Widget from '../../../components/common/Widget'
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
import { mockProjects } from "../../../mocks/mockProjects";
import MultiSelectTeam from "../../../components/common/MultiSelectTeam";
import Widget from "../../../components/common/Widget";
import { import {
getPhaseCategoryColor, getPhaseCategoryColor,
getProgressColor, getProgressColor,
getPhaseStatusIcon, getPhaseStatusIcon,
getPhaseStatusText, getPhaseStatusText,
getPhaseStatusColor, getPhaseStatusColor,
} from "../../../utils/erp"; } from '../../../utils/erp'
import { Container } from '@/components/shared'
const ProjectPhases: React.FC = () => { const ProjectPhases: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState('')
const [selectedStatus, setSelectedStatus] = useState("All"); const [selectedStatus, setSelectedStatus] = useState('All')
const [selectedProject, setSelectedProject] = useState("All"); const [selectedProject, setSelectedProject] = useState('All')
const [selectedPhase, setSelectedPhase] = useState<PsProjectPhase | null>( const [selectedPhase, setSelectedPhase] = useState<PsProjectPhase | null>(null)
null const [isModalOpen, setIsModalOpen] = useState(false)
); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false)
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [editingPhase, setEditingPhase] = useState<PsProjectPhase | null>(null)
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [editingPhase, setEditingPhase] = useState<PsProjectPhase | null>(null);
// Form state for creating/editing phases // Form state for creating/editing phases
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
phaseName: "", phaseName: '',
description: "", description: '',
projectId: "", projectId: '',
status: PhaseStatusEnum.NotStarted, status: PhaseStatusEnum.NotStarted,
startDate: "", startDate: '',
endDate: "", endDate: '',
budget: 0, budget: 0,
category: "", category: '',
assignedTeams: [] as string[], assignedTeams: [] as string[],
}); })
const filteredPhases = mockProjectPhases.filter((phase) => { const filteredPhases = mockProjectPhases.filter((phase) => {
const matchesSearch = const matchesSearch =
phase.name.toLowerCase().includes(searchTerm.toLowerCase()) || phase.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
phase.code.toLowerCase().includes(searchTerm.toLowerCase()) || phase.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
phase.description?.toLowerCase().includes(searchTerm.toLowerCase()); phase.description?.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = const matchesStatus = selectedStatus === 'All' || phase.status === selectedStatus
selectedStatus === "All" || phase.status === selectedStatus; const matchesProject = selectedProject === 'All' || phase.projectId === selectedProject
const matchesProject =
selectedProject === "All" || phase.projectId === selectedProject;
return matchesSearch && matchesStatus && matchesProject; return matchesSearch && matchesStatus && matchesProject
}); })
const calculateBudgetVariance = (budget: number, actualCost: number) => { const calculateBudgetVariance = (budget: number, actualCost: number) => {
return ((actualCost - budget) / budget) * 100; return ((actualCost - budget) / budget) * 100
}; }
const calculateScheduleVariance = (phase: PsProjectPhase) => { const calculateScheduleVariance = (phase: PsProjectPhase) => {
const plannedDuration = const plannedDuration = new Date(phase.endDate).getTime() - new Date(phase.startDate).getTime()
new Date(phase.endDate).getTime() - new Date(phase.startDate).getTime();
const actualDuration = phase.actualEndDate const actualDuration = phase.actualEndDate
? new Date(phase.actualEndDate).getTime() - ? new Date(phase.actualEndDate).getTime() -
new Date(phase.actualStartDate || phase.startDate).getTime() new Date(phase.actualStartDate || phase.startDate).getTime()
: Date.now() - : Date.now() - new Date(phase.actualStartDate || phase.startDate).getTime()
new Date(phase.actualStartDate || phase.startDate).getTime();
return ((actualDuration - plannedDuration) / plannedDuration) * 100; return ((actualDuration - plannedDuration) / plannedDuration) * 100
}; }
const openModal = (phase: PsProjectPhase) => { const openModal = (phase: PsProjectPhase) => {
setSelectedPhase(phase); setSelectedPhase(phase)
setIsModalOpen(true); setIsModalOpen(true)
}; }
const closeModal = () => { const closeModal = () => {
setSelectedPhase(null); setSelectedPhase(null)
setIsModalOpen(false); setIsModalOpen(false)
}; }
const openCreateModal = () => { const openCreateModal = () => {
setFormData({ setFormData({
phaseName: "", phaseName: '',
description: "", description: '',
projectId: "", projectId: '',
status: PhaseStatusEnum.NotStarted, status: PhaseStatusEnum.NotStarted,
startDate: "", startDate: '',
endDate: "", endDate: '',
budget: 0, budget: 0,
category: "", category: '',
assignedTeams: [], assignedTeams: [],
}); })
setIsCreateModalOpen(true); setIsCreateModalOpen(true)
}; }
const closeCreateModal = () => { const closeCreateModal = () => {
setIsCreateModalOpen(false); setIsCreateModalOpen(false)
}; }
const openEditModal = (phase: PsProjectPhase) => { const openEditModal = (phase: PsProjectPhase) => {
setEditingPhase(phase); setEditingPhase(phase)
setFormData({ setFormData({
phaseName: phase.name, phaseName: phase.name,
description: phase.description || "", description: phase.description || '',
projectId: phase.projectId, projectId: phase.projectId,
status: phase.status, status: phase.status,
startDate: phase.startDate.toISOString().split("T")[0], startDate: phase.startDate.toISOString().split('T')[0],
endDate: phase.endDate.toISOString().split("T")[0], endDate: phase.endDate.toISOString().split('T')[0],
budget: phase.budget, budget: phase.budget,
category: phase.category, category: phase.category,
assignedTeams: phase.assignedTeams || [], assignedTeams: phase.assignedTeams || [],
}); })
setIsEditModalOpen(true); setIsEditModalOpen(true)
}; }
const closeEditModal = () => { const closeEditModal = () => {
setIsEditModalOpen(false); setIsEditModalOpen(false)
setEditingPhase(null); setEditingPhase(null)
}; }
const handleInputChange = ( const handleInputChange = (
e: React.ChangeEvent< e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => { ) => {
const { name, value } = e.target; const { name, value } = e.target
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: value, [name]: value,
})); }))
}; }
const handleTeamChange = (teams: string[]) => { const handleTeamChange = (teams: string[]) => {
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
assignedTeams: teams, assignedTeams: teams,
})); }))
}; }
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault()
// Here you would normally save to backend // Here you would normally save to backend
console.log("Saving phase:", formData); console.log('Saving phase:', formData)
alert(isEditModalOpen ? "Aşama güncellendi!" : "Yeni aşama oluşturuldu!"); alert(isEditModalOpen ? 'Aşama güncellendi!' : 'Yeni aşama oluşturuldu!')
if (isEditModalOpen) { if (isEditModalOpen) {
closeEditModal(); closeEditModal()
} else { } else {
closeCreateModal(); closeCreateModal()
} }
}; }
const generatePhaseCode = () => { const generatePhaseCode = () => {
const phaseCount = mockProjectPhases.length + 1; const phaseCount = mockProjectPhases.length + 1
return `PH-${phaseCount.toString().padStart(3, "0")}`; return `PH-${phaseCount.toString().padStart(3, '0')}`
}; }
return ( return (
<div> <Container>
<div className="mb-3 mt-2"> <div className="space-y-2">
{/* Header Section with Description */} {/* Header Section with Description */}
<div className="rounded-xl mt-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center justify-between mb-2"> <div className="flex-1">
<div className="flex-1"> <div className="flex items-center gap-3 mb-2">
<div className="flex items-center gap-3 mb-2"> <div>
<div> <h2 className="text-xl font-bold text-gray-900">Proje Aşamaları</h2>
<h2 className="text-lg font-bold text-gray-900"> <p className="text-gray-600">Proje İlerleme Yönetimi</p>
Proje Aşamaları
</h2>
<p className="text-sm text-gray-600">
Proje İlerleme Yönetimi
</p>
</div>
</div> </div>
</div> </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 Aşama
</button>
</div> </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 Aşama
</button>
</div> </div>
{/* Summary Cards */} {/* Summary Cards */}
@ -208,22 +191,14 @@ const ProjectPhases: React.FC = () => {
<Widget <Widget
title="Aktif Aşama" title="Aktif Aşama"
value={ value={mockProjectPhases.filter((p) => p.status === PhaseStatusEnum.InProgress).length}
mockProjectPhases.filter(
(p) => p.status === PhaseStatusEnum.InProgress
).length
}
color="green" color="green"
icon="FaBolt" icon="FaBolt"
/> />
<Widget <Widget
title="Tamamlanan" title="Tamamlanan"
value={ value={mockProjectPhases.filter((p) => p.status === PhaseStatusEnum.Completed).length}
mockProjectPhases.filter(
(p) => p.status === PhaseStatusEnum.Completed
).length
}
color="blue" color="blue"
icon="FaCheckCircle" icon="FaCheckCircle"
/> />
@ -232,9 +207,7 @@ const ProjectPhases: React.FC = () => {
title="Geciken" title="Geciken"
value={ value={
mockProjectPhases.filter( mockProjectPhases.filter(
(p) => (p) => p.status !== PhaseStatusEnum.Completed && new Date(p.endDate) < new Date(),
p.status !== PhaseStatusEnum.Completed &&
new Date(p.endDate) < new Date()
).length ).length
} }
color="red" color="red"
@ -281,124 +254,109 @@ const ProjectPhases: React.FC = () => {
<option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option> <option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option>
</select> </select>
</div> </div>
</div>
{/* Phase Timeline */} {/* Phase Timeline */}
<h3 className="text-base font-semibold mb-2 flex items-center gap-2"> <h3 className="text-base font-semibold mb-2 flex items-center gap-2">
<FaCalendar className="w-4 h-4" /> <FaCalendar className="w-4 h-4" />
Aşama Zaman Çizelgesi Aşama Zaman Çizelgesi
</h3> </h3>
<div className="space-y-2"> <div className="space-y-2">
{filteredPhases {filteredPhases
.sort( .sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())
(a, b) => .map((phase) => (
new Date(a.startDate).getTime() - new Date(b.startDate).getTime() <div key={phase.id} className="relative">
) <div
.map((phase) => ( className="flex items-center gap-2 p-2 border rounded-lg hover:shadow-md transition-shadow cursor-pointer bg-white"
<div key={phase.id} className="relative"> onClick={() => openModal(phase)}
<div >
className="flex items-center gap-2 p-2 border rounded-lg hover:shadow-md transition-shadow cursor-pointer bg-white" <div className="flex-1">
onClick={() => openModal(phase)} <div className="flex items-center justify-between mb-2">
> <div className="flex items-center gap-2">
<div className="flex-1"> <h4 className="font-medium text-gray-900">{phase.name}</h4>
<div className="flex items-center justify-between mb-2"> <span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
<div className="flex items-center gap-2"> {phase.code}
<h4 className="font-medium text-gray-900"> </span>
{phase.name} </div>
</h4> </div>
<span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
{phase.code} {/* Project Information */}
<div className="flex items-center gap-2 mb-2">
<span className="text-xs font-medium text-blue-600">
📋 {phase.project?.name || `Proje ID: ${phase.projectId}`}
</span>
<span className="text-xs text-gray-400">
({phase.project?.code || phase.projectId})
</span> </span>
</div> </div>
</div>
{/* Project Information */} <p className="text-xs text-gray-600 mb-2">{phase.description}</p>
<div className="flex items-center gap-2 mb-2">
<span className="text-xs font-medium text-blue-600">
📋 {phase.project?.name || `Proje ID: ${phase.projectId}`}
</span>
<span className="text-xs text-gray-400">
({phase.project?.code || phase.projectId})
</span>
</div>
<p className="text-xs text-gray-600 mb-2"> {/* Assigned Teams */}
{phase.description} <div className="flex items-center gap-2 mb-2">
</p> <FaUsers className="w-4 h-4 text-gray-500" />
<span className="text-sm font-medium text-gray-700">Atanmış Ekipler:</span>
<div className="flex flex-wrap gap-1">
{phase.assignedTeams.map((team, index) => (
<span
key={index}
className="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs bg-blue-50 text-blue-700 border border-blue-200"
>
{team}
</span>
))}
</div>
</div>
{/* Assigned Teams */} <div className="flex items-center gap-3 text-xs text-gray-500">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-1">
<FaUsers className="w-4 h-4 text-gray-500" /> <FaCalendar className="w-4 h-4" />
<span className="text-sm font-medium text-gray-700"> {new Date(phase.startDate).toLocaleDateString('tr-TR')} -{' '}
Atanmış Ekipler: {new Date(phase.endDate).toLocaleDateString('tr-TR')}
</span> </div>
<div className="flex flex-wrap gap-1"> <div className="flex items-center gap-1">
{phase.assignedTeams.map((team, index) => ( <FaDollarSign className="w-4 h-4" />{phase.budget.toLocaleString()}
<span </div>
key={index} <div className="text-xs text-gray-500">
className="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs bg-blue-50 text-blue-700 border border-blue-200" {phase.completedMilestones}/{phase.milestones} milestone
> </div>
{team}
</span>
))}
</div> </div>
</div> </div>
<div className="flex items-center gap-3 text-xs text-gray-500"> <div className="flex-shrink-0 text-right">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1.5 mb-1.5">
<FaCalendar className="w-4 h-4" /> <span
{new Date(phase.startDate).toLocaleDateString( className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
"tr-TR" phase.status,
)} - {new Date(phase.endDate).toLocaleDateString("tr-TR")}
</div>
<div className="flex items-center gap-1">
<FaDollarSign className="w-4 h-4" />
{phase.budget.toLocaleString()}
</div>
<div className="text-xs text-gray-500">
{phase.completedMilestones}/{phase.milestones} milestone
</div>
</div>
</div>
<div className="flex-shrink-0 text-right">
<div className="flex items-center gap-1.5 mb-1.5">
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
phase.status
)}`}
>
{getPhaseStatusText(phase.status)}
</span>
</div>
<div className="flex items-center gap-1.5 mb-1.5">
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
phase.category
)}`}
>
{phase.category}
</span>
</div>
<div className="flex items-center gap-2 mb-1">
<div className="w-20 bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${getProgressColor(
phase.progress
)}`} )}`}
style={{ width: `${phase.progress}%` }} >
/> {getPhaseStatusText(phase.status)}
</span>
</div> </div>
<span className="text-xs font-medium text-gray-900"> <div className="flex items-center gap-1.5 mb-1.5">
{phase.progress}% <span
</span> className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
phase.category,
)}`}
>
{phase.category}
</span>
</div>
<div className="flex items-center gap-2 mb-1">
<div className="w-20 bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${getProgressColor(phase.progress)}`}
style={{ width: `${phase.progress}%` }}
/>
</div>
<span className="text-xs font-medium text-gray-900">{phase.progress}%</span>
</div>
<div className="text-xs text-gray-500">Tamamlanma Oranı</div>
</div> </div>
<div className="text-xs text-gray-500">Tamamlanma Oranı</div>
</div> </div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
{/* Phase Detail Modal */} {/* Phase Detail Modal */}
@ -412,10 +370,7 @@ const ProjectPhases: React.FC = () => {
{selectedPhase.code} - {selectedPhase.name} {selectedPhase.code} - {selectedPhase.name}
</h3> </h3>
</div> </div>
<button <button onClick={closeModal} className="text-gray-400 hover:text-gray-600">
onClick={closeModal}
className="text-gray-400 hover:text-gray-600"
>
<FaTimesCircle className="w-5 h-5" /> <FaTimesCircle className="w-5 h-5" />
</button> </button>
</div> </div>
@ -424,22 +379,16 @@ const ProjectPhases: React.FC = () => {
<div className="lg:col-span-2"> <div className="lg:col-span-2">
<div className="space-y-3 pt-1"> <div className="space-y-3 pt-1">
<div> <div>
<h4 className="text-sm font-semibold mb-2"> <h4 className="text-sm font-semibold mb-2">Aşama Bilgileri</h4>
Aşama Bilgileri
</h4>
<div className="bg-gray-50 p-2.5 rounded-lg"> <div className="bg-gray-50 p-2.5 rounded-lg">
<p className="text-sm text-gray-900 mb-3"> <p className="text-sm text-gray-900 mb-3">{selectedPhase.description}</p>
{selectedPhase.description}
</p>
<div className="grid grid-cols-2 gap-2 mb-2"> <div className="grid grid-cols-2 gap-2 mb-2">
<div> <div>
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">
Proje Proje
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">{selectedPhase.project?.name}</p>
{selectedPhase.project?.name}
</p>
</div> </div>
<div> <div>
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">
@ -447,7 +396,7 @@ const ProjectPhases: React.FC = () => {
</label> </label>
<span <span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor( className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
selectedPhase.category selectedPhase.category,
)}`} )}`}
> >
{selectedPhase.category} {selectedPhase.category}
@ -462,7 +411,7 @@ const ProjectPhases: React.FC = () => {
</label> </label>
<span <span
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor( className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
selectedPhase.status selectedPhase.status,
)}`} )}`}
> >
{getPhaseStatusIcon(selectedPhase.status)} {getPhaseStatusIcon(selectedPhase.status)}
@ -477,7 +426,7 @@ const ProjectPhases: React.FC = () => {
<div className="flex-1 bg-gray-200 rounded-full h-2"> <div className="flex-1 bg-gray-200 rounded-full h-2">
<div <div
className={`h-2 rounded-full ${getProgressColor( className={`h-2 rounded-full ${getProgressColor(
selectedPhase.progress selectedPhase.progress,
)}`} )}`}
style={{ width: `${selectedPhase.progress}%` }} style={{ width: `${selectedPhase.progress}%` }}
/> />
@ -492,9 +441,7 @@ const ProjectPhases: React.FC = () => {
</div> </div>
<div> <div>
<h4 className="text-sm font-semibold mb-2"> <h4 className="text-sm font-semibold mb-2">Zaman Çizelgesi</h4>
Zaman Çizelgesi
</h4>
<div className="bg-gray-50 p-2.5 rounded-lg"> <div className="bg-gray-50 p-2.5 rounded-lg">
<div className="grid grid-cols-2 gap-2 mb-2"> <div className="grid grid-cols-2 gap-2 mb-2">
<div> <div>
@ -502,9 +449,7 @@ const ProjectPhases: React.FC = () => {
Planlanan Başlangıç Planlanan Başlangıç
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">
{new Date( {new Date(selectedPhase.startDate).toLocaleDateString('tr-TR')}
selectedPhase.startDate
).toLocaleDateString("tr-TR")}
</p> </p>
</div> </div>
<div> <div>
@ -512,15 +457,12 @@ const ProjectPhases: React.FC = () => {
Planlanan Bitiş Planlanan Bitiş
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">
{new Date(selectedPhase.endDate).toLocaleDateString( {new Date(selectedPhase.endDate).toLocaleDateString('tr-TR')}
"tr-TR"
)}
</p> </p>
</div> </div>
</div> </div>
{(selectedPhase.actualStartDate || {(selectedPhase.actualStartDate || selectedPhase.actualEndDate) && (
selectedPhase.actualEndDate) && (
<div className="grid grid-cols-2 gap-2 mb-2 pt-2 border-t"> <div className="grid grid-cols-2 gap-2 mb-2 pt-2 border-t">
<div> <div>
<label className="block text-xs font-medium text-gray-700 mb-1"> <label className="block text-xs font-medium text-gray-700 mb-1">
@ -528,10 +470,10 @@ const ProjectPhases: React.FC = () => {
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">
{selectedPhase.actualStartDate {selectedPhase.actualStartDate
? new Date( ? new Date(selectedPhase.actualStartDate).toLocaleDateString(
selectedPhase.actualStartDate 'tr-TR',
).toLocaleDateString("tr-TR") )
: "-"} : '-'}
</p> </p>
</div> </div>
<div> <div>
@ -540,10 +482,8 @@ const ProjectPhases: React.FC = () => {
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">
{selectedPhase.actualEndDate {selectedPhase.actualEndDate
? new Date( ? new Date(selectedPhase.actualEndDate).toLocaleDateString('tr-TR')
selectedPhase.actualEndDate : '-'}
).toLocaleDateString("tr-TR")
: "-"}
</p> </p>
</div> </div>
</div> </div>
@ -557,20 +497,15 @@ const ProjectPhases: React.FC = () => {
<div <div
className={`text-sm font-semibold ${ className={`text-sm font-semibold ${
calculateScheduleVariance(selectedPhase) > 0 calculateScheduleVariance(selectedPhase) > 0
? "text-red-600" ? 'text-red-600'
: "text-green-600" : 'text-green-600'
}`} }`}
> >
{calculateScheduleVariance(selectedPhase) > 0 ? '+' : ''}
{calculateScheduleVariance(selectedPhase).toFixed(1)}%
{calculateScheduleVariance(selectedPhase) > 0 {calculateScheduleVariance(selectedPhase) > 0
? "+" ? ' (Gecikme)'
: ""} : ' (Erken)'}
{calculateScheduleVariance(selectedPhase).toFixed(
1
)}
%
{calculateScheduleVariance(selectedPhase) > 0
? " (Gecikme)"
: " (Erken)"}
</div> </div>
</div> </div>
)} )}
@ -578,9 +513,7 @@ const ProjectPhases: React.FC = () => {
</div> </div>
<div> <div>
<h4 className="text-sm font-semibold mb-2"> <h4 className="text-sm font-semibold mb-2">Bütçe Analizi</h4>
Bütçe Analizi
</h4>
<div className="bg-gray-50 p-2.5 rounded-lg"> <div className="bg-gray-50 p-2.5 rounded-lg">
<div className="grid grid-cols-2 gap-3 mb-3"> <div className="grid grid-cols-2 gap-3 mb-3">
<div> <div>
@ -609,27 +542,25 @@ const ProjectPhases: React.FC = () => {
className={`text-lg font-semibold ${ className={`text-lg font-semibold ${
calculateBudgetVariance( calculateBudgetVariance(
selectedPhase.budget, selectedPhase.budget,
selectedPhase.actualCost selectedPhase.actualCost,
) > 0 ) > 0
? "text-red-600" ? 'text-red-600'
: "text-green-600" : 'text-green-600'
}`} }`}
> >
{calculateBudgetVariance(selectedPhase.budget, selectedPhase.actualCost) >
0
? '+'
: ''}
{calculateBudgetVariance( {calculateBudgetVariance(
selectedPhase.budget, selectedPhase.budget,
selectedPhase.actualCost selectedPhase.actualCost,
) > 0
? "+"
: ""}
{calculateBudgetVariance(
selectedPhase.budget,
selectedPhase.actualCost
).toFixed(1)} ).toFixed(1)}
% %
<span className="text-sm font-normal text-gray-600 ml-2"> <span className="text-sm font-normal text-gray-600 ml-2">
( (
{Math.abs( {Math.abs(
selectedPhase.actualCost - selectedPhase.budget selectedPhase.actualCost - selectedPhase.budget,
).toLocaleString()} ).toLocaleString()}
) )
</span> </span>
@ -641,10 +572,7 @@ const ProjectPhases: React.FC = () => {
Kalan Bütçe Kalan Bütçe
</label> </label>
<p className="text-sm text-gray-900"> <p className="text-sm text-gray-900">
{(selectedPhase.budget - selectedPhase.actualCost).toLocaleString()}
{(
selectedPhase.budget - selectedPhase.actualCost
).toLocaleString()}
</p> </p>
</div> </div>
</div> </div>
@ -660,8 +588,7 @@ const ProjectPhases: React.FC = () => {
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-600">İlerleme</span> <span className="text-sm text-gray-600">İlerleme</span>
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{selectedPhase.completedMilestones}/ {selectedPhase.completedMilestones}/{selectedPhase.milestones}
{selectedPhase.milestones}
</span> </span>
</div> </div>
<div className="w-full bg-gray-200 rounded-full h-2 mb-3"> <div className="w-full bg-gray-200 rounded-full h-2 mb-3">
@ -669,25 +596,20 @@ const ProjectPhases: React.FC = () => {
className="bg-green-500 h-2 rounded-full" className="bg-green-500 h-2 rounded-full"
style={{ style={{
width: `${ width: `${
(selectedPhase.completedMilestones / (selectedPhase.completedMilestones / selectedPhase.milestones) * 100
selectedPhase.milestones) *
100
}%`, }%`,
}} }}
/> />
</div> </div>
<p className="text-xs text-gray-600"> <p className="text-xs text-gray-600">
{selectedPhase.milestones - {selectedPhase.milestones - selectedPhase.completedMilestones} milestone
selectedPhase.completedMilestones}{" "} kaldı
milestone kaldı
</p> </p>
</div> </div>
</div> </div>
<div> <div>
<h4 className="text-sm font-semibold mb-2"> <h4 className="text-sm font-semibold mb-2">Atanmış Ekipler</h4>
Atanmış Ekipler
</h4>
<div className="space-y-2"> <div className="space-y-2">
{selectedPhase.assignedTeams.map((team, index) => ( {selectedPhase.assignedTeams.map((team, index) => (
<div <div
@ -710,9 +632,7 @@ const ProjectPhases: React.FC = () => {
className="flex items-center gap-2 p-1.5 bg-gray-100 rounded" className="flex items-center gap-2 p-1.5 bg-gray-100 rounded"
> >
<FaBullseye className="w-4 h-4 text-green-500" /> <FaBullseye className="w-4 h-4 text-green-500" />
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">{deliverable}</span>
{deliverable}
</span>
</div> </div>
))} ))}
</div> </div>
@ -728,9 +648,7 @@ const ProjectPhases: React.FC = () => {
className="flex items-center gap-2 p-1.5 bg-red-50 rounded" className="flex items-center gap-2 p-1.5 bg-red-50 rounded"
> >
<FaExclamationTriangle className="w-4 h-4 text-red-500" /> <FaExclamationTriangle className="w-4 h-4 text-red-500" />
<span className="text-sm text-gray-900"> <span className="text-sm text-gray-900">{risk}</span>
{risk}
</span>
</div> </div>
))} ))}
</div> </div>
@ -749,8 +667,8 @@ const ProjectPhases: React.FC = () => {
</button> </button>
<button <button
onClick={() => { onClick={() => {
closeModal(); closeModal()
openEditModal(selectedPhase!); openEditModal(selectedPhase!)
}} }}
className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2" className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2"
> >
@ -778,12 +696,12 @@ const ProjectPhases: React.FC = () => {
</div> </div>
<div> <div>
<h3 className="text-lg font-bold text-gray-900"> <h3 className="text-lg font-bold text-gray-900">
{isEditModalOpen ? "Aşama Düzenle" : "Yeni Aşama Oluştur"} {isEditModalOpen ? 'Aşama Düzenle' : 'Yeni Aşama Oluştur'}
</h3> </h3>
<p className="text-xs text-gray-600"> <p className="text-xs text-gray-600">
{isEditModalOpen {isEditModalOpen
? "Mevcut aşama bilgilerini güncelleyin" ? 'Mevcut aşama bilgilerini güncelleyin'
: "Yeni bir proje aşaması tanımlayın"} : 'Yeni bir proje aşaması tanımlayın'}
</p> </p>
</div> </div>
</div> </div>
@ -830,9 +748,9 @@ const ProjectPhases: React.FC = () => {
{formData.projectId && {formData.projectId &&
(() => { (() => {
const selectedProject = mockProjects.find( const selectedProject = mockProjects.find(
(p) => p.id === formData.projectId (p) => p.id === formData.projectId,
); )
if (!selectedProject) return null; if (!selectedProject) return null
return ( return (
<div className="mt-2 p-2 bg-blue-50 border border-blue-200 rounded-lg"> <div className="mt-2 p-2 bg-blue-50 border border-blue-200 rounded-lg">
@ -846,39 +764,32 @@ const ProjectPhases: React.FC = () => {
</p> </p>
<div className="grid grid-cols-2 gap-2 text-xs"> <div className="grid grid-cols-2 gap-2 text-xs">
<div> <div>
<span className="font-medium text-blue-800"> <span className="font-medium text-blue-800">Durum:</span>
Durum:
</span>
<span <span
className={`ml-1 px-2 py-1 rounded-full text-xs font-medium ${ className={`ml-1 px-2 py-1 rounded-full text-xs font-medium ${
selectedProject.status === selectedProject.status === ProjectStatusEnum.Active
ProjectStatusEnum.Active ? 'bg-green-100 text-green-800'
? "bg-green-100 text-green-800"
: selectedProject.status === : selectedProject.status ===
ProjectStatusEnum.Planning ProjectStatusEnum.Planning
? "bg-yellow-100 text-yellow-800" ? 'bg-yellow-100 text-yellow-800'
: "bg-gray-100 text-gray-800" : 'bg-gray-100 text-gray-800'
}`} }`}
> >
{selectedProject.status === {selectedProject.status === ProjectStatusEnum.Active
ProjectStatusEnum.Active ? 'Aktif'
? "Aktif" : selectedProject.status === ProjectStatusEnum.Planning
: selectedProject.status === ? 'Planlama'
ProjectStatusEnum.Planning : selectedProject.status ===
? "Planlama" ProjectStatusEnum.Completed
: selectedProject.status === ? 'Tamamlandı'
ProjectStatusEnum.Completed : selectedProject.status ===
? "Tamamlandı" ProjectStatusEnum.OnHold
: selectedProject.status === ? 'Beklemede'
ProjectStatusEnum.OnHold : 'Iptal'}
? "Beklemede"
: "Iptal"}
</span> </span>
</div> </div>
<div> <div>
<span className="font-medium text-blue-800"> <span className="font-medium text-blue-800">İlerleme:</span>
İlerleme:
</span>
<span className="ml-1 text-blue-700"> <span className="ml-1 text-blue-700">
%{selectedProject.progress} %{selectedProject.progress}
</span> </span>
@ -888,37 +799,27 @@ const ProjectPhases: React.FC = () => {
Başlangıç: Başlangıç:
</span> </span>
<span className="ml-1 text-blue-700"> <span className="ml-1 text-blue-700">
{selectedProject.startDate.toLocaleDateString( {selectedProject.startDate.toLocaleDateString('tr-TR')}
"tr-TR"
)}
</span> </span>
</div> </div>
<div> <div>
<span className="font-medium text-blue-800"> <span className="font-medium text-blue-800">Bitiş:</span>
Bitiş:
</span>
<span className="ml-1 text-blue-700"> <span className="ml-1 text-blue-700">
{selectedProject.endDate.toLocaleDateString( {selectedProject.endDate.toLocaleDateString('tr-TR')}
"tr-TR"
)}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div className="ml-4 text-right"> <div className="ml-4 text-right">
<div className="text-lg font-bold text-blue-900"> <div className="text-lg font-bold text-blue-900">
{selectedProject.budget.toLocaleString( {selectedProject.budget.toLocaleString('tr-TR')}{' '}
"tr-TR"
)}{" "}
{selectedProject.currency} {selectedProject.currency}
</div> </div>
<div className="text-xs text-blue-600"> <div className="text-xs text-blue-600">Bütçe</div>
Bütçe
</div>
</div> </div>
</div> </div>
</div> </div>
); )
})()} })()}
</div> </div>
<div> <div>
@ -979,21 +880,11 @@ const ProjectPhases: React.FC = () => {
onChange={handleInputChange} 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" 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={PhaseStatusEnum.NotStarted}> <option value={PhaseStatusEnum.NotStarted}>Başlanmadı</option>
Başlanmadı <option value={PhaseStatusEnum.InProgress}>Devam Ediyor</option>
</option> <option value={PhaseStatusEnum.Completed}>Tamamlandı</option>
<option value={PhaseStatusEnum.InProgress}> <option value={PhaseStatusEnum.OnHold}>Beklemede</option>
Devam Ediyor <option value={PhaseStatusEnum.Cancelled}>İptal Edildi</option>
</option>
<option value={PhaseStatusEnum.Completed}>
Tamamlandı
</option>
<option value={PhaseStatusEnum.OnHold}>
Beklemede
</option>
<option value={PhaseStatusEnum.Cancelled}>
İptal Edildi
</option>
</select> </select>
</div> </div>
</div> </div>
@ -1075,9 +966,7 @@ const ProjectPhases: React.FC = () => {
<FaProjectDiagram className="w-4 h-4" /> <FaProjectDiagram className="w-4 h-4" />
<span className="text-sm font-medium">Aşama Kodu:</span> <span className="text-sm font-medium">Aşama Kodu:</span>
<span className="font-bold"> <span className="font-bold">
{isEditModalOpen {isEditModalOpen ? editingPhase?.code : generatePhaseCode()}
? editingPhase?.code
: generatePhaseCode()}
</span> </span>
</div> </div>
</div> </div>
@ -1099,15 +988,15 @@ const ProjectPhases: React.FC = () => {
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 shadow-md hover:shadow-lg" className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 shadow-md hover:shadow-lg"
> >
<FaSave className="w-4 h-4" /> <FaSave className="w-4 h-4" />
{isEditModalOpen ? "Güncelle" : "Oluştur"} {isEditModalOpen ? 'Güncelle' : 'Oluştur'}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
)} )}
</div> </Container>
); )
}; }
export default ProjectPhases; export default ProjectPhases

View file

@ -26,6 +26,7 @@ import {
getPriorityColor, getPriorityColor,
getTaskTypeColor, getTaskTypeColor,
} from "../../../utils/erp"; } from "../../../utils/erp";
import { Container } from "@/components/shared";
const ProjectTasks: React.FC = () => { const ProjectTasks: React.FC = () => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
@ -166,12 +167,12 @@ const ProjectTasks: React.FC = () => {
}; };
return ( return (
<div> <Container>
<div className="mb-3 mt-2"> <div className="space-y-2">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div> <div>
<h2 className="text-xl font-bold text-gray-900">Görev Yönetimi</h2> <h2 className="text-xl font-bold text-gray-900">Görev Yönetimi</h2>
<p className="text-gray-600 mt-1"> <p className="text-gray-600">
Proje görevlerinizi oluşturun, düzenleyin ve takip edin Proje görevlerinizi oluşturun, düzenleyin ve takip edin
</p> </p>
</div> </div>
@ -1156,7 +1157,8 @@ const ProjectTasks: React.FC = () => {
</div> </div>
</div> </div>
)} )}
</div> </Container>
); );
}; };

File diff suppressed because it is too large Load diff

View file

@ -1,99 +1,77 @@
import React, { useState } from "react"; import React, { useState } from 'react'
import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from "react-icons/fa"; import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from 'react-icons/fa'
import { import { PsTaskDailyUpdate, WorkTypeEnum, DailyUpdateStatusEnum } from '../../../types/ps'
PsTaskDailyUpdate, import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
WorkTypeEnum, import { mockEmployees } from '../../../mocks/mockEmployees'
DailyUpdateStatusEnum, import { mockProjectTaskDailyUpdates } from '../../../mocks/mockProjectTaskDailyUpdates'
} from "../../../types/ps"; import Widget from '../../../components/common/Widget'
import { mockProjectTasks } from "../../../mocks/mockProjectTasks"; import { getDailyUpdateStatusColor, getWorkTypeColor } from '../../../utils/erp'
import { mockEmployees } from "../../../mocks/mockEmployees"; import { Container } from '@/components/shared'
import { mockProjectTaskDailyUpdates } from "../../../mocks/mockProjectTaskDailyUpdates";
import Widget from "../../../components/common/Widget";
import {
getDailyUpdateStatusColor,
getWorkTypeColor,
} from "../../../utils/erp";
interface TaskDailyUpdatesFormData { interface TaskDailyUpdatesFormData {
taskId: string; taskId: string
date: string; date: string
hoursWorked: number; hoursWorked: number
description: string; description: string
workType: WorkTypeEnum; workType: WorkTypeEnum
progress: number; progress: number
challenges?: string; challenges?: string
nextSteps?: string; nextSteps?: string
} }
const initialFormData: TaskDailyUpdatesFormData = { const initialFormData: TaskDailyUpdatesFormData = {
taskId: "", taskId: '',
date: new Date().toISOString().split("T")[0], date: new Date().toISOString().split('T')[0],
hoursWorked: 0, hoursWorked: 0,
description: "", description: '',
workType: WorkTypeEnum.Development, workType: WorkTypeEnum.Development,
progress: 0, progress: 0,
challenges: "", challenges: '',
nextSteps: "", nextSteps: '',
}; }
const TaskDailyUpdates: React.FC = () => { const TaskDailyUpdates: React.FC = () => {
const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>( const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>(mockProjectTaskDailyUpdates)
mockProjectTaskDailyUpdates const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>(null)
); const [formData, setFormData] = useState<TaskDailyUpdatesFormData>(initialFormData)
const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>( const [selectedDate, setSelectedDate] = useState<string>(new Date().toISOString().split('T')[0])
null const [selectedEmployee, setSelectedEmployee] = useState<string>(mockEmployees[0].id)
); const [isModalVisible, setIsModalVisible] = useState(false)
const [formData, setFormData] = const [searchQuery, setSearchQuery] = useState<string>('')
useState<TaskDailyUpdatesFormData>(initialFormData); const [selectedEmployeeForForm, setSelectedEmployeeForForm] = useState<string>(
const [selectedDate, setSelectedDate] = useState<string>( mockEmployees[0].id,
new Date().toISOString().split("T")[0] )
);
const [selectedEmployee, setSelectedEmployee] = useState<string>(
mockEmployees[0].id
);
const [isModalVisible, setIsModalVisible] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>("");
const [selectedEmployeeForForm, setSelectedEmployeeForForm] =
useState<string>(mockEmployees[0].id);
// Filter updates based on selected filters // Filter updates based on selected filters
const filteredUpdates = updates.filter((update) => { const filteredUpdates = updates.filter((update) => {
const matchesDate = const matchesDate = !selectedDate || update.date.toISOString().split('T')[0] === selectedDate
!selectedDate || update.date.toISOString().split("T")[0] === selectedDate; const matchesEmployee = update.employeeId === selectedEmployee
const matchesEmployee = update.employeeId === selectedEmployee;
const matchesSearch = const matchesSearch =
!searchQuery || !searchQuery ||
update.description?.toLowerCase().includes(searchQuery.toLowerCase()) || update.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
update.task?.name?.toLowerCase().includes(searchQuery.toLowerCase()) || update.task?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
update.employee?.firstName update.employee?.firstName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
?.toLowerCase() update.employee?.lastName?.toLowerCase().includes(searchQuery.toLowerCase())
.includes(searchQuery.toLowerCase()) || return matchesDate && matchesEmployee && matchesSearch
update.employee?.lastName })
?.toLowerCase()
.includes(searchQuery.toLowerCase());
return matchesDate && matchesEmployee && matchesSearch;
});
// Get tasks for the selected employee in form // Get tasks for the selected employee in form
const formUserTasks = mockProjectTasks.filter( const formUserTasks = mockProjectTasks.filter(
(task) => task.assignedTo === selectedEmployeeForForm (task) => task.assignedTo === selectedEmployeeForForm,
); )
const handleInputChange = ( const handleInputChange = (
e: React.ChangeEvent< e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => { ) => {
const { name, value } = e.target; const { name, value } = e.target
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: [name]: name === 'hoursWorked' || name === 'progress' ? Number(value) : value,
name === "hoursWorked" || name === "progress" ? Number(value) : value, }))
})); }
};
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault()
const newUpdate: PsTaskDailyUpdate = { const newUpdate: PsTaskDailyUpdate = {
id: editingUpdate ? editingUpdate.id : Date.now().toString(), id: editingUpdate ? editingUpdate.id : Date.now().toString(),
@ -112,86 +90,268 @@ const TaskDailyUpdates: React.FC = () => {
attachments: [], attachments: [],
creationTime: editingUpdate ? editingUpdate.creationTime : new Date(), creationTime: editingUpdate ? editingUpdate.creationTime : new Date(),
lastModificationTime: new Date(), lastModificationTime: new Date(),
}; }
if (editingUpdate) { if (editingUpdate) {
setUpdates((prev) => setUpdates((prev) =>
prev.map((update) => prev.map((update) => (update.id === editingUpdate.id ? newUpdate : update)),
update.id === editingUpdate.id ? newUpdate : update )
)
);
} else { } else {
setUpdates((prev) => [...prev, newUpdate]); setUpdates((prev) => [...prev, newUpdate])
} }
resetForm(); resetForm()
}; }
const resetForm = () => { const resetForm = () => {
setFormData(initialFormData); setFormData(initialFormData)
setIsModalVisible(false); setIsModalVisible(false)
setEditingUpdate(null); setEditingUpdate(null)
setSelectedEmployeeForForm(mockEmployees[0].id); setSelectedEmployeeForForm(mockEmployees[0].id)
}; }
const handleEdit = (update: PsTaskDailyUpdate) => { const handleEdit = (update: PsTaskDailyUpdate) => {
setEditingUpdate(update); setEditingUpdate(update)
setSelectedEmployeeForForm(update.employeeId); setSelectedEmployeeForForm(update.employeeId)
setFormData({ setFormData({
taskId: update.taskId, taskId: update.taskId,
date: update.date.toISOString().split("T")[0], date: update.date.toISOString().split('T')[0],
hoursWorked: update.hoursWorked, hoursWorked: update.hoursWorked,
description: update.description, description: update.description,
workType: update.workType, workType: update.workType,
progress: update.progress, progress: update.progress,
challenges: update.challenges || "", challenges: update.challenges || '',
nextSteps: update.nextSteps || "", nextSteps: update.nextSteps || '',
}); })
setIsModalVisible(true); setIsModalVisible(true)
}; }
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
if ( if (window.confirm('Bu günlük raporu silmek istediğinizden emin misiniz?')) {
window.confirm("Bu günlük raporu silmek istediğinizden emin misiniz?") setUpdates((prev) => prev.filter((update) => update.id !== id))
) {
setUpdates((prev) => prev.filter((update) => update.id !== id));
} }
}; }
const handleStatusChange = (id: string, status: DailyUpdateStatusEnum) => { const handleStatusChange = (id: string, status: DailyUpdateStatusEnum) => {
setUpdates((prev) => setUpdates((prev) => prev.map((update) => (update.id === id ? { ...update, status } : update)))
prev.map((update) => (update.id === id ? { ...update, status } : update)) }
);
};
const todayTotalHours = filteredUpdates const todayTotalHours = filteredUpdates
.filter( .filter(
(update) => (update) =>
update.date.toISOString().split("T")[0] === update.date.toISOString().split('T')[0] === new Date().toISOString().split('T')[0],
new Date().toISOString().split("T")[0]
) )
.reduce((sum, update) => sum + update.hoursWorked, 0); .reduce((sum, update) => sum + update.hoursWorked, 0)
return ( return (
<div className="space-y-6"> <Container>
<div className="space-y-2">
{/* Header */}
<div className="mb-4 mt-2">
<div className="flex items-center justify-between mb-3">
<div>
<h2 className="text-lg font-bold text-gray-900">Günlük Aktivite Takibi</h2>
<p className="text-gray-600">
Günlük işlerinizi planlayın, tamamlanan görevleri işaretleyin
</p>
</div>
<button
onClick={() => setIsModalVisible(true)}
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ünlük Rapor
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-2">
<Widget
title="Bugün Toplam"
value={`${todayTotalHours}h`}
color="blue"
icon="FaClock"
/>
<Widget
title="Onaylanan"
value={
filteredUpdates.filter((update) => update.status === DailyUpdateStatusEnum.Approved)
.length
}
color="green"
icon="FaCheckCircle"
/>
<Widget
title="Beklemede"
value={
filteredUpdates.filter(
(update) => update.status === DailyUpdateStatusEnum.Submitted,
).length
}
color="yellow"
icon="FaHourglassHalf"
/>
<Widget
title="Taslak Rapor"
value={
filteredUpdates.filter((update) => update.status === DailyUpdateStatusEnum.Draft)
.length
}
color="gray"
icon="FaFileAlt"
/>
</div>
{/* Filters */}
<div className="bg-white grid grid-cols-6 gap-2 p-2 rounded-lg shadow-sm border border-gray-200 mt-3 mb-3">
<div className="col-span-2">
<select
value={selectedEmployee}
onChange={(e) => setSelectedEmployee(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.id}>
{employee.firstName} {employee.lastName}
</option>
))}
</select>
</div>
<div className="col-span-3">
<input
type="text"
placeholder="Açıklama, görev veya çalışan adı..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="col-span-1">
<input
type="date"
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
{/* Updates List */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-1.5 border-b border-gray-200">
<h3 className="text-base font-semibold text-gray-900 ml-1.5">Günlük Raporlar</h3>
</div>
<div className="divide-y divide-gray-200">
{filteredUpdates.length === 0 ? (
<div className="p-4 text-center text-gray-500">
<FaCalendarAlt className="h-10 w-10 mx-auto text-gray-300 mb-2" />
<p className="text-sm">Henüz günlük rapor bulunmuyor.</p>
<p className="text-sm">Yeni bir rapor eklemek için yukarıdaki butonu kullanın.</p>
</div>
) : (
filteredUpdates.map((update) => (
<div key={update.id} className="p-3 hover:bg-gray-50">
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1.5">
<h4 className="font-medium text-sm text-gray-900">
{update.task?.name || 'Bilinmeyen Görev'}
</h4>
<span
className={`px-2 py-1 text-xs rounded-full ${getDailyUpdateStatusColor(
update.status,
)}`}
>
{update.status}
</span>
<span
className={`px-2 py-1 text-xs rounded-full ${getWorkTypeColor(
update.workType,
)}`}
>
{update.workType}
</span>
</div>
<div className="grid grid-cols-3 gap-3 mb-2 text-xs text-gray-600">
<div>
<span className="font-medium">Tarih:</span>{' '}
{update.date.toLocaleDateString('tr-TR')}
</div>
<div>
<span className="font-medium">Çalışılan Saat:</span> {update.hoursWorked}h
</div>
<div>
<span className="font-medium">İlerleme:</span> %{update.progress}
</div>
</div>
<p className="text-sm text-gray-700 mb-2">{update.description}</p>
{update.challenges && (
<div className="mb-2">
<span className="text-xs font-medium text-red-700">Zorluklar:</span>
<p className="text-sm text-gray-600">{update.challenges}</p>
</div>
)}
{update.nextSteps && (
<div>
<span className="text-xs font-medium text-blue-700">
Sonraki Adımlar:
</span>
<p className="text-sm text-gray-600">{update.nextSteps}</p>
</div>
)}
</div>
<div className="flex items-center gap-1">
{update.status === DailyUpdateStatusEnum.Draft && (
<button
onClick={() =>
handleStatusChange(update.id, DailyUpdateStatusEnum.Submitted)
}
className="px-2 py-0.5 text-xs bg-blue-600 text-white hover:bg-blue-700 rounded"
>
Gönder
</button>
)}
<button
onClick={() => handleEdit(update)}
className="p-1 text-gray-400 hover:text-blue-600"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(update.id)}
className="p-1 text-gray-400 hover:text-red-600"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
{/* Modal */} {/* Modal */}
{isModalVisible && ( {isModalVisible && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl p-3 w-full max-w-lg max-h-[90vh] overflow-y-auto"> <div className="bg-white rounded-lg shadow-xl p-3 w-full max-w-lg max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h3 className="text-base font-semibold text-gray-900"> <h3 className="text-base font-semibold text-gray-900">
{editingUpdate ? "Günlük Rapor Düzenle" : "Yeni Günlük Rapor"} {editingUpdate ? 'Günlük Rapor Düzenle' : 'Yeni Günlük Rapor'}
</h3> </h3>
<button <button onClick={resetForm} className="text-gray-400 hover:text-gray-600">
onClick={resetForm} <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
className="text-gray-400 hover:text-gray-600"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@ -211,8 +371,8 @@ const TaskDailyUpdates: React.FC = () => {
<select <select
value={selectedEmployeeForForm} value={selectedEmployeeForForm}
onChange={(e) => { onChange={(e) => {
setSelectedEmployeeForForm(e.target.value); setSelectedEmployeeForForm(e.target.value)
setFormData((prev) => ({ ...prev, taskId: "" })); // Reset task selection setFormData((prev) => ({ ...prev, taskId: '' })) // Reset task selection
}} }}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500" className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
> >
@ -239,8 +399,8 @@ const TaskDailyUpdates: React.FC = () => {
<option value="">Görev Seçin</option> <option value="">Görev Seçin</option>
{formUserTasks.map((task) => ( {formUserTasks.map((task) => (
<option key={task.id} value={task.id}> <option key={task.id} value={task.id}>
{task.phase?.project?.name || "Proje"} {" "} {task.phase?.project?.name || 'Proje'} {task.phase?.name || 'Aşama'} {' '}
{task.phase?.name || "Aşama"} {task.name} {task.name}
</option> </option>
))} ))}
</select> </select>
@ -263,9 +423,7 @@ const TaskDailyUpdates: React.FC = () => {
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-1">İş Türü</label>
İş Türü
</label>
<select <select
name="workType" name="workType"
value={formData.workType} value={formData.workType}
@ -371,231 +529,15 @@ const TaskDailyUpdates: React.FC = () => {
className="bg-blue-600 text-white py-1 px-3 text-sm rounded-md hover:bg-blue-700 flex items-center justify-center gap-2" className="bg-blue-600 text-white py-1 px-3 text-sm rounded-md hover:bg-blue-700 flex items-center justify-center gap-2"
> >
<FaSave className="h-4 w-4" /> <FaSave className="h-4 w-4" />
{editingUpdate ? "Güncelle" : "Kaydet"} {editingUpdate ? 'Güncelle' : 'Kaydet'}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
)} )}
</Container>
)
}
{/* Header */} export default TaskDailyUpdates
<div className="mb-4 mt-2">
<div className="flex items-center justify-between mb-3">
<div>
<h2 className="text-lg font-bold text-gray-900">
Günlük Aktivite Takibi
</h2>
<p className="text-sm text-gray-600">
Günlük işlerinizi planlayın, tamamlanan görevleri işaretleyin
</p>
</div>
<button
onClick={() => setIsModalVisible(true)}
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ünlük Rapor
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-2">
<Widget
title="Bugün Toplam"
value={`${todayTotalHours}h`}
color="blue"
icon="FaClock"
/>
<Widget
title="Onaylanan"
value={
filteredUpdates.filter(
(update) => update.status === DailyUpdateStatusEnum.Approved
).length
}
color="green"
icon="FaCheckCircle"
/>
<Widget
title="Beklemede"
value={
filteredUpdates.filter(
(update) => update.status === DailyUpdateStatusEnum.Submitted
).length
}
color="yellow"
icon="FaHourglassHalf"
/>
<Widget
title="Taslak Rapor"
value={
filteredUpdates.filter(
(update) => update.status === DailyUpdateStatusEnum.Draft
).length
}
color="gray"
icon="FaFileAlt"
/>
</div>
{/* Filters */}
<div className="bg-white grid grid-cols-6 gap-2 p-2 rounded-lg shadow-sm border border-gray-200 mt-3 mb-3">
<div className="col-span-2">
<select
value={selectedEmployee}
onChange={(e) => setSelectedEmployee(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
{mockEmployees.map((employee) => (
<option key={employee.id} value={employee.id}>
{employee.firstName} {employee.lastName}
</option>
))}
</select>
</div>
<div className="col-span-3">
<input
type="text"
placeholder="Açıklama, görev veya çalışan adı..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="col-span-1">
<input
type="date"
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
{/* Updates List */}
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="p-1.5 border-b border-gray-200">
<h3 className="text-base font-semibold text-gray-900 ml-1.5">
Günlük Raporlar
</h3>
</div>
<div className="divide-y divide-gray-200">
{filteredUpdates.length === 0 ? (
<div className="p-4 text-center text-gray-500">
<FaCalendarAlt className="h-10 w-10 mx-auto text-gray-300 mb-2" />
<p className="text-sm">Henüz günlük rapor bulunmuyor.</p>
<p className="text-sm">
Yeni bir rapor eklemek için yukarıdaki butonu kullanın.
</p>
</div>
) : (
filteredUpdates.map((update) => (
<div key={update.id} className="p-3 hover:bg-gray-50">
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1.5">
<h4 className="font-medium text-sm text-gray-900">
{update.task?.name || "Bilinmeyen Görev"}
</h4>
<span
className={`px-2 py-1 text-xs rounded-full ${getDailyUpdateStatusColor(
update.status
)}`}
>
{update.status}
</span>
<span
className={`px-2 py-1 text-xs rounded-full ${getWorkTypeColor(
update.workType
)}`}
>
{update.workType}
</span>
</div>
<div className="grid grid-cols-3 gap-3 mb-2 text-xs text-gray-600">
<div>
<span className="font-medium">Tarih:</span>{" "}
{update.date.toLocaleDateString("tr-TR")}
</div>
<div>
<span className="font-medium">Çalışılan Saat:</span>{" "}
{update.hoursWorked}h
</div>
<div>
<span className="font-medium">İlerleme:</span> %
{update.progress}
</div>
</div>
<p className="text-sm text-gray-700 mb-2">
{update.description}
</p>
{update.challenges && (
<div className="mb-2">
<span className="text-xs font-medium text-red-700">
Zorluklar:
</span>
<p className="text-sm text-gray-600">
{update.challenges}
</p>
</div>
)}
{update.nextSteps && (
<div>
<span className="text-xs font-medium text-blue-700">
Sonraki Adımlar:
</span>
<p className="text-sm text-gray-600">
{update.nextSteps}
</p>
</div>
)}
</div>
<div className="flex items-center gap-1">
{update.status === DailyUpdateStatusEnum.Draft && (
<button
onClick={() =>
handleStatusChange(
update.id,
DailyUpdateStatusEnum.Submitted
)
}
className="px-2 py-0.5 text-xs bg-blue-600 text-white hover:bg-blue-700 rounded"
>
Gönder
</button>
)}
<button
onClick={() => handleEdit(update)}
className="p-1 text-gray-400 hover:text-blue-600"
>
<FaEdit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(update.id)}
className="p-1 text-gray-400 hover:text-red-600"
>
<FaTrash className="h-4 w-4" />
</button>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
);
};
export default TaskDailyUpdates;

View file

@ -84,7 +84,7 @@ const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => {
}; };
return ( return (
<div className="space-y-1"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="font-medium">İlerleme</span> <span className="font-medium">İlerleme</span>
<span>%{progress}</span> <span>%{progress}</span>