Project Management
This commit is contained in:
parent
0cb87b8295
commit
2217722243
11 changed files with 4063 additions and 4713 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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> = ({
|
|||
Açı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
|
||||
|
|
|
|||
|
|
@ -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">Açı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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
Açıklama
|
||||
</h3>
|
||||
<h3 className="text-base font-semibold text-gray-900 mb-2">Açı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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue