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

View file

@ -1,13 +1,13 @@
import React, { useState, useEffect } from "react";
import { FaSave, FaTimes } from "react-icons/fa";
import { PsActivityTypeEnum, PsActivity } from "../../../types/ps";
import { getPsActivityTypeText } from "../../../utils/erp";
import React, { useState, useEffect } from 'react'
import { FaSave, FaTimes } from 'react-icons/fa'
import { PsActivityTypeEnum, PsActivity } from '../../../types/ps'
import { getPsActivityTypeText } from '../../../utils/erp'
interface ActivityTypeFormModalProps {
open: boolean;
activityType: PsActivity | null;
onClose: () => void;
onSave: (activityType: Partial<PsActivity>) => void;
open: boolean
activityType: PsActivity | null
onClose: () => void
onSave: (activityType: Partial<PsActivity>) => void
}
const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
@ -17,98 +17,99 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
onSave,
}) => {
const [formData, setFormData] = useState<Partial<PsActivity>>({
name: "",
description: "",
category: "",
name: '',
description: '',
category: '',
defaultDuration: 1,
activityType: PsActivityTypeEnum.WorkLog,
isActive: true,
});
})
const [errors, setErrors] = useState<Record<string, string>>({});
const [errors, setErrors] = useState<Record<string, string>>({})
useEffect(() => {
if (activityType) {
setFormData({
...activityType,
});
})
} else {
setFormData({
name: "",
description: "",
category: "",
name: '',
description: '',
category: '',
defaultDuration: 1,
activityType: PsActivityTypeEnum.WorkLog,
isActive: true,
});
})
}
setErrors({});
}, [activityType]);
setErrors({})
}, [activityType])
const handleInputChange = (
field: keyof PsActivity,
value: string | number | boolean | PsActivityTypeEnum
value: string | number | boolean | PsActivityTypeEnum,
) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
}))
// Clear error when user starts typing
if (errors[field]) {
setErrors((prev) => ({
...prev,
[field]: "",
}));
[field]: '',
}))
}
}
};
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
const newErrors: Record<string, string> = {}
if (!formData.name?.trim()) {
newErrors.name = "Aktivite türü adı zorunludur";
newErrors.name = 'Aktivite türü adı zorunludur'
}
if (!formData.description?.trim()) {
newErrors.description = "Açıklama zorunludur";
newErrors.description = 'Açıklama zorunludur'
}
if (!formData.category?.trim()) {
newErrors.category = "Kategori zorunludur";
newErrors.category = 'Kategori zorunludur'
}
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);
return Object.keys(newErrors).length === 0;
};
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSave = () => {
if (validateForm()) {
onSave(formData);
onClose();
onSave(formData)
onClose()
}
}
};
if (!open) return null;
if (!open) return null
return (
<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
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={onClose}
></div>
<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="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="space-y-3">
<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ü"}
{activityType ? 'Aktivite Türü Düzenle' : 'Yeni Aktivite Türü'}
</h3>
<button
onClick={onClose}
@ -128,15 +129,12 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
Aktivite Türü *
</label>
<select
value={formData.activityType || ""}
value={formData.activityType || ''}
onChange={(e) =>
handleInputChange(
"activityType",
e.target.value as PsActivityTypeEnum
)
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"
errors.activityType ? 'border-red-500' : 'border-gray-300'
}`}
>
{Object.values(PsActivityTypeEnum).map((type) => (
@ -146,29 +144,23 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
))}
</select>
{errors.activityType && (
<p className="mt-1 text-sm text-red-600">
{errors.activityType}
</p>
<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>
<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)}
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"
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>
)}
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
</div>
{/* Description */}
@ -177,20 +169,16 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
ıklama *
</label>
<textarea
value={formData.description || ""}
onChange={(e) =>
handleInputChange("description", e.target.value)
}
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"
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>
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
)}
</div>
@ -201,12 +189,10 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
</label>
<input
type="text"
value={formData.category || ""}
onChange={(e) =>
handleInputChange("category", e.target.value)
}
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"
errors.category ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="Kategori adını girin (örn: Görev Yönetimi)"
/>
@ -224,24 +210,17 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
type="number"
min="0"
step="0.25"
value={formData.defaultDuration || ""}
value={formData.defaultDuration || ''}
onChange={(e) =>
handleInputChange(
"defaultDuration",
parseFloat(e.target.value)
)
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"
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>
<p className="mt-1 text-sm text-red-600">{errors.defaultDuration}</p>
)}
</div>
@ -251,15 +230,10 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
type="checkbox"
id="isActive"
checked={formData.isActive || false}
onChange={(e) =>
handleInputChange("isActive", e.target.checked)
}
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"
>
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
Aktif durumda
</label>
</div>
@ -287,7 +261,9 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
</div>
</div>
</div>
);
};
</div>
</div>
)
}
export default ActivityTypeFormModal;
export default ActivityTypeFormModal

View file

@ -1,108 +1,85 @@
import React, { useState } from "react";
import {
FaSearch,
FaPlus,
FaEdit,
FaTrash,
FaEye,
FaTh,
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";
import React, { useState } from 'react'
import { FaSearch, FaPlus, FaEdit, FaTrash, FaEye, FaTh, 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'
import { Container } from '@/components/shared'
const ActivityTypes: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [categoryFilter, setCategoryFilter] = useState<string>("");
const [selectedActivity, setSelectedActivity] = useState<string>("");
const [showActivityForm, setShowActivityForm] = useState(false);
const [editingActivity, setEditingActivity] = useState<PsActivity | null>(
null
);
const [viewMode, setViewMode] = useState<"card" | "list">("card");
const [searchTerm, setSearchTerm] = useState('')
const [categoryFilter, setCategoryFilter] = useState<string>('')
const [selectedActivity, setSelectedActivity] = useState<string>('')
const [showActivityForm, setShowActivityForm] = useState(false)
const [editingActivity, setEditingActivity] = useState<PsActivity | null>(null)
const [viewMode, setViewMode] = useState<'card' | 'list'>('card')
// Helper functions
const handleEdit = (activity: PsActivity) => {
setEditingActivity(activity);
setShowActivityForm(true);
};
setEditingActivity(activity)
setShowActivityForm(true)
}
const handleNew = () => {
setEditingActivity(null);
setShowActivityForm(true);
};
setEditingActivity(null)
setShowActivityForm(true)
}
const handleCloseForm = () => {
setShowActivityForm(false);
setEditingActivity(null);
};
setShowActivityForm(false)
setEditingActivity(null)
}
const handleSave = (activityData: Partial<PsActivity>) => {
// 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) {
console.log(
"Updating existing activity type with ID:",
editingActivity.id
);
console.log('Updating existing activity type with ID:', editingActivity.id)
} else {
console.log("Creating new activity type");
console.log('Creating new activity type')
}
// 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 matchesSearch =
activity.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(activity.description &&
activity.description.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesCategory =
categoryFilter === "" || activity.category === categoryFilter;
return matchesSearch && matchesCategory;
});
activity.description.toLowerCase().includes(searchTerm.toLowerCase()))
const matchesCategory = categoryFilter === '' || activity.category === categoryFilter
return matchesSearch && matchesCategory
})
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 (
<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="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={() => setSelectedActivity("")}
onClick={() => setSelectedActivity('')}
></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="flex items-center justify-between mb-4">
<h3 className="text-lg font-medium text-gray-900">
Aktivite Türü Detayları
</h3>
<h3 className="text-lg font-medium text-gray-900">Aktivite Türü Detayları</h3>
<button
onClick={() => setSelectedActivity("")}
onClick={() => setSelectedActivity('')}
className="text-gray-400 hover:text-gray-600"
>
<svg
className="w-5 h-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -117,16 +94,12 @@ const ActivityTypes: React.FC = () => {
{/* Activity Header */}
<div className="flex items-center gap-3">
<div
className={`p-2 rounded-lg ${getPsActivityTypeColor(
activity.activityType
)}`}
className={`p-2 rounded-lg ${getPsActivityTypeColor(activity.activityType)}`}
>
<ActivityIcon className="w-6 h-6" />
</div>
<div>
<h4 className="text-lg font-medium text-gray-900">
{activity.name}
</h4>
<h4 className="text-lg font-medium text-gray-900">{activity.name}</h4>
<p className="text-sm text-gray-500">{activity.category}</p>
</div>
</div>
@ -134,9 +107,7 @@ const ActivityTypes: React.FC = () => {
{/* Activity Details */}
<div className="grid grid-cols-2 gap-2">
<div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2">
Genel Bilgiler
</h5>
<h5 className="font-medium text-gray-900 mb-2">Genel Bilgiler</h5>
<div className="space-y-2 text-sm">
<div>
<strong>Aktivite Türü:</strong> {activity.activityType}
@ -145,38 +116,33 @@ const ActivityTypes: React.FC = () => {
<strong>Kategori:</strong> {activity.category}
</div>
<div>
<strong>Varsayılan Süre:</strong>{" "}
{activity.defaultDuration} saat
<strong>Varsayılan Süre:</strong> {activity.defaultDuration} saat
</div>
<div>
<strong>Durum:</strong>
<span
className={`ml-2 px-2 py-1 text-xs rounded-full ${
activity.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{activity.isActive ? "Aktif" : "Pasif"}
{activity.isActive ? 'Aktif' : 'Pasif'}
</span>
</div>
</div>
</div>
<div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2">
Zaman Bilgileri
</h5>
<h5 className="font-medium text-gray-900 mb-2">Zaman Bilgileri</h5>
<div className="space-y-2 text-sm">
<div>
<strong>Oluşturulma:</strong>{" "}
{activity.creationTime.toLocaleDateString("tr-TR")}
<strong>Oluşturulma:</strong>{' '}
{activity.creationTime.toLocaleDateString('tr-TR')}
</div>
<div>
<strong>Son Güncelleme:</strong>{" "}
{activity.lastModificationTime.toLocaleDateString(
"tr-TR"
)}
<strong>Son Güncelleme:</strong>{' '}
{activity.lastModificationTime.toLocaleDateString('tr-TR')}
</div>
</div>
</div>
@ -184,45 +150,42 @@ const ActivityTypes: React.FC = () => {
<div className="bg-gray-50 rounded-lg p-2">
<h5 className="font-medium text-gray-900 mb-2">ıklama</h5>
<p className="text-sm text-gray-700">
{activity.description}
</p>
<p className="text-sm text-gray-700">{activity.description}</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
)
}
return (
<div className="space-y-2 pt-2">
<Container>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Aktivite Türleri</h2>
<p className="text-gray-600 mt-1">
Proje ve görev aktivitelerinin türlerini yönetin
</p>
<p className="text-gray-600 mt-1">Proje ve görev aktivitelerinin türlerini yönetin</p>
</div>
<div className="flex gap-1.5">
<div className="flex border border-gray-300 rounded-lg overflow-hidden">
<button
onClick={() => setViewMode("card")}
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"
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")}
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"
viewMode === 'list'
? 'bg-blue-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
<FaList className="w-4 h-4" />
@ -264,8 +227,7 @@ const ActivityTypes: React.FC = () => {
<Widget
title="Ortalama Süre"
value={`${(
activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) /
activityTypes.length
activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) / activityTypes.length
).toFixed(1)}h`}
color="orange"
icon="FaFlask"
@ -299,10 +261,10 @@ const ActivityTypes: React.FC = () => {
</div>
{/* Activity Types - Card/List View */}
{viewMode === "card" ? (
{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);
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
return (
<div
@ -313,18 +275,14 @@ const ActivityTypes: React.FC = () => {
<div className="flex items-center gap-3">
<div
className={`p-2 rounded-lg ${getPsActivityTypeColor(
activity.activityType
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>
<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">
@ -342,43 +300,34 @@ const ActivityTypes: React.FC = () => {
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-red-600 hover:text-red-900 p-1"
title="Sil"
>
<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>
<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="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"
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{activity.isActive ? "Aktif" : "Pasif"}
{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>
<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">
@ -406,9 +355,7 @@ const ActivityTypes: React.FC = () => {
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredActivities.map((activity) => {
const ActivityIcon = getPsActivityTypeIcon(
activity.activityType
);
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
return (
<tr key={activity.id} className="hover:bg-gray-50 text-sm">
@ -416,7 +363,7 @@ const ActivityTypes: React.FC = () => {
<div className="flex items-center">
<div
className={`p-2 rounded-lg mr-3 ${getPsActivityTypeColor(
activity.activityType
activity.activityType,
)}`}
>
<ActivityIcon className="w-4 h-4" />
@ -425,16 +372,12 @@ const ActivityTypes: React.FC = () => {
<div className="text-sm font-medium text-gray-900">
{activity.name}
</div>
<div className="text-sm text-gray-500">
{activity.description}
</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>
<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">
@ -445,17 +388,15 @@ const ActivityTypes: React.FC = () => {
<span
className={`px-2 py-1 text-xs rounded-full ${
activity.isActive
? "bg-green-100 text-green-800"
: "bg-red-100 text-red-800"
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}
>
{activity.isActive ? "Aktif" : "Pasif"}
{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"
)}
{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">
@ -473,22 +414,20 @@ const ActivityTypes: React.FC = () => {
>
<FaEdit className="w-4 h-4" />
</button>
<button
className="text-red-600 hover:text-red-900"
title="Sil"
>
<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>
{/* Activity Detail Modal */}
<ActivityDetailModal />
@ -500,8 +439,8 @@ const ActivityTypes: React.FC = () => {
onClose={handleCloseForm}
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 {
FaUser,
FaChevronDown,
FaChevronRight,
FaChevronLeft,
FaChevronRight as FaArrowRight,
} from "react-icons/fa";
import { PsGanttTask } from "../../../types/ps";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { mockProjects } from "../../../mocks/mockProjects";
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
import { PriorityEnum } from "../../../types/common";
import { getPriorityColor, getProjectStatusColor } from "../../../utils/erp";
} from 'react-icons/fa'
import { PsGanttTask } from '../../../types/ps'
import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockProjects } from '../../../mocks/mockProjects'
import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
import { PriorityEnum } from '../../../types/common'
import { getPriorityColor, getProjectStatusColor } from '../../../utils/erp'
import { Container } from '@/components/shared'
interface ProjectGanttProps {
employeeId?: string;
employeeId?: string
}
const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
// İlk 2 projeyi ve fazlarını varsayılan olarak açık başlat
const getInitialExpandedItems = () => {
const expandedItems = new Set<string>();
const expandedItems = new Set<string>()
// İlk 2 projeyi aç
const firstTwoProjects = mockProjects.slice(0, 2);
const firstTwoProjects = mockProjects.slice(0, 2)
firstTwoProjects.forEach((project) => {
expandedItems.add(`project-${project.id}`);
expandedItems.add(`project-${project.id}`)
// Bu projelerin fazlarını da aç
const projectPhases = mockProjectPhases.filter(
(phase) => phase.projectId === project.id
);
const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
projectPhases.forEach((phase) => {
expandedItems.add(`phase-${phase.id}`);
});
});
expandedItems.add(`phase-${phase.id}`)
})
})
return expandedItems;
};
return expandedItems
}
const [expandedItems, setExpandedItems] = useState<Set<string>>(
getInitialExpandedItems()
);
const [expandedItems, setExpandedItems] = useState<Set<string>>(getInitialExpandedItems())
const [selectedEmployee, setSelectedEmployee] = useState<string>(
employeeId || mockEmployees[0]?.id || ""
);
const [viewMode, setViewMode] = useState<"day" | "week" | "month" | "year">(
"week"
);
const [currentDate, setCurrentDate] = useState<Date>(new Date());
employeeId || mockEmployees[0]?.id || '',
)
const [viewMode, setViewMode] = useState<'day' | 'week' | 'month' | 'year'>('week')
const [currentDate, setCurrentDate] = useState<Date>(new Date())
// Gantt chart için tarih aralığı oluştur
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
const hours = [];
const hours = []
for (let i = 0; i < 24; i++) {
const hour = new Date(startDate);
hour.setHours(i, 0, 0, 0);
hours.push(hour);
const hour = new Date(startDate)
hour.setHours(i, 0, 0, 0)
hours.push(hour)
}
return hours;
} else if (viewMode === "week") {
return hours
} else if (viewMode === 'week') {
// Haftalık görünüm: hafta başından itibaren 7 gün
const weekStart = new Date(startDate);
const dayOfWeek = weekStart.getDay();
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Pazartesi = 0, Pazar = 6
weekStart.setDate(weekStart.getDate() - daysToSubtract);
weekStart.setHours(0, 0, 0, 0);
const weekStart = new Date(startDate)
const dayOfWeek = weekStart.getDay()
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1 // Pazartesi = 0, Pazar = 6
weekStart.setDate(weekStart.getDate() - daysToSubtract)
weekStart.setHours(0, 0, 0, 0)
const dates = [];
const dates = []
for (let i = 0; i < 7; i++) {
const date = new Date(weekStart);
date.setDate(weekStart.getDate() + i);
dates.push(date);
const date = new Date(weekStart)
date.setDate(weekStart.getDate() + i)
dates.push(date)
}
return dates;
} else if (viewMode === "month") {
return dates
} else if (viewMode === 'month') {
// Aylık görünüm: ay başından itibaren ay sonu
const monthStart = new Date(
currentDate.getFullYear(),
currentDate.getMonth(),
1
);
const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
const daysInMonth = new Date(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
0
).getDate();
0,
).getDate()
const dates = [];
const dates = []
for (let i = 0; i < daysInMonth; i++) {
const date = new Date(monthStart);
date.setDate(monthStart.getDate() + i);
dates.push(date);
const date = new Date(monthStart)
date.setDate(monthStart.getDate() + i)
dates.push(date)
}
return dates;
return dates
} else {
// Yıllık görünüm: yıl başından itibaren 12 ay
const yearStart = new Date(currentDate.getFullYear(), 0, 1);
const months = [];
const yearStart = new Date(currentDate.getFullYear(), 0, 1)
const months = []
for (let i = 0; i < 12; i++) {
const month = new Date(yearStart);
month.setMonth(i);
months.push(month);
const month = new Date(yearStart)
month.setMonth(i)
months.push(month)
}
return months
}
return months;
}
};
const dateRange = generateDateRange();
const dateRange = generateDateRange()
// Seçili çalışana göre görevleri filtrele
const filteredTasks = useMemo(() => {
// Mock verilerden GanttTask formatına dönüştür
const createGanttTasks = (): PsGanttTask[] => {
const ganttTasks: PsGanttTask[] = [];
const ganttTasks: PsGanttTask[] = []
mockProjects.forEach((project) => {
const projectPhases = mockProjectPhases.filter(
(phase) => phase.projectId === project.id
);
const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
const projectChildren: PsGanttTask[] = [];
const projectChildren: PsGanttTask[] = []
projectPhases.forEach((phase) => {
const phaseTasks = mockProjectTasks.filter(
(task) => task.phaseId === phase.id
);
const phaseTasks = mockProjectTasks.filter((task) => task.phaseId === phase.id)
const phaseChildren: PsGanttTask[] = phaseTasks.map((task) => ({
id: `task-${task.id}`,
name: task.name,
type: "task" as const,
type: 'task' as const,
startDate: task.startDate,
endDate: task.endDate,
progress: task.progress,
@ -146,13 +133,13 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
children: [],
estimatedHours: task.estimatedHours,
hoursWorked: task.actualHours,
}));
}))
if (phaseChildren.length > 0) {
projectChildren.push({
id: `phase-${phase.id}`,
name: phase.name,
type: "phase" as const,
type: 'phase' as const,
startDate: phase.startDate,
endDate: phase.endDate,
progress: phase.progress,
@ -160,15 +147,15 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
priority: PriorityEnum.Normal,
level: 1,
children: phaseChildren,
});
})
}
});
})
if (projectChildren.length > 0) {
ganttTasks.push({
id: `project-${project.id}`,
name: project.name,
type: "project" as const,
type: 'project' as const,
startDate: project.startDate,
endDate: project.endDate,
progress: project.progress,
@ -176,265 +163,254 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
priority: project.priority,
level: 0,
children: projectChildren,
});
})
}
});
})
return ganttTasks;
};
return ganttTasks
}
const filterTasksByEmployee = (tasks: PsGanttTask[]): PsGanttTask[] => {
return tasks
.map((task) => {
if (task.type === "task" && task.assignee?.id !== selectedEmployee) {
return null;
if (task.type === 'task' && task.assignee?.id !== selectedEmployee) {
return null
}
if (task.children) {
const filteredChildren = filterTasksByEmployee(
task.children
).filter(Boolean) as PsGanttTask[];
const filteredChildren = filterTasksByEmployee(task.children).filter(
Boolean,
) as PsGanttTask[]
if (filteredChildren.length > 0) {
return { ...task, children: filteredChildren };
} else if (task.type === "task") {
return task;
return { ...task, children: filteredChildren }
} else if (task.type === 'task') {
return task
}
return null;
return null
}
return task;
return task
})
.filter(Boolean) as PsGanttTask[];
};
.filter(Boolean) as PsGanttTask[]
}
const allGanttTasks = createGanttTasks();
return filterTasksByEmployee(allGanttTasks);
}, [selectedEmployee]);
const allGanttTasks = createGanttTasks()
return filterTasksByEmployee(allGanttTasks)
}, [selectedEmployee])
const toggleExpand = (id: string) => {
const newExpanded = new Set(expandedItems);
const newExpanded = new Set(expandedItems)
if (newExpanded.has(id)) {
newExpanded.delete(id);
newExpanded.delete(id)
} else {
newExpanded.add(id);
newExpanded.add(id)
}
setExpandedItems(newExpanded)
}
setExpandedItems(newExpanded);
};
// Navigasyon fonksiyonları
const navigatePrevious = () => {
const newDate = new Date(currentDate);
if (viewMode === "day") {
newDate.setDate(newDate.getDate() - 1);
} else if (viewMode === "week") {
newDate.setDate(newDate.getDate() - 7);
} else if (viewMode === "month") {
newDate.setMonth(newDate.getMonth() - 1);
const newDate = new Date(currentDate)
if (viewMode === 'day') {
newDate.setDate(newDate.getDate() - 1)
} else if (viewMode === 'week') {
newDate.setDate(newDate.getDate() - 7)
} else if (viewMode === 'month') {
newDate.setMonth(newDate.getMonth() - 1)
} else {
newDate.setFullYear(newDate.getFullYear() - 1);
newDate.setFullYear(newDate.getFullYear() - 1)
}
setCurrentDate(newDate)
}
setCurrentDate(newDate);
};
const navigateNext = () => {
const newDate = new Date(currentDate);
if (viewMode === "day") {
newDate.setDate(newDate.getDate() + 1);
} else if (viewMode === "week") {
newDate.setDate(newDate.getDate() + 7);
} else if (viewMode === "month") {
newDate.setMonth(newDate.getMonth() + 1);
const newDate = new Date(currentDate)
if (viewMode === 'day') {
newDate.setDate(newDate.getDate() + 1)
} else if (viewMode === 'week') {
newDate.setDate(newDate.getDate() + 7)
} else if (viewMode === 'month') {
newDate.setMonth(newDate.getMonth() + 1)
} else {
newDate.setFullYear(newDate.getFullYear() + 1);
newDate.setFullYear(newDate.getFullYear() + 1)
}
setCurrentDate(newDate)
}
setCurrentDate(newDate);
};
const navigateToday = () => {
setCurrentDate(new Date());
};
setCurrentDate(new Date())
}
// Mevcut tarih bilgisi gösterimi
const getCurrentDateInfo = () => {
if (viewMode === "day") {
return currentDate.toLocaleDateString("tr-TR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
} else if (viewMode === "week") {
const weekStart = new Date(currentDate);
const dayOfWeek = weekStart.getDay();
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
weekStart.setDate(weekStart.getDate() - daysToSubtract);
if (viewMode === 'day') {
return currentDate.toLocaleDateString('tr-TR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
})
} else if (viewMode === 'week') {
const weekStart = new Date(currentDate)
const dayOfWeek = weekStart.getDay()
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1
weekStart.setDate(weekStart.getDate() - daysToSubtract)
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
const weekEnd = new Date(weekStart)
weekEnd.setDate(weekStart.getDate() + 6)
return `${weekStart.toLocaleDateString("tr-TR", {
day: "numeric",
month: "short",
})} - ${weekEnd.toLocaleDateString("tr-TR", {
day: "numeric",
month: "short",
year: "numeric",
})}`;
} else if (viewMode === "month") {
return currentDate.toLocaleDateString("tr-TR", {
year: "numeric",
month: "long",
});
return `${weekStart.toLocaleDateString('tr-TR', {
day: 'numeric',
month: 'short',
})} - ${weekEnd.toLocaleDateString('tr-TR', {
day: 'numeric',
month: 'short',
year: 'numeric',
})}`
} else if (viewMode === 'month') {
return currentDate.toLocaleDateString('tr-TR', {
year: 'numeric',
month: 'long',
})
} else {
return currentDate.toLocaleDateString("tr-TR", {
year: "numeric",
});
return currentDate.toLocaleDateString('tr-TR', {
year: 'numeric',
})
}
}
};
const calculateTaskPosition = (startDate: Date, endDate: Date) => {
const chartStart = dateRange[0];
const chartEnd = dateRange[dateRange.length - 1];
const taskStart = new Date(startDate);
const taskEnd = new Date(endDate);
const chartStart = dateRange[0]
const chartEnd = dateRange[dateRange.length - 1]
const taskStart = new Date(startDate)
const taskEnd = new Date(endDate)
if (viewMode === "day") {
if (viewMode === 'day') {
// Günlük görünüm: saatlik hesaplama
const dayStart = new Date(chartStart);
dayStart.setHours(0, 0, 0, 0);
const dayEnd = new Date(chartStart);
dayEnd.setHours(23, 59, 59, 999);
const dayStart = new Date(chartStart)
dayStart.setHours(0, 0, 0, 0)
const dayEnd = new Date(chartStart)
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
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
let startHour = 0;
let startHour = 0
if (taskStart > dayStart) {
startHour = taskStart.getHours() + taskStart.getMinutes() / 60;
startHour = taskStart.getHours() + taskStart.getMinutes() / 60
}
// Görev bitişini hesapla
let endHour = 24;
let endHour = 24
if (taskEnd < dayEnd) {
endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60;
endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60
}
const left = (startHour / 24) * 100;
const width = Math.max(1, ((endHour - startHour) / 24) * 100);
const left = (startHour / 24) * 100
const width = Math.max(1, ((endHour - startHour) / 24) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true };
} else if (viewMode === "week") {
return { left: `${left}%`, width: `${width}%`, isVisible: true }
} else if (viewMode === 'week') {
// Haftalık görünüm: günlük hesaplama
const weekStart = new Date(chartStart);
const weekEnd = new Date(chartEnd);
weekEnd.setHours(23, 59, 59, 999);
const weekStart = new Date(chartStart)
const weekEnd = new Date(chartEnd)
weekEnd.setHours(23, 59, 59, 999)
// Görev hafta dışındaysa gizle
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
let startDay = 0;
let startDay = 0
if (taskStart > weekStart) {
startDay = Math.floor(
(taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
);
startDay = Math.floor((taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
}
// Bitiş gününü hesapla
let endDay = 7;
let endDay = 7
if (taskEnd < weekEnd) {
endDay = Math.ceil(
(taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
);
endDay = Math.ceil((taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
}
const left = (startDay / 7) * 100;
const width = Math.max(1, ((endDay - startDay) / 7) * 100);
const left = (startDay / 7) * 100
const width = Math.max(1, ((endDay - startDay) / 7) * 100)
return { left: `${left}%`, width: `${width}%`, isVisible: true };
} else if (viewMode === "month") {
return { left: `${left}%`, width: `${width}%`, isVisible: true }
} else if (viewMode === 'month') {
// Aylık görünüm: günlük hesaplama
const monthStart = new Date(chartStart);
const monthEnd = new Date(chartEnd);
monthEnd.setHours(23, 59, 59, 999);
const monthStart = new Date(chartStart)
const monthEnd = new Date(chartEnd)
monthEnd.setHours(23, 59, 59, 999)
// Görev ay dışındaysa gizle
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
let startDay = 0;
let startDay = 0
if (taskStart > monthStart) {
startDay = Math.floor(
(taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
);
startDay = Math.floor((taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
}
// Bitiş gününü hesapla
let endDay = daysInMonth;
let endDay = daysInMonth
if (taskEnd < monthEnd) {
endDay = Math.ceil(
(taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
);
endDay = Math.ceil((taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
}
const left = (startDay / daysInMonth) * 100;
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100);
const left = (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 {
// Yıllık görünüm: aylık hesaplama
const yearStart = new Date(chartStart);
const yearEnd = new Date(chartEnd);
yearEnd.setMonth(11, 31);
yearEnd.setHours(23, 59, 59, 999);
const yearStart = new Date(chartStart)
const yearEnd = new Date(chartEnd)
yearEnd.setMonth(11, 31)
yearEnd.setHours(23, 59, 59, 999)
// Görev yıl dışındaysa gizle
if (taskEnd < yearStart || taskStart > yearEnd) {
return { left: "100%", width: "0%", isVisible: false };
return { left: '100%', width: '0%', isVisible: false }
}
// Başlangıç ayını hesapla
let startMonth = 0;
let startMonth = 0
if (taskStart > yearStart) {
startMonth = taskStart.getMonth();
startMonth = taskStart.getMonth()
}
// Bitiş ayını hesapla
let endMonth = 12;
let endMonth = 12
if (taskEnd < yearEnd) {
endMonth = taskEnd.getMonth() + 1;
endMonth = taskEnd.getMonth() + 1
}
const left = (startMonth / 12) * 100;
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100);
const left = (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 hasChildren = task.children && task.children.length > 0;
const isExpanded = expandedItems.has(task.id);
const indent = task.level * 20;
const hasChildren = task.children && task.children.length > 0
const isExpanded = expandedItems.has(task.id)
const indent = task.level * 20
return (
<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]">
{/* Task Info */}
<div className="w-1/3 sm:w-1/4 p-0.5 border-r border-gray-200">
<div
className="flex items-center"
style={{ paddingLeft: `${Math.min(indent, 40)}px` }}
>
<div className="flex items-center" style={{ paddingLeft: `${Math.min(indent, 40)}px` }}>
{hasChildren && (
<button
onClick={() => toggleExpand(task.id)}
@ -453,11 +429,11 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
<div className="flex items-center gap-1">
<span
className={`font-medium text-xs sm:text-sm truncate ${
task.type === "project"
? "text-blue-900 font-semibold"
: task.type === "phase"
? "text-blue-700"
: "text-gray-900"
task.type === 'project'
? 'text-blue-900 font-semibold'
: task.type === 'phase'
? 'text-blue-700'
: 'text-gray-900'
}`}
title={task.name}
>
@ -465,14 +441,14 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
</span>
<span
className={`px-1 py-0.5 text-xs border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
task.priority
task.priority,
)}`}
>
{task.priority}
</span>
</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">
<FaUser className="h-2 w-2 sm:h-2.5 sm:w-2.5 flex-shrink-0" />
<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">
{task.startDate.toLocaleDateString("tr-TR")} -{" "}
{task.endDate.toLocaleDateString("tr-TR")}
{task.startDate.toLocaleDateString('tr-TR')} -{' '}
{task.endDate.toLocaleDateString('tr-TR')}
</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="relative h-5 sm:h-7 bg-gray-200 rounded">
{(() => {
const position = calculateTaskPosition(
task.startDate,
task.endDate
);
const position = calculateTaskPosition(task.startDate, task.endDate)
if (task.type === "task" && position.isVisible) {
if (task.type === 'task' && position.isVisible) {
return (
<div
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`}
style={{ left: position.left, width: position.width }}
title={`${task.name} - ${task.progress}% tamamlandı`}
@ -528,24 +501,16 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
{/* 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="font-semibold">{task.name}</div>
<div className="mt-1">
İlerleme: {task.progress}% tamamlandı
</div>
<div>
Planlanan Başlangıç:{" "}
{task.startDate.toLocaleDateString("tr-TR")}
</div>
<div>
Planlanan Bitiş:{" "}
{task.endDate.toLocaleDateString("tr-TR")}
</div>
<div className="mt-1">İlerleme: {task.progress}% tamamlandı</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>
</div>
);
)
}
if (task.type !== "task" && position.isVisible) {
if (task.type !== 'task' && position.isVisible) {
return (
<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"
@ -555,48 +520,37 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
{/* 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="font-semibold">{task.name}</div>
<div>
Planlanan Başlangıç:{" "}
{task.startDate.toLocaleDateString("tr-TR")}
</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>
</div>
);
)
}
return null;
return null
})()}
</div>
</div>
</div>
{/* Render children if expanded */}
{hasChildren &&
isExpanded &&
task.children?.map((child) => renderTask(child))}
{hasChildren && isExpanded && task.children?.map((child) => renderTask(child))}
</React.Fragment>
);
};
)
}
return (
<>
<div className="space-y-3 py-2">
<Container>
<div className="space-y-2">
<div className="flex items-center gap-3">
<div>
<h2 className="text-xl font-bold text-gray-900">
İş Yükü ve Proje Takibi
</h2>
<p className="text-sm sm:text-base text-gray-600 mt-1">
<h2 className="text-xl font-bold text-gray-900">İş Yükü ve Proje Takibi</h2>
<p className="text-gray-600 mt-1">
Tüm görevlerinizi merkezi bir noktadan yönetin.
</p>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm border h-full flex flex-col">
{/* Header */}
@ -645,11 +599,7 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
<select
value={viewMode}
onChange={(e) =>
setViewMode(
e.target.value as "day" | "week" | "month" | "year"
)
}
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>
@ -669,28 +619,25 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
<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 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 === '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" })}
: 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" && (
{viewMode !== 'day' && viewMode !== 'year' && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.toLocaleDateString("tr-TR", { weekday: "short" })}
{date.toLocaleDateString('tr-TR', { weekday: 'short' })}
</div>
)}
{viewMode === "year" && (
{viewMode === 'year' && (
<div className="text-xs text-gray-500 hidden sm:block">
{date.getFullYear()}
</div>
@ -706,8 +653,9 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
{filteredTasks.map((task) => renderTask(task))}
</div>
</div>
</>
);
};
</div>
</Container>
)
}
export default ProjectGantt;
export default ProjectGantt

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import {
FaFolder,
FaPlus,
@ -18,15 +18,15 @@ import {
FaList,
FaFlag,
FaTasks,
} from "react-icons/fa";
import classNames from "classnames";
import { ProjectStatusEnum, PsProject } from "../../../types/ps";
import dayjs from "dayjs";
import { mockProjects } from "../../../mocks/mockProjects";
import PhaseViewModal from "./PhaseViewModal";
import TaskViewModal from "./TaskViewModal";
import Widget from "../../../components/common/Widget";
import { PriorityEnum } from "../../../types/common";
} from 'react-icons/fa'
import classNames from 'classnames'
import { ProjectStatusEnum, PsProject } from '../../../types/ps'
import dayjs from 'dayjs'
import { mockProjects } from '../../../mocks/mockProjects'
import PhaseViewModal from './PhaseViewModal'
import TaskViewModal from './TaskViewModal'
import Widget from '../../../components/common/Widget'
import { PriorityEnum } from '../../../types/common'
import {
getProjectStatusColor,
getProjectStatusIcon,
@ -34,59 +34,56 @@ import {
getPriorityColor,
getPriorityText,
getProgressColor,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const ProjectList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
const [filterStatus, setFilterStatus] = useState("all");
const [filterPriority, setFilterPriority] = useState("all");
const [showFilters, setShowFilters] = useState(false);
const [viewMode, setViewMode] = useState<"card" | "list">("list");
const [searchTerm, setSearchTerm] = useState('')
const [filterStatus, setFilterStatus] = useState('all')
const [filterPriority, setFilterPriority] = useState('all')
const [showFilters, setShowFilters] = useState(false)
const [viewMode, setViewMode] = useState<'card' | 'list'>('list')
// Modal states
const [phasesModalOpen, setPhasesModalOpen] = useState(false);
const [tasksModalOpen, setTasksModalOpen] = useState(false);
const [selectedProject, setSelectedProject] = useState<PsProject | null>(
null
);
const [phasesModalOpen, setPhasesModalOpen] = useState(false)
const [tasksModalOpen, setTasksModalOpen] = useState(false)
const [selectedProject, setSelectedProject] = useState<PsProject | null>(null)
// Modal functions
const openPhasesModal = (project: PsProject) => {
setSelectedProject(project);
setPhasesModalOpen(true);
};
setSelectedProject(project)
setPhasesModalOpen(true)
}
const openTasksModal = (project: PsProject) => {
setSelectedProject(project);
setTasksModalOpen(true);
};
setSelectedProject(project)
setTasksModalOpen(true)
}
const closeModals = () => {
setPhasesModalOpen(false);
setTasksModalOpen(false);
setSelectedProject(null);
};
setPhasesModalOpen(false)
setTasksModalOpen(false)
setSelectedProject(null)
}
const {
data: projects,
isLoading,
error,
} = useQuery({
queryKey: ["projects", searchTerm, filterStatus, filterPriority],
queryKey: ['projects', searchTerm, filterStatus, filterPriority],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500))
return mockProjects.filter((project) => {
const matchesSearch =
project.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.name.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus =
filterStatus === "all" || project.status === filterStatus;
const matchesPriority =
filterPriority === "all" || project.priority === filterPriority;
return matchesSearch && matchesStatus && matchesPriority;
});
project.name.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = filterStatus === 'all' || project.status === filterStatus
const matchesPriority = filterPriority === 'all' || project.priority === filterPriority
return matchesSearch && matchesStatus && matchesPriority
})
},
});
})
if (isLoading) {
return (
@ -94,7 +91,7 @@ const ProjectList: React.FC = () => {
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">Projeler yükleniyor...</span>
</div>
);
)
}
if (error) {
@ -102,16 +99,15 @@ const ProjectList: React.FC = () => {
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center">
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
<span className="text-red-800">
Projeler yüklenirken hata oluştu.
</span>
<span className="text-red-800">Projeler yüklenirken hata oluştu.</span>
</div>
</div>
);
)
}
return (
<div className="space-y-4 pt-2">
<Container>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Proje Listesi</h2>
@ -121,21 +117,21 @@ const ProjectList: React.FC = () => {
{/* View Toggle */}
<div className="flex bg-gray-100 rounded-lg">
<button
onClick={() => setViewMode("card")}
onClick={() => setViewMode('card')}
className={`px-2.5 py-1.5 rounded-md flex items-center space-x-2 transition-colors ${
viewMode === "card"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
viewMode === 'card'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaTh className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode("list")}
onClick={() => setViewMode('list')}
className={`px-2.5 py-1.5 rounded-md flex items-center space-x-2 transition-colors ${
viewMode === "list"
? "bg-white text-blue-600 shadow-sm"
: "text-gray-600 hover:text-gray-900"
viewMode === 'list'
? 'bg-white text-blue-600 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
<FaList className="w-4 h-4" />
@ -143,7 +139,7 @@ const ProjectList: React.FC = () => {
</div>
<button
onClick={() => alert("Dışa aktarma özelliği yakında eklenecek")}
onClick={() => alert('Dışa aktarma özelliği yakında eklenecek')}
className="flex items-center px-3 py-1.5 text-sm border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
<FaDownload size={16} className="mr-2" />
@ -180,10 +176,10 @@ const ProjectList: React.FC = () => {
<button
onClick={() => setShowFilters(!showFilters)}
className={classNames(
"flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors",
'flex items-center px-3 py-1.5 text-sm border rounded-lg transition-colors',
showFilters
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
)}
>
<FaFilter size={16} className="mr-2" />
@ -197,9 +193,7 @@ const ProjectList: React.FC = () => {
<div className="bg-white border border-gray-200 rounded-lg p-3">
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Durum
</label>
<label className="block text-sm font-medium text-gray-700 mb-2">Durum</label>
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
@ -210,16 +204,12 @@ const ProjectList: React.FC = () => {
<option value={ProjectStatusEnum.Active}>Aktif</option>
<option value={ProjectStatusEnum.OnHold}>Beklemede</option>
<option value={ProjectStatusEnum.Completed}>Tamamlandı</option>
<option value={ProjectStatusEnum.Cancelled}>
İptal Edildi
</option>
<option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Öncelik
</label>
<label className="block text-sm font-medium text-gray-700 mb-2">Öncelik</label>
<select
value={filterPriority}
onChange={(e) => setFilterPriority(e.target.value)}
@ -236,9 +226,9 @@ const ProjectList: React.FC = () => {
<div className="flex items-end">
<button
onClick={() => {
setFilterStatus("all");
setFilterPriority("all");
setSearchTerm("");
setFilterStatus('all')
setFilterPriority('all')
setSearchTerm('')
}}
className="w-full px-3 py-1.5 border border-gray-300 bg-white text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
>
@ -251,29 +241,18 @@ const ProjectList: React.FC = () => {
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Widget
title="Toplam Proje"
value={projects?.length || 0}
color="blue"
icon="FaFolder"
/>
<Widget title="Toplam Proje" value={projects?.length || 0} color="blue" icon="FaFolder" />
<Widget
title="Aktif Proje"
value={
projects?.filter((p) => p.status === ProjectStatusEnum.Active)
.length || 0
}
value={projects?.filter((p) => p.status === ProjectStatusEnum.Active).length || 0}
color="green"
icon="FaArrowUp"
/>
<Widget
title="Toplam Bütçe"
value={`${
projects?.reduce((acc, p) => acc + p.budget, 0).toLocaleString() ||
0
}`}
value={`${projects?.reduce((acc, p) => acc + p.budget, 0).toLocaleString() || 0}`}
color="purple"
icon="FaDollarSign"
/>
@ -283,10 +262,9 @@ const ProjectList: React.FC = () => {
value={
projects?.length
? `${Math.round(
projects.reduce((acc, p) => acc + p.progress, 0) /
projects.length
projects.reduce((acc, p) => acc + p.progress, 0) / projects.length,
)}%`
: "0%"
: '0%'
}
color="yellow"
icon="FaBullseye"
@ -294,7 +272,7 @@ const ProjectList: React.FC = () => {
</div>
{/* Projects Display */}
{viewMode === "list" ? (
{viewMode === 'list' ? (
/* List View */
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
<div className="px-3 py-2 border-b border-gray-200">
@ -331,10 +309,7 @@ const ProjectList: React.FC = () => {
<tbody className="bg-white divide-y divide-gray-200">
{projects?.map((project) => (
<tr
key={project.id}
className="hover:bg-gray-50 transition-colors text-sm"
>
<tr key={project.id} className="hover:bg-gray-50 transition-colors text-sm">
<td className="px-3 py-2">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
@ -343,9 +318,7 @@ const ProjectList: React.FC = () => {
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{project.code}
</div>
<div className="text-sm font-medium text-gray-900">{project.code}</div>
<div className="text-sm text-gray-500 max-w-xs truncate">
{project.name}
</div>
@ -368,19 +341,17 @@ const ProjectList: React.FC = () => {
<div className="space-y-2">
<span
className={classNames(
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
getProjectStatusColor(project.status)
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
getProjectStatusColor(project.status),
)}
>
{getProjectStatusIcon(project.status)}
<span className="ml-1">
{getProjectStatusText(project.status)}
</span>
<span className="ml-1">{getProjectStatusText(project.status)}</span>
</span>
<div
className={classNames(
"text-sm font-medium",
getPriorityColor(project.priority)
'text-sm font-medium',
getPriorityColor(project.priority),
)}
>
{getPriorityText(project.priority)}
@ -392,10 +363,10 @@ const ProjectList: React.FC = () => {
<div className="space-y-1">
<div className="flex items-center text-sm text-gray-900">
<FaCalendar size={14} className="mr-1" />
{dayjs(project.startDate).format("DD.MM.YYYY")}
{dayjs(project.startDate).format('DD.MM.YYYY')}
</div>
<div className="text-sm text-gray-500">
Bitiş: {dayjs(project.endDate).format("DD.MM.YYYY")}
Bitiş: {dayjs(project.endDate).format('DD.MM.YYYY')}
</div>
</div>
</td>
@ -409,11 +380,7 @@ const ProjectList: React.FC = () => {
Harcanan: {project.actualCost.toLocaleString()}
</div>
<div className="text-xs text-blue-600">
%
{Math.round(
(project.actualCost / project.budget) * 100
)}{" "}
kullanıldı
%{Math.round((project.actualCost / project.budget) * 100)} kullanıldı
</div>
</div>
</td>
@ -428,8 +395,8 @@ const ProjectList: React.FC = () => {
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={classNames(
"h-2 rounded-full",
getProgressColor(project.progress)
'h-2 rounded-full',
getProgressColor(project.progress),
)}
style={{ width: `${project.progress}%` }}
/>
@ -490,27 +457,23 @@ const ProjectList: React.FC = () => {
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<FaFolder className="w-5 h-5 text-blue-600" />
<h3 className="text-base font-semibold text-gray-900">
{project.code}
</h3>
<h3 className="text-base font-semibold text-gray-900">{project.code}</h3>
</div>
<p className="text-sm text-gray-600 mb-2">{project.name}</p>
<div className="flex items-center space-x-2 mb-3">
<span
className={classNames(
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium",
getProjectStatusColor(project.status)
'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium',
getProjectStatusColor(project.status),
)}
>
{getProjectStatusIcon(project.status)}
<span className="ml-1">
{getProjectStatusText(project.status)}
</span>
<span className="ml-1">{getProjectStatusText(project.status)}</span>
</span>
<span
className={classNames(
"px-2 py-0.5 rounded-full text-xs font-medium",
getPriorityColor(project.priority)
'px-2 py-0.5 rounded-full text-xs font-medium',
getPriorityColor(project.priority),
)}
>
{getPriorityText(project.priority)}
@ -551,13 +514,9 @@ const ProjectList: React.FC = () => {
<div className="bg-gray-50 rounded-lg p-2 mb-3">
<div className="flex items-center space-x-2 mb-1">
<FaUser className="w-4 h-4 text-gray-600" />
<span className="font-medium text-gray-900">
Proje Yöneticisi
</span>
<span className="font-medium text-gray-900">Proje Yöneticisi</span>
</div>
<p className="text-sm text-gray-700">
{project.projectManager?.fullName}
</p>
<p className="text-sm text-gray-700">{project.projectManager?.fullName}</p>
</div>
{/* Progress */}
@ -571,10 +530,7 @@ const ProjectList: React.FC = () => {
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={classNames(
"h-2 rounded-full",
getProgressColor(project.progress)
)}
className={classNames('h-2 rounded-full', getProgressColor(project.progress))}
style={{ width: `${project.progress}%` }}
/>
</div>
@ -589,13 +545,13 @@ const ProjectList: React.FC = () => {
Başlangıç
</span>
<span className="font-medium">
{dayjs(project.startDate).format("DD.MM.YYYY")}
{dayjs(project.startDate).format('DD.MM.YYYY')}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500">Bitiş</span>
<span className="font-medium">
{dayjs(project.endDate).format("DD.MM.YYYY")}
{dayjs(project.endDate).format('DD.MM.YYYY')}
</span>
</div>
</div>
@ -606,15 +562,11 @@ const ProjectList: React.FC = () => {
<FaDollarSign className="w-4 h-4 mr-1" />
Bütçe
</span>
<span className="font-medium">
{project.budget.toLocaleString()}
</span>
<span className="font-medium">{project.budget.toLocaleString()}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500">Harcanan</span>
<span className="font-medium">
{project.actualCost.toLocaleString()}
</span>
<span className="font-medium">{project.actualCost.toLocaleString()}</span>
</div>
</div>
</div>
@ -622,9 +574,7 @@ const ProjectList: React.FC = () => {
{/* Budget Usage */}
<div className="bg-blue-50 rounded-lg p-2">
<div className="flex items-center justify-between text-sm">
<span className="text-blue-700 font-medium">
Bütçe Kullanımı
</span>
<span className="text-blue-700 font-medium">Bütçe Kullanımı</span>
<span className="text-blue-600 font-bold">
%{Math.round((project.actualCost / project.budget) * 100)}
</span>
@ -639,12 +589,8 @@ const ProjectList: React.FC = () => {
{(!projects || projects.length === 0) && (
<div className="text-center py-10">
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Proje bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Yeni proje oluşturarak başlayın.
</p>
<h3 className="mt-2 text-sm font-medium text-gray-900">Proje bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">Yeni proje oluşturarak başlayın.</p>
<div className="mt-6">
<Link
to="/admin/projects/new"
@ -656,6 +602,7 @@ const ProjectList: React.FC = () => {
</div>
</div>
)}
</div>
{/* Modals */}
{selectedProject && (
@ -665,15 +612,11 @@ const ProjectList: React.FC = () => {
isOpen={phasesModalOpen}
onClose={closeModals}
/>
<TaskViewModal
project={selectedProject}
isOpen={tasksModalOpen}
onClose={closeModals}
/>
<TaskViewModal project={selectedProject} isOpen={tasksModalOpen} onClose={closeModals} />
</>
)}
</div>
);
};
</Container>
)
}
export default ProjectList;
export default ProjectList

View file

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

View file

@ -26,6 +26,7 @@ import {
getPriorityColor,
getTaskTypeColor,
} from "../../../utils/erp";
import { Container } from "@/components/shared";
const ProjectTasks: React.FC = () => {
const [searchTerm, setSearchTerm] = useState("");
@ -166,12 +167,12 @@ const ProjectTasks: React.FC = () => {
};
return (
<div>
<div className="mb-3 mt-2">
<Container>
<div className="space-y-2">
<div className="flex items-center justify-between mb-4">
<div>
<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
</p>
</div>
@ -1156,7 +1157,8 @@ const ProjectTasks: React.FC = () => {
</div>
</div>
)}
</div>
</Container>
);
};

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { useParams, Link, useNavigate } from "react-router-dom";
import React, { useState } from 'react'
import { useParams, Link, useNavigate } from 'react-router-dom'
import {
FaArrowLeft,
FaEdit,
@ -16,14 +16,14 @@ import {
FaFolder,
FaPhone,
FaEnvelope,
} from "react-icons/fa";
import { TaskStatusEnum } from "../../../types/ps";
import { mockProjects } from "../../../mocks/mockProjects";
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
import dayjs from "dayjs";
import classNames from "classnames";
import { PriorityEnum } from "../../../types/common";
} from 'react-icons/fa'
import { TaskStatusEnum } from '../../../types/ps'
import { mockProjects } from '../../../mocks/mockProjects'
import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
import dayjs from 'dayjs'
import classNames from 'classnames'
import { PriorityEnum } from '../../../types/common'
import {
getProjectStatusColor,
getProjectStatusIcon,
@ -33,27 +33,24 @@ import {
getPriorityText,
getProjectTypeColor,
getProjectTypeText,
} from "../../../utils/erp";
} from '../../../utils/erp'
import { Container } from '@/components/shared'
const ProjectView: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState("overview");
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
const [activeTab, setActiveTab] = useState('overview')
// Find the project by ID
const project = mockProjects.find((p) => p.id === id);
const project = mockProjects.find((p) => p.id === id)
if (!project) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Proje bulunamadı
</h3>
<p className="mt-1 text-sm text-gray-500">
Belirtilen ID ile proje mevcut değil.
</p>
<h3 className="mt-2 text-sm font-medium text-gray-900">Proje bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">Belirtilen ID ile proje mevcut değil.</p>
<div className="mt-6">
<Link
to="/admin/projects"
@ -65,35 +62,33 @@ const ProjectView: React.FC = () => {
</div>
</div>
</div>
);
)
}
const tabs = [
{ id: "overview", name: "Genel Bakış", icon: FaChartLine },
{ id: "phases", name: "Fazlar", icon: FaFlag },
{ id: "tasks", name: "Görevler", icon: FaTasks },
{ id: "documents", name: "Belgeler", icon: FaFileAlt },
{ id: "risks", name: "Riskler", icon: FaExclamationCircle },
{ id: "financial", name: "Mali Özet", icon: FaDollarSign },
];
{ id: 'overview', name: 'Genel Bakış', icon: FaChartLine },
{ id: 'phases', name: 'Fazlar', icon: FaFlag },
{ id: 'tasks', name: 'Görevler', icon: FaTasks },
{ id: 'documents', name: 'Belgeler', icon: FaFileAlt },
{ id: 'risks', name: 'Riskler', icon: FaExclamationCircle },
{ id: 'financial', name: 'Mali Özet', icon: FaDollarSign },
]
const budgetUsagePercentage = (project.actualCost / project.budget) * 100;
const timeElapsed = dayjs().diff(dayjs(project.startDate), "day");
const totalDuration = dayjs(project.endDate).diff(
dayjs(project.startDate),
"day"
);
const timeProgress = Math.min((timeElapsed / totalDuration) * 100, 100);
const budgetUsagePercentage = (project.actualCost / project.budget) * 100
const timeElapsed = dayjs().diff(dayjs(project.startDate), 'day')
const totalDuration = dayjs(project.endDate).diff(dayjs(project.startDate), 'day')
const timeProgress = Math.min((timeElapsed / totalDuration) * 100, 100)
return (
<div className="min-h-screen bg-gray-50">
<Container>
<div className="space-y-2">
{/* Header */}
<div className="bg-white shadow-sm border-b border-gray-200">
<div className="mx-auto px-2">
<div className="p-2">
<div className="flex items-center justify-between h-12">
<div className="flex items-center space-x-2">
<button
onClick={() => navigate("/admin/projects")}
onClick={() => navigate('/admin/projects')}
className="flex items-center px-2 py-1.5 text-sm text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<FaArrowLeft className="w-4 h-4 mr-2" />
@ -104,9 +99,7 @@ const ProjectView: React.FC = () => {
<FaFolder className="w-6 h-6 text-blue-600" />
</div>
<div>
<h1 className="text-lg font-bold text-gray-900">
{project.code}
</h1>
<h1 className="text-lg font-bold text-gray-900">{project.code}</h1>
<p className="text-sm text-gray-600">{project.name}</p>
</div>
</div>
@ -114,14 +107,12 @@ const ProjectView: React.FC = () => {
<div className="flex items-center space-x-2">
<span
className={classNames(
"inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium border",
getProjectStatusColor(project.status)
'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium border',
getProjectStatusColor(project.status),
)}
>
{getProjectStatusIcon(project.status)}
<span className="ml-2">
{getProjectStatusText(project.status)}
</span>
<span className="ml-2">{getProjectStatusText(project.status)}</span>
</span>
<Link
to={`/admin/projects/edit/${project.id}`}
@ -144,19 +135,12 @@ const ProjectView: React.FC = () => {
<div className="w-7 h-7 bg-blue-100 rounded-lg flex items-center justify-center">
<FaBullseye className="w-5 h-5 text-blue-600" />
</div>
<span className="text-lg font-bold text-blue-600">
{project.progress}%
</span>
<span className="text-lg font-bold text-blue-600">{project.progress}%</span>
</div>
<h3 className="text-sm font-medium text-gray-600 mb-1.5">
İlerleme
</h3>
<h3 className="text-sm font-medium text-gray-600 mb-1.5">İlerleme</h3>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div
className={classNames(
"h-1.5 rounded-full",
getProgressColor(project.progress)
)}
className={classNames('h-1.5 rounded-full', getProgressColor(project.progress))}
style={{ width: `${project.progress}%` }}
/>
</div>
@ -172,12 +156,9 @@ const ProjectView: React.FC = () => {
{budgetUsagePercentage.toFixed(0)}%
</span>
</div>
<h3 className="text-sm font-medium text-gray-600 mb-1">
Bütçe Kullanımı
</h3>
<h3 className="text-sm font-medium text-gray-600 mb-1">Bütçe Kullanımı</h3>
<div className="text-xs text-gray-500">
{project.actualCost.toLocaleString()} /
{project.budget.toLocaleString()}
{project.actualCost.toLocaleString()} / {project.budget.toLocaleString()}
</div>
</div>
@ -191,9 +172,7 @@ const ProjectView: React.FC = () => {
{timeProgress.toFixed(0)}%
</span>
</div>
<h3 className="text-sm font-medium text-gray-600 mb-1">
Zaman İlerlemesi
</h3>
<h3 className="text-sm font-medium text-gray-600 mb-1">Zaman İlerlemesi</h3>
<div className="text-xs text-gray-500">
{timeElapsed} / {totalDuration} gün
</div>
@ -206,10 +185,7 @@ const ProjectView: React.FC = () => {
<FaFlag className="w-5 h-5 text-red-600" />
</div>
<span
className={classNames(
"text-lg font-bold",
getPriorityColor(project.priority)
)}
className={classNames('text-lg font-bold', getPriorityColor(project.priority))}
>
{getPriorityText(project.priority)}
</span>
@ -217,8 +193,8 @@ const ProjectView: React.FC = () => {
<h3 className="text-sm font-medium text-gray-600 mb-1">Öncelik</h3>
<span
className={classNames(
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border",
getProjectTypeColor(project.projectType)
'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border',
getProjectTypeColor(project.projectType),
)}
>
{getProjectTypeText(project.projectType)}
@ -231,28 +207,28 @@ const ProjectView: React.FC = () => {
<div className="border-b border-gray-200">
<nav className="flex space-x-4 px-3" aria-label="Tabs">
{tabs.map((tab) => {
const Icon = tab.icon;
const Icon = tab.icon
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={classNames(
"py-2 px-1 border-b-2 font-medium text-sm flex items-center space-x-2 transition-colors",
'py-2 px-1 border-b-2 font-medium text-sm flex items-center space-x-2 transition-colors',
activeTab === tab.id
? "border-blue-500 text-blue-600"
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
)}
>
<Icon className="w-4 h-4" />
<span>{tab.name}</span>
</button>
);
)
})}
</nav>
</div>
<div className="p-3">
{activeTab === "overview" && (
{activeTab === 'overview' && (
<div className="space-y-3">
{/* Project Info Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
@ -265,45 +241,37 @@ const ProjectView: React.FC = () => {
<div className="bg-gray-50 rounded-lg p-2.5 space-y-1.5 text-sm">
<div className="flex justify-between items-center">
<span className="text-gray-600">Proje Kodu:</span>
<span className="font-medium text-gray-900">
{project.code}
</span>
<span className="font-medium text-gray-900">{project.code}</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-600">Proje Türü:</span>
<span
className={classNames(
"px-2 py-0.5 rounded-full text-xs font-medium border",
getProjectTypeColor(project.projectType)
'px-2 py-0.5 rounded-full text-xs font-medium border',
getProjectTypeColor(project.projectType),
)}
>
{getProjectTypeText(project.projectType)}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-600">
Başlangıç Tarihi:
</span>
<span className="text-gray-600">Başlangıç Tarihi:</span>
<span className="font-medium text-gray-900">
{dayjs(project.startDate).format("DD.MM.YYYY")}
{dayjs(project.startDate).format('DD.MM.YYYY')}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-600">Bitiş Tarihi:</span>
<span className="font-medium text-gray-900">
{dayjs(project.endDate).format("DD.MM.YYYY")}
{dayjs(project.endDate).format('DD.MM.YYYY')}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-600">
Gerçek Başlangıç:
</span>
<span className="text-gray-600">Gerçek Başlangıç:</span>
<span className="font-medium text-gray-900">
{project.actualStartDate
? dayjs(project.actualStartDate).format(
"DD.MM.YYYY"
)
: "Henüz başlamadı"}
? dayjs(project.actualStartDate).format('DD.MM.YYYY')
: 'Henüz başlamadı'}
</span>
</div>
</div>
@ -312,9 +280,7 @@ const ProjectView: React.FC = () => {
{/* Description */}
{project.description && (
<div>
<h3 className="text-base font-semibold text-gray-900 mb-2">
ıklama
</h3>
<h3 className="text-base font-semibold text-gray-900 mb-2">ıklama</h3>
<div className="bg-gray-50 rounded-lg p-2.5">
<p className="text-sm text-gray-700 leading-relaxed">
{project.description}
@ -342,8 +308,7 @@ const ProjectView: React.FC = () => {
{project.projectManager.fullName}
</h4>
<p className="text-sm text-gray-600">
{project.projectManager.jobPosition?.name ||
"Proje Yöneticisi"}
{project.projectManager.jobPosition?.name || 'Proje Yöneticisi'}
</p>
<div className="flex items-center mt-1 text-sm text-gray-500">
<FaEnvelope className="w-3 h-3 mr-1" />
@ -364,9 +329,7 @@ const ProjectView: React.FC = () => {
{/* Customer */}
{project.customer && (
<div>
<h3 className="text-base font-semibold text-gray-900 mb-2">
Müşteri
</h3>
<h3 className="text-base font-semibold text-gray-900 mb-2">Müşteri</h3>
<div className="bg-gray-50 rounded-lg p-2.5">
<div className="flex items-center space-x-2">
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
@ -377,7 +340,7 @@ const ProjectView: React.FC = () => {
{project.customer.name}
</h4>
<p className="text-sm text-gray-600">
{project.customer.primaryContact?.firstName}{" "}
{project.customer.primaryContact?.firstName}{' '}
{project.customer.primaryContact?.lastName}
</p>
{project.customer.primaryContact?.email && (
@ -402,21 +365,17 @@ const ProjectView: React.FC = () => {
</div>
)}
{activeTab === "phases" && (
{activeTab === 'phases' && (
<div className="space-y-3">
{/* Phases List */}
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="divide-y divide-gray-200">
{mockProjectPhases.filter(
(phase) => phase.projectId === project.id
).length > 0 ? (
{mockProjectPhases.filter((phase) => phase.projectId === project.id).length >
0 ? (
mockProjectPhases
.filter((phase) => phase.projectId === project.id)
.map((phase) => (
<div
key={phase.id}
className="px-3 py-2 hover:bg-gray-50"
>
<div key={phase.id} className="px-3 py-2 hover:bg-gray-50">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
@ -435,40 +394,26 @@ const ProjectView: React.FC = () => {
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-1.5">
<div>
<p className="text-xs font-medium text-gray-700">
Başlangıç
</p>
<p className="text-xs font-medium text-gray-700">Başlangıç</p>
<p className="text-xs text-gray-600">
{dayjs(phase.startDate).format(
"DD.MM.YYYY"
)}
{dayjs(phase.startDate).format('DD.MM.YYYY')}
</p>
</div>
<div>
<p className="text-xs font-medium text-gray-700">
Bitiş
</p>
<p className="text-xs font-medium text-gray-700">Bitiş</p>
<p className="text-xs text-gray-600">
{dayjs(phase.endDate).format(
"DD.MM.YYYY"
)}
{dayjs(phase.endDate).format('DD.MM.YYYY')}
</p>
</div>
<div>
<p className="text-xs font-medium text-gray-700">
Bütçe
</p>
<p className="text-xs font-medium text-gray-700">Bütçe</p>
<p className="text-xs text-gray-600">
{phase.budget.toLocaleString()}
</p>
</div>
<div>
<p className="text-xs font-medium text-gray-700">
İlerleme
</p>
<p className="text-xs text-gray-600">
%{phase.progress}
</p>
<p className="text-xs font-medium text-gray-700">İlerleme</p>
<p className="text-xs text-gray-600">%{phase.progress}</p>
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
@ -489,9 +434,7 @@ const ProjectView: React.FC = () => {
) : (
<div className="px-3 py-5 text-center">
<FaFlag className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
Faz bulunamadı
</h3>
<h3 className="mt-2 text-sm font-medium text-gray-900">Faz bulunamadı</h3>
<p className="mt-1 text-sm text-gray-500">
Bu proje için henüz faz tanımlanmamış.
</p>
@ -502,21 +445,17 @@ const ProjectView: React.FC = () => {
</div>
)}
{activeTab === "tasks" && (
{activeTab === 'tasks' && (
<div className="space-y-3">
{/* Tasks List */}
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="divide-y divide-gray-200">
{mockProjectTasks.filter(
(task) => task.projectId === project.id
).length > 0 ? (
{mockProjectTasks.filter((task) => task.projectId === project.id).length >
0 ? (
mockProjectTasks
.filter((task) => task.projectId === project.id)
.map((task) => (
<div
key={task.id}
className="px-3 py-2 hover:bg-gray-50"
>
<div key={task.id} className="px-3 py-2 hover:bg-gray-50">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
@ -529,64 +468,52 @@ const ProjectView: React.FC = () => {
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
task.status === TaskStatusEnum.Completed
? "bg-green-100 text-green-800"
: task.status ===
TaskStatusEnum.InProgress
? "bg-yellow-100 text-yellow-800"
: task.status ===
TaskStatusEnum.NotStarted
? "bg-gray-100 text-gray-800"
: "bg-red-100 text-red-800"
? 'bg-green-100 text-green-800'
: task.status === TaskStatusEnum.InProgress
? 'bg-yellow-100 text-yellow-800'
: task.status === TaskStatusEnum.NotStarted
? 'bg-gray-100 text-gray-800'
: 'bg-red-100 text-red-800'
}`}
>
{task.status === TaskStatusEnum.Completed
? "Tamamlandı"
: task.status ===
TaskStatusEnum.InProgress
? "Devam Ediyor"
: task.status ===
TaskStatusEnum.NotStarted
? "Başlamadı"
? 'Tamamlandı'
: task.status === TaskStatusEnum.InProgress
? 'Devam Ediyor'
: task.status === TaskStatusEnum.NotStarted
? 'Başlamadı'
: task.status}
</span>
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
task.priority === PriorityEnum.High
? "bg-red-100 text-red-800"
? 'bg-red-100 text-red-800'
: task.priority === PriorityEnum.Normal
? "bg-orange-100 text-orange-800"
: "bg-green-100 text-green-800"
? 'bg-orange-100 text-orange-800'
: 'bg-green-100 text-green-800'
}`}
>
{task.priority === PriorityEnum.High
? "Yüksek"
? 'Yüksek'
: task.priority === PriorityEnum.Normal
? "Normal"
? 'Normal'
: task.priority === PriorityEnum.Urgent
? "Acil"
: "Düşük"}
? 'Acil'
: 'Düşük'}
</span>
</div>
<p className="text-sm text-gray-600 mb-2">
{task.description}
</p>
<p className="text-sm text-gray-600 mb-2">{task.description}</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-2">
<div>
<p className="text-xs font-medium text-gray-700">
Başlangıç
</p>
<p className="text-xs font-medium text-gray-700">Başlangıç</p>
<p className="text-xs text-gray-600">
{dayjs(task.startDate).format(
"DD.MM.YYYY"
)}
{dayjs(task.startDate).format('DD.MM.YYYY')}
</p>
</div>
<div>
<p className="text-xs font-medium text-gray-700">
Bitiş
</p>
<p className="text-xs font-medium text-gray-700">Bitiş</p>
<p className="text-xs text-gray-600">
{dayjs(task.endDate).format("DD.MM.YYYY")}
{dayjs(task.endDate).format('DD.MM.YYYY')}
</p>
</div>
<div>
@ -598,24 +525,20 @@ const ProjectView: React.FC = () => {
</p>
</div>
<div>
<p className="text-xs font-medium text-gray-700">
İlerleme
</p>
<p className="text-xs text-gray-600">
%{task.progress}
</p>
<p className="text-xs font-medium text-gray-700">İlerleme</p>
<p className="text-xs text-gray-600">%{task.progress}</p>
</div>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div
className={`h-1.5 rounded-full ${
task.progress >= 90
? "bg-green-500"
? 'bg-green-500'
: task.progress >= 70
? "bg-blue-500"
? 'bg-blue-500'
: task.progress >= 50
? "bg-yellow-500"
: "bg-red-500"
? 'bg-yellow-500'
: 'bg-red-500'
}`}
style={{ width: `${task.progress}%` }}
/>
@ -645,17 +568,14 @@ const ProjectView: React.FC = () => {
</div>
)}
{activeTab === "documents" && (
{activeTab === 'documents' && (
<div className="space-y-3">
{/* Documents List */}
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="divide-y divide-gray-200">
{project.documents.length > 0 ? (
project.documents.map((doc) => (
<div
key={doc.id}
className="px-3 py-2 hover:bg-gray-50"
>
<div key={doc.id} className="px-3 py-2 hover:bg-gray-50">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-7 h-7 bg-blue-100 rounded-lg flex items-center justify-center">
@ -668,9 +588,7 @@ const ProjectView: React.FC = () => {
<div className="flex items-center space-x-3 text-xs text-gray-500 mt-1">
<span>{doc.documentType}</span>
<span>{doc.fileSize} MB</span>
<span>
{dayjs(doc.uploadedAt).format("DD.MM.YYYY")}
</span>
<span>{dayjs(doc.uploadedAt).format('DD.MM.YYYY')}</span>
<span>Yükleyen: {doc.uploadedBy}</span>
</div>
</div>
@ -699,17 +617,14 @@ const ProjectView: React.FC = () => {
</div>
)}
{activeTab === "risks" && (
{activeTab === 'risks' && (
<div className="space-y-3">
{/* Risks List */}
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="divide-y divide-gray-200">
{project.risks.length > 0 ? (
project.risks.map((risk) => (
<div
key={risk.id}
className="px-3 py-2 hover:bg-gray-50"
>
<div key={risk.id} className="px-3 py-2 hover:bg-gray-50">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-1.5">
@ -723,26 +638,19 @@ const ProjectView: React.FC = () => {
{risk.status}
</span>
</div>
<p className="text-sm text-gray-600 mb-1.5">
{risk.description}
</p>
<p className="text-sm text-gray-600 mb-1.5">{risk.description}</p>
{risk.mitigationPlan && (
<div className="bg-gray-50 rounded-lg p-2 mb-2">
<p className="text-xs font-medium text-gray-700 mb-1">
Önlem Planı:
</p>
<p className="text-xs text-gray-600">
{risk.mitigationPlan}
</p>
<p className="text-xs text-gray-600">{risk.mitigationPlan}</p>
</div>
)}
<div className="flex items-center space-x-3 text-xs text-gray-500">
<span>Risk Kodu: {risk.riskCode}</span>
<span>
Tanımlama:{" "}
{dayjs(risk.identifiedDate).format(
"DD.MM.YYYY"
)}
Tanımlama: {dayjs(risk.identifiedDate).format('DD.MM.YYYY')}
</span>
</div>
</div>
@ -771,7 +679,7 @@ const ProjectView: React.FC = () => {
)}
{/* Financial Summary Tab */}
{activeTab === "financial" && (
{activeTab === 'financial' && (
<div className="space-y-3">
{/* Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
@ -781,9 +689,7 @@ const ProjectView: React.FC = () => {
<FaDollarSign className="h-5 w-5 text-blue-600" />
</div>
<div className="ml-2">
<p className="text-sm font-medium text-gray-500">
Toplam Bütçe
</p>
<p className="text-sm font-medium text-gray-500">Toplam Bütçe</p>
<p className="text-base font-semibold text-gray-900">
{project.budget.toLocaleString()}
</p>
@ -797,9 +703,7 @@ const ProjectView: React.FC = () => {
<FaDollarSign className="h-5 w-5 text-red-600" />
</div>
<div className="ml-2">
<p className="text-sm font-medium text-gray-500">
Harcanan
</p>
<p className="text-sm font-medium text-gray-500">Harcanan</p>
<p className="text-base font-semibold text-gray-900">
{project.actualCost.toLocaleString()}
</p>
@ -813,14 +717,9 @@ const ProjectView: React.FC = () => {
<FaDollarSign className="h-5 w-5 text-green-600" />
</div>
<div className="ml-2">
<p className="text-sm font-medium text-gray-500">
Kalan Bütçe
</p>
<p className="text-sm font-medium text-gray-500">Kalan Bütçe</p>
<p className="text-base font-semibold text-gray-900">
{(
project.budget - project.actualCost
).toLocaleString()}
{(project.budget - project.actualCost).toLocaleString()}
</p>
</div>
</div>
@ -832,9 +731,7 @@ const ProjectView: React.FC = () => {
<FaChartLine className="h-5 w-5 text-orange-600" />
</div>
<div className="ml-2">
<p className="text-sm font-medium text-gray-500">
Kullanım Oranı
</p>
<p className="text-sm font-medium text-gray-500">Kullanım Oranı</p>
<p className="text-base font-semibold text-gray-900">
%{budgetUsagePercentage.toFixed(1)}
</p>
@ -850,19 +747,17 @@ const ProjectView: React.FC = () => {
</h3>
<div className="space-y-2">
<div className="flex justify-between text-xs">
<span>
Harcanan: {project.actualCost.toLocaleString()}
</span>
<span>Harcanan: {project.actualCost.toLocaleString()}</span>
<span>Toplam: {project.budget.toLocaleString()}</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className={`h-2.5 rounded-full transition-all duration-300 ${
budgetUsagePercentage > 90
? "bg-red-500"
? 'bg-red-500'
: budgetUsagePercentage > 70
? "bg-orange-500"
: "bg-green-500"
? 'bg-orange-500'
: 'bg-green-500'
}`}
style={{
width: `${Math.min(budgetUsagePercentage, 100)}%`,
@ -872,17 +767,17 @@ const ProjectView: React.FC = () => {
<div
className={`text-xs font-medium ${
budgetUsagePercentage > 90
? "text-red-600"
? 'text-red-600'
: budgetUsagePercentage > 70
? "text-orange-600"
: "text-green-600"
? 'text-orange-600'
: 'text-green-600'
}`}
>
{budgetUsagePercentage > 90
? "Kritik Seviye - Bütçe Aşımı Riski"
? 'Kritik Seviye - Bütçe Aşımı Riski'
: budgetUsagePercentage > 70
? "Dikkat - Yüksek Kullanım"
: "Normal Seviye"}
? 'Dikkat - Yüksek Kullanım'
: 'Normal Seviye'}
</div>
</div>
</div>
@ -922,8 +817,7 @@ const ProjectView: React.FC = () => {
{mockProjectPhases
.filter((phase) => phase.projectId === project.id)
.map((phase) => {
const phaseUsage =
(phase.actualCost / phase.budget) * 100;
const phaseUsage = (phase.actualCost / phase.budget) * 100
return (
<tr key={phase.id} className="hover:bg-gray-50">
<td className="px-3 py-2 whitespace-nowrap">
@ -931,9 +825,7 @@ const ProjectView: React.FC = () => {
<div className="text-sm font-medium text-gray-900">
{phase.name}
</div>
<div className="text-sm text-gray-500">
{phase.code}
</div>
<div className="text-sm text-gray-500">{phase.code}</div>
</div>
</td>
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
@ -943,10 +835,7 @@ const ProjectView: React.FC = () => {
{phase.actualCost.toLocaleString()}
</td>
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
{(
phase.budget - phase.actualCost
).toLocaleString()}
{(phase.budget - phase.actualCost).toLocaleString()}
</td>
<td className="px-3 py-2 whitespace-nowrap">
<div className="flex items-center">
@ -954,16 +843,13 @@ const ProjectView: React.FC = () => {
<div
className={`h-1.5 rounded-full ${
phaseUsage > 90
? "bg-red-500"
? 'bg-red-500'
: phaseUsage > 70
? "bg-orange-500"
: "bg-green-500"
? 'bg-orange-500'
: 'bg-green-500'
}`}
style={{
width: `${Math.min(
phaseUsage,
100
)}%`,
width: `${Math.min(phaseUsage, 100)}%`,
}}
/>
</div>
@ -976,25 +862,25 @@ const ProjectView: React.FC = () => {
<span
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
phaseUsage > 100
? "bg-red-100 text-red-800"
? 'bg-red-100 text-red-800'
: phaseUsage > 90
? "bg-orange-100 text-orange-800"
? 'bg-orange-100 text-orange-800'
: phaseUsage > 70
? "bg-yellow-100 text-yellow-800"
: "bg-green-100 text-green-800"
? 'bg-yellow-100 text-yellow-800'
: 'bg-green-100 text-green-800'
}`}
>
{phaseUsage > 100
? "Bütçe Aşımı"
? 'Bütçe Aşımı'
: phaseUsage > 90
? "Kritik"
? 'Kritik'
: phaseUsage > 70
? "Dikkat"
: "Normal"}
? 'Dikkat'
: 'Normal'}
</span>
</td>
</tr>
);
)
})}
</tbody>
</table>
@ -1009,33 +895,25 @@ const ProjectView: React.FC = () => {
</h3>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">
Personel Maliyeti
</span>
<span className="text-sm text-gray-600">Personel Maliyeti</span>
<span className="text-sm font-medium text-gray-900">
{(project.actualCost * 0.6).toLocaleString()}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">
Malzeme Maliyeti
</span>
<span className="text-sm text-gray-600">Malzeme Maliyeti</span>
<span className="text-sm font-medium text-gray-900">
{(project.actualCost * 0.25).toLocaleString()}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">
İş Merkezi Maliyeti
</span>
<span className="text-sm text-gray-600">İş Merkezi Maliyeti</span>
<span className="text-sm font-medium text-gray-900">
{(project.actualCost * 0.1).toLocaleString()}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600">
Diğer Giderler
</span>
<span className="text-sm text-gray-600">Diğer Giderler</span>
<span className="text-sm font-medium text-gray-900">
{(project.actualCost * 0.05).toLocaleString()}
</span>
@ -1050,13 +928,11 @@ const ProjectView: React.FC = () => {
<div className="space-y-2">
<div>
<div className="flex justify-between mb-1">
<span className="text-sm text-gray-600">
Ortalama Günlük Harcama
</span>
<span className="text-sm text-gray-600">Ortalama Günlük Harcama</span>
<span className="text-sm font-medium text-gray-900">
{Math.round(
project.actualCost / Math.max(timeElapsed, 1)
project.actualCost / Math.max(timeElapsed, 1),
).toLocaleString()}
</span>
</div>
@ -1069,29 +945,23 @@ const ProjectView: React.FC = () => {
<span className="text-sm font-medium text-gray-900">
{Math.round(
(project.actualCost /
Math.max(project.progress, 1)) *
100
(project.actualCost / Math.max(project.progress, 1)) * 100,
).toLocaleString()}
</span>
</div>
</div>
<div>
<div className="flex justify-between mb-1">
<span className="text-sm text-gray-600">
Maliyet Varyansı
</span>
<span className="text-sm text-gray-600">Maliyet Varyansı</span>
<span
className={`text-sm font-medium ${
project.actualCost > project.budget
? "text-red-600"
: "text-green-600"
? 'text-red-600'
: 'text-green-600'
}`}
>
{project.actualCost > project.budget ? "+" : ""}
{(
project.actualCost - project.budget
).toLocaleString()}
{project.actualCost > project.budget ? '+' : ''}
{(project.actualCost - project.budget).toLocaleString()}
</span>
</div>
</div>
@ -1103,8 +973,8 @@ const ProjectView: React.FC = () => {
<span
className={`text-sm font-medium ${
project.budget / project.actualCost >= 1
? "text-green-600"
: "text-red-600"
? 'text-green-600'
: 'text-red-600'
}`}
>
{(project.budget / project.actualCost).toFixed(2)}
@ -1120,7 +990,8 @@ const ProjectView: React.FC = () => {
</div>
</div>
</div>
);
};
</Container>
)
}
export default ProjectView;
export default ProjectView

View file

@ -1,99 +1,77 @@
import React, { useState } from "react";
import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from "react-icons/fa";
import {
PsTaskDailyUpdate,
WorkTypeEnum,
DailyUpdateStatusEnum,
} from "../../../types/ps";
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
import { mockEmployees } from "../../../mocks/mockEmployees";
import { mockProjectTaskDailyUpdates } from "../../../mocks/mockProjectTaskDailyUpdates";
import Widget from "../../../components/common/Widget";
import {
getDailyUpdateStatusColor,
getWorkTypeColor,
} from "../../../utils/erp";
import React, { useState } from 'react'
import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from 'react-icons/fa'
import { PsTaskDailyUpdate, WorkTypeEnum, DailyUpdateStatusEnum } from '../../../types/ps'
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
import { mockEmployees } from '../../../mocks/mockEmployees'
import { mockProjectTaskDailyUpdates } from '../../../mocks/mockProjectTaskDailyUpdates'
import Widget from '../../../components/common/Widget'
import { getDailyUpdateStatusColor, getWorkTypeColor } from '../../../utils/erp'
import { Container } from '@/components/shared'
interface TaskDailyUpdatesFormData {
taskId: string;
date: string;
hoursWorked: number;
description: string;
workType: WorkTypeEnum;
progress: number;
challenges?: string;
nextSteps?: string;
taskId: string
date: string
hoursWorked: number
description: string
workType: WorkTypeEnum
progress: number
challenges?: string
nextSteps?: string
}
const initialFormData: TaskDailyUpdatesFormData = {
taskId: "",
date: new Date().toISOString().split("T")[0],
taskId: '',
date: new Date().toISOString().split('T')[0],
hoursWorked: 0,
description: "",
description: '',
workType: WorkTypeEnum.Development,
progress: 0,
challenges: "",
nextSteps: "",
};
challenges: '',
nextSteps: '',
}
const TaskDailyUpdates: React.FC = () => {
const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>(
mockProjectTaskDailyUpdates
);
const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>(
null
);
const [formData, setFormData] =
useState<TaskDailyUpdatesFormData>(initialFormData);
const [selectedDate, setSelectedDate] = useState<string>(
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);
const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>(mockProjectTaskDailyUpdates)
const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>(null)
const [formData, setFormData] = useState<TaskDailyUpdatesFormData>(initialFormData)
const [selectedDate, setSelectedDate] = useState<string>(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
const filteredUpdates = updates.filter((update) => {
const matchesDate =
!selectedDate || update.date.toISOString().split("T")[0] === selectedDate;
const matchesEmployee = update.employeeId === selectedEmployee;
const matchesDate = !selectedDate || update.date.toISOString().split('T')[0] === selectedDate
const matchesEmployee = update.employeeId === selectedEmployee
const matchesSearch =
!searchQuery ||
update.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
update.task?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
update.employee?.firstName
?.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
update.employee?.lastName
?.toLowerCase()
.includes(searchQuery.toLowerCase());
return matchesDate && matchesEmployee && matchesSearch;
});
update.employee?.firstName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
update.employee?.lastName?.toLowerCase().includes(searchQuery.toLowerCase())
return matchesDate && matchesEmployee && matchesSearch
})
// Get tasks for the selected employee in form
const formUserTasks = mockProjectTasks.filter(
(task) => task.assignedTo === selectedEmployeeForForm
);
(task) => task.assignedTo === selectedEmployeeForForm,
)
const handleInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
) => {
const { name, value } = e.target;
const { name, value } = e.target
setFormData((prev) => ({
...prev,
[name]:
name === "hoursWorked" || name === "progress" ? Number(value) : value,
}));
};
[name]: name === 'hoursWorked' || name === 'progress' ? Number(value) : value,
}))
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
e.preventDefault()
const newUpdate: PsTaskDailyUpdate = {
id: editingUpdate ? editingUpdate.id : Date.now().toString(),
@ -112,86 +90,268 @@ const TaskDailyUpdates: React.FC = () => {
attachments: [],
creationTime: editingUpdate ? editingUpdate.creationTime : new Date(),
lastModificationTime: new Date(),
};
}
if (editingUpdate) {
setUpdates((prev) =>
prev.map((update) =>
update.id === editingUpdate.id ? newUpdate : update
prev.map((update) => (update.id === editingUpdate.id ? newUpdate : update)),
)
);
} else {
setUpdates((prev) => [...prev, newUpdate]);
setUpdates((prev) => [...prev, newUpdate])
}
resetForm();
};
resetForm()
}
const resetForm = () => {
setFormData(initialFormData);
setIsModalVisible(false);
setEditingUpdate(null);
setSelectedEmployeeForForm(mockEmployees[0].id);
};
setFormData(initialFormData)
setIsModalVisible(false)
setEditingUpdate(null)
setSelectedEmployeeForForm(mockEmployees[0].id)
}
const handleEdit = (update: PsTaskDailyUpdate) => {
setEditingUpdate(update);
setSelectedEmployeeForForm(update.employeeId);
setEditingUpdate(update)
setSelectedEmployeeForForm(update.employeeId)
setFormData({
taskId: update.taskId,
date: update.date.toISOString().split("T")[0],
date: update.date.toISOString().split('T')[0],
hoursWorked: update.hoursWorked,
description: update.description,
workType: update.workType,
progress: update.progress,
challenges: update.challenges || "",
nextSteps: update.nextSteps || "",
});
setIsModalVisible(true);
};
challenges: update.challenges || '',
nextSteps: update.nextSteps || '',
})
setIsModalVisible(true)
}
const handleDelete = (id: string) => {
if (
window.confirm("Bu günlük raporu silmek istediğinizden emin misiniz?")
) {
setUpdates((prev) => prev.filter((update) => update.id !== id));
if (window.confirm('Bu günlük raporu silmek istediğinizden emin misiniz?')) {
setUpdates((prev) => prev.filter((update) => update.id !== id))
}
}
};
const handleStatusChange = (id: string, status: DailyUpdateStatusEnum) => {
setUpdates((prev) =>
prev.map((update) => (update.id === id ? { ...update, status } : update))
);
};
setUpdates((prev) => prev.map((update) => (update.id === id ? { ...update, status } : update)))
}
const todayTotalHours = filteredUpdates
.filter(
(update) =>
update.date.toISOString().split("T")[0] ===
new Date().toISOString().split("T")[0]
update.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 (
<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 */}
{isModalVisible && (
<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="flex items-center justify-between mb-3">
<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>
<button
onClick={resetForm}
className="text-gray-400 hover:text-gray-600"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<button onClick={resetForm} className="text-gray-400 hover:text-gray-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
@ -211,8 +371,8 @@ const TaskDailyUpdates: React.FC = () => {
<select
value={selectedEmployeeForForm}
onChange={(e) => {
setSelectedEmployeeForForm(e.target.value);
setFormData((prev) => ({ ...prev, taskId: "" })); // Reset task selection
setSelectedEmployeeForForm(e.target.value)
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"
>
@ -239,8 +399,8 @@ const TaskDailyUpdates: React.FC = () => {
<option value="">Görev Seçin</option>
{formUserTasks.map((task) => (
<option key={task.id} value={task.id}>
{task.phase?.project?.name || "Proje"} {" "}
{task.phase?.name || "Aşama"} {task.name}
{task.phase?.project?.name || 'Proje'} {task.phase?.name || 'Aşama'} {' '}
{task.name}
</option>
))}
</select>
@ -263,9 +423,7 @@ const TaskDailyUpdates: React.FC = () => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
İş Türü
</label>
<label className="block text-sm font-medium text-gray-700 mb-1">İş Türü</label>
<select
name="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"
>
<FaSave className="h-4 w-4" />
{editingUpdate ? "Güncelle" : "Kaydet"}
{editingUpdate ? 'Güncelle' : 'Kaydet'}
</button>
</div>
</form>
</div>
</div>
)}
{/* 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-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
</Container>
)
}
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;
export default TaskDailyUpdates

View file

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