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",
|
"key": "admin.project.list",
|
||||||
"path": "/admin/project",
|
"path": "/admin/projects",
|
||||||
"componentPath": "@/views/project/components/ProjectList",
|
"componentPath": "@/views/project/components/ProjectList",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.new",
|
"key": "admin.project.new",
|
||||||
"path": "/admin/project/new",
|
"path": "/admin/projects/new",
|
||||||
"componentPath": "@/views/project/components/ProjectForm",
|
"componentPath": "@/views/project/components/ProjectForm",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.edit",
|
"key": "admin.project.edit",
|
||||||
"path": "/admin/project/edit/:id",
|
"path": "/admin/projects/edit/:id",
|
||||||
"componentPath": "@/views/project/components/ProjectForm",
|
"componentPath": "@/views/project/components/ProjectForm",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.detail",
|
"key": "admin.project.detail",
|
||||||
"path": "/admin/project/:id",
|
"path": "/admin/projects/:id",
|
||||||
"componentPath": "@/views/project/components/ProjectView",
|
"componentPath": "@/views/project/components/ProjectView",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.tasks",
|
"key": "admin.project.tasks",
|
||||||
"path": "/admin/project/tasks",
|
"path": "/admin/projects/tasks",
|
||||||
"componentPath": "@/views/project/components/ProjectTasks",
|
"componentPath": "@/views/project/components/ProjectTasks",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.phases",
|
"key": "admin.project.phases",
|
||||||
"path": "/admin/project/phases",
|
"path": "/admin/projects/phases",
|
||||||
"componentPath": "@/views/project/components/ProjectPhases",
|
"componentPath": "@/views/project/components/ProjectPhases",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.activities",
|
"key": "admin.project.activities",
|
||||||
"path": "/admin/project/activities",
|
"path": "/admin/projects/activities",
|
||||||
"componentPath": "@/views/project/components/ActivityTypes",
|
"componentPath": "@/views/project/components/ActivityTypes",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.workload",
|
"key": "admin.project.workload",
|
||||||
"path": "/admin/project/workload",
|
"path": "/admin/projects/workload",
|
||||||
"componentPath": "@/views/project/components/ProjectGantt",
|
"componentPath": "@/views/project/components/ProjectGantt",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.costTracking",
|
"key": "admin.project.costTracking",
|
||||||
"path": "/admin/project/cost-tracking",
|
"path": "/admin/projects/cost-tracking",
|
||||||
"componentPath": "@/views/project/components/CostTimeTracking",
|
"componentPath": "@/views/project/components/CostTimeTracking",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.project.dailyUpdates",
|
"key": "admin.project.dailyUpdates",
|
||||||
"path": "/admin/project/daily-updates",
|
"path": "/admin/projects/daily-updates",
|
||||||
"componentPath": "@/views/project/components/TaskDailyUpdates",
|
"componentPath": "@/views/project/components/TaskDailyUpdates",
|
||||||
"routeType": "protected",
|
"routeType": "protected",
|
||||||
"authority": null
|
"authority": null
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react'
|
||||||
import { FaSave, FaTimes } from "react-icons/fa";
|
import { FaSave, FaTimes } from 'react-icons/fa'
|
||||||
import { PsActivityTypeEnum, PsActivity } from "../../../types/ps";
|
import { PsActivityTypeEnum, PsActivity } from '../../../types/ps'
|
||||||
import { getPsActivityTypeText } from "../../../utils/erp";
|
import { getPsActivityTypeText } from '../../../utils/erp'
|
||||||
|
|
||||||
interface ActivityTypeFormModalProps {
|
interface ActivityTypeFormModalProps {
|
||||||
open: boolean;
|
open: boolean
|
||||||
activityType: PsActivity | null;
|
activityType: PsActivity | null
|
||||||
onClose: () => void;
|
onClose: () => void
|
||||||
onSave: (activityType: Partial<PsActivity>) => void;
|
onSave: (activityType: Partial<PsActivity>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
|
|
@ -17,98 +17,99 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
onSave,
|
onSave,
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] = useState<Partial<PsActivity>>({
|
const [formData, setFormData] = useState<Partial<PsActivity>>({
|
||||||
name: "",
|
name: '',
|
||||||
description: "",
|
description: '',
|
||||||
category: "",
|
category: '',
|
||||||
defaultDuration: 1,
|
defaultDuration: 1,
|
||||||
activityType: PsActivityTypeEnum.WorkLog,
|
activityType: PsActivityTypeEnum.WorkLog,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activityType) {
|
if (activityType) {
|
||||||
setFormData({
|
setFormData({
|
||||||
...activityType,
|
...activityType,
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: "",
|
name: '',
|
||||||
description: "",
|
description: '',
|
||||||
category: "",
|
category: '',
|
||||||
defaultDuration: 1,
|
defaultDuration: 1,
|
||||||
activityType: PsActivityTypeEnum.WorkLog,
|
activityType: PsActivityTypeEnum.WorkLog,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
setErrors({});
|
setErrors({})
|
||||||
}, [activityType]);
|
}, [activityType])
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
field: keyof PsActivity,
|
field: keyof PsActivity,
|
||||||
value: string | number | boolean | PsActivityTypeEnum
|
value: string | number | boolean | PsActivityTypeEnum,
|
||||||
) => {
|
) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
// Clear error when user starts typing
|
// Clear error when user starts typing
|
||||||
if (errors[field]) {
|
if (errors[field]) {
|
||||||
setErrors((prev) => ({
|
setErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: "",
|
[field]: '',
|
||||||
}));
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
const newErrors: Record<string, string> = {};
|
const newErrors: Record<string, string> = {}
|
||||||
|
|
||||||
if (!formData.name?.trim()) {
|
if (!formData.name?.trim()) {
|
||||||
newErrors.name = "Aktivite türü adı zorunludur";
|
newErrors.name = 'Aktivite türü adı zorunludur'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.description?.trim()) {
|
if (!formData.description?.trim()) {
|
||||||
newErrors.description = "Açıklama zorunludur";
|
newErrors.description = 'Açıklama zorunludur'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.category?.trim()) {
|
if (!formData.category?.trim()) {
|
||||||
newErrors.category = "Kategori zorunludur";
|
newErrors.category = 'Kategori zorunludur'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.defaultDuration || formData.defaultDuration <= 0) {
|
if (!formData.defaultDuration || formData.defaultDuration <= 0) {
|
||||||
newErrors.defaultDuration = "Varsayılan süre pozitif bir sayı olmalıdır";
|
newErrors.defaultDuration = 'Varsayılan süre pozitif bir sayı olmalıdır'
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors)
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (validateForm()) {
|
if (validateForm()) {
|
||||||
onSave(formData);
|
onSave(formData)
|
||||||
onClose();
|
onClose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||||
<div className="flex items-center justify-center min-h-screen pt-2 px-2 pb-8 text-center sm:block sm:p-0">
|
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
></div>
|
></div>
|
||||||
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
||||||
<div className="inline-block w-full align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-2 sm:align-middle sm:max-w-md sm:w-full">
|
<div className="space-y-3">
|
||||||
|
<div className="space-y-3">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white px-3 py-2 border-b border-gray-200">
|
<div className="bg-white px-3 py-2 border-b border-gray-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-base font-medium text-gray-900">
|
<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>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|
@ -128,15 +129,12 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
Aktivite Türü *
|
Aktivite Türü *
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={formData.activityType || ""}
|
value={formData.activityType || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('activityType', e.target.value as PsActivityTypeEnum)
|
||||||
"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 ${
|
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) => (
|
{Object.values(PsActivityTypeEnum).map((type) => (
|
||||||
|
|
@ -146,29 +144,23 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{errors.activityType && (
|
{errors.activityType && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.activityType}</p>
|
||||||
{errors.activityType}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">Ad *</label>
|
||||||
Ad *
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.name || ""}
|
value={formData.name || ''}
|
||||||
onChange={(e) => handleInputChange("name", e.target.value)}
|
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 ${
|
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"
|
placeholder="Aktivite türü adını girin"
|
||||||
/>
|
/>
|
||||||
{errors.name && (
|
{errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
|
||||||
<p className="mt-1 text-sm text-red-600">{errors.name}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
|
|
@ -177,20 +169,16 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
Açıklama *
|
Açıklama *
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description || ""}
|
value={formData.description || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('description', e.target.value)}
|
||||||
handleInputChange("description", e.target.value)
|
|
||||||
}
|
|
||||||
rows={2}
|
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 ${
|
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"
|
placeholder="Aktivite türü açıklamasını girin"
|
||||||
/>
|
/>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
|
||||||
{errors.description}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -201,12 +189,10 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.category || ""}
|
value={formData.category || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('category', e.target.value)}
|
||||||
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 ${
|
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)"
|
placeholder="Kategori adını girin (örn: Görev Yönetimi)"
|
||||||
/>
|
/>
|
||||||
|
|
@ -224,24 +210,17 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.25"
|
step="0.25"
|
||||||
value={formData.defaultDuration || ""}
|
value={formData.defaultDuration || ''}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange('defaultDuration', parseFloat(e.target.value))
|
||||||
"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 ${
|
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
|
errors.defaultDuration ? 'border-red-500' : 'border-gray-300'
|
||||||
? "border-red-500"
|
|
||||||
: "border-gray-300"
|
|
||||||
}`}
|
}`}
|
||||||
placeholder="1.0"
|
placeholder="1.0"
|
||||||
/>
|
/>
|
||||||
{errors.defaultDuration && (
|
{errors.defaultDuration && (
|
||||||
<p className="mt-1 text-sm text-red-600">
|
<p className="mt-1 text-sm text-red-600">{errors.defaultDuration}</p>
|
||||||
{errors.defaultDuration}
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -251,15 +230,10 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="isActive"
|
id="isActive"
|
||||||
checked={formData.isActive || false}
|
checked={formData.isActive || false}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleInputChange('isActive', e.target.checked)}
|
||||||
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"
|
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
|
<label htmlFor="isActive" className="ml-2 text-sm text-gray-700">
|
||||||
htmlFor="isActive"
|
|
||||||
className="ml-2 text-sm text-gray-700"
|
|
||||||
>
|
|
||||||
Aktif durumda
|
Aktif durumda
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -287,7 +261,9 @@ const ActivityTypeFormModal: React.FC<ActivityTypeFormModalProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
};
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ActivityTypeFormModal;
|
export default ActivityTypeFormModal
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,85 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import {
|
import { FaSearch, FaPlus, FaEdit, FaTrash, FaEye, FaTh, FaList } from 'react-icons/fa'
|
||||||
FaSearch,
|
import { PsActivity } from '../../../types/ps'
|
||||||
FaPlus,
|
import { activityTypes } from '../../../mocks/mockActivityTypes'
|
||||||
FaEdit,
|
import ActivityTypeFormModal from './ActivityTypeFormModal'
|
||||||
FaTrash,
|
import Widget from '../../../components/common/Widget'
|
||||||
FaEye,
|
import { getPsActivityTypeColor, getPsActivityTypeIcon } from '../../../utils/erp'
|
||||||
FaTh,
|
import { Container } from '@/components/shared'
|
||||||
FaList,
|
|
||||||
} from "react-icons/fa";
|
|
||||||
import { PsActivity } from "../../../types/ps";
|
|
||||||
import { activityTypes } from "../../../mocks/mockActivityTypes";
|
|
||||||
import ActivityTypeFormModal from "./ActivityTypeFormModal";
|
|
||||||
import Widget from "../../../components/common/Widget";
|
|
||||||
import {
|
|
||||||
getPsActivityTypeColor,
|
|
||||||
getPsActivityTypeIcon,
|
|
||||||
} from "../../../utils/erp";
|
|
||||||
|
|
||||||
const ActivityTypes: React.FC = () => {
|
const ActivityTypes: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [categoryFilter, setCategoryFilter] = useState<string>("");
|
const [categoryFilter, setCategoryFilter] = useState<string>('')
|
||||||
const [selectedActivity, setSelectedActivity] = useState<string>("");
|
const [selectedActivity, setSelectedActivity] = useState<string>('')
|
||||||
const [showActivityForm, setShowActivityForm] = useState(false);
|
const [showActivityForm, setShowActivityForm] = useState(false)
|
||||||
const [editingActivity, setEditingActivity] = useState<PsActivity | null>(
|
const [editingActivity, setEditingActivity] = useState<PsActivity | null>(null)
|
||||||
null
|
const [viewMode, setViewMode] = useState<'card' | 'list'>('card')
|
||||||
);
|
|
||||||
const [viewMode, setViewMode] = useState<"card" | "list">("card");
|
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
const handleEdit = (activity: PsActivity) => {
|
const handleEdit = (activity: PsActivity) => {
|
||||||
setEditingActivity(activity);
|
setEditingActivity(activity)
|
||||||
setShowActivityForm(true);
|
setShowActivityForm(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleNew = () => {
|
const handleNew = () => {
|
||||||
setEditingActivity(null);
|
setEditingActivity(null)
|
||||||
setShowActivityForm(true);
|
setShowActivityForm(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleCloseForm = () => {
|
const handleCloseForm = () => {
|
||||||
setShowActivityForm(false);
|
setShowActivityForm(false)
|
||||||
setEditingActivity(null);
|
setEditingActivity(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSave = (activityData: Partial<PsActivity>) => {
|
const handleSave = (activityData: Partial<PsActivity>) => {
|
||||||
// TODO: In a real application, this would make an API call
|
// TODO: In a real application, this would make an API call
|
||||||
console.log("Saving activity type:", activityData);
|
console.log('Saving activity type:', activityData)
|
||||||
|
|
||||||
if (editingActivity) {
|
if (editingActivity) {
|
||||||
console.log(
|
console.log('Updating existing activity type with ID:', editingActivity.id)
|
||||||
"Updating existing activity type with ID:",
|
|
||||||
editingActivity.id
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.log("Creating new activity type");
|
console.log('Creating new activity type')
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, just close the modal
|
// For now, just close the modal
|
||||||
handleCloseForm();
|
handleCloseForm()
|
||||||
};
|
}
|
||||||
|
|
||||||
const categories = Array.from(new Set(activityTypes.map((a) => a.category)));
|
const categories = Array.from(new Set(activityTypes.map((a) => a.category)))
|
||||||
|
|
||||||
const filteredActivities = activityTypes.filter((activity) => {
|
const filteredActivities = activityTypes.filter((activity) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
activity.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
activity.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
(activity.description &&
|
(activity.description &&
|
||||||
activity.description.toLowerCase().includes(searchTerm.toLowerCase()));
|
activity.description.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||||
const matchesCategory =
|
const matchesCategory = categoryFilter === '' || activity.category === categoryFilter
|
||||||
categoryFilter === "" || activity.category === categoryFilter;
|
return matchesSearch && matchesCategory
|
||||||
return matchesSearch && matchesCategory;
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const ActivityDetailModal = () => {
|
const ActivityDetailModal = () => {
|
||||||
const activity = activityTypes.find((a) => a.id === selectedActivity);
|
const activity = activityTypes.find((a) => a.id === selectedActivity)
|
||||||
|
|
||||||
if (!selectedActivity || !activity) return null;
|
if (!selectedActivity || !activity) return null
|
||||||
|
|
||||||
const ActivityIcon = getPsActivityTypeIcon(activity.activityType);
|
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 overflow-y-auto">
|
<div className="fixed inset-0 z-50 overflow-y-auto">
|
||||||
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
|
||||||
onClick={() => setSelectedActivity("")}
|
onClick={() => setSelectedActivity('')}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-2 sm:align-middle sm:max-w-xl sm:w-full">
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
||||||
<div className="bg-white px-3 pt-3 pb-3 sm:p-4 sm:pb-3">
|
<div className="bg-white px-3 pt-3 pb-3 sm:p-4 sm:pb-3">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-medium text-gray-900">
|
<h3 className="text-lg font-medium text-gray-900">Aktivite Türü Detayları</h3>
|
||||||
Aktivite Türü Detayları
|
|
||||||
</h3>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedActivity("")}
|
onClick={() => setSelectedActivity('')}
|
||||||
className="text-gray-400 hover:text-gray-600"
|
className="text-gray-400 hover:text-gray-600"
|
||||||
>
|
>
|
||||||
<svg
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
|
|
@ -117,16 +94,12 @@ const ActivityTypes: React.FC = () => {
|
||||||
{/* Activity Header */}
|
{/* Activity Header */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={`p-2 rounded-lg ${getPsActivityTypeColor(
|
className={`p-2 rounded-lg ${getPsActivityTypeColor(activity.activityType)}`}
|
||||||
activity.activityType
|
|
||||||
)}`}
|
|
||||||
>
|
>
|
||||||
<ActivityIcon className="w-6 h-6" />
|
<ActivityIcon className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-lg font-medium text-gray-900">
|
<h4 className="text-lg font-medium text-gray-900">{activity.name}</h4>
|
||||||
{activity.name}
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-gray-500">{activity.category}</p>
|
<p className="text-sm text-gray-500">{activity.category}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -134,9 +107,7 @@ const ActivityTypes: React.FC = () => {
|
||||||
{/* Activity Details */}
|
{/* Activity Details */}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div className="bg-gray-50 rounded-lg p-2">
|
<div className="bg-gray-50 rounded-lg p-2">
|
||||||
<h5 className="font-medium text-gray-900 mb-2">
|
<h5 className="font-medium text-gray-900 mb-2">Genel Bilgiler</h5>
|
||||||
Genel Bilgiler
|
|
||||||
</h5>
|
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<strong>Aktivite Türü:</strong> {activity.activityType}
|
<strong>Aktivite Türü:</strong> {activity.activityType}
|
||||||
|
|
@ -145,38 +116,33 @@ const ActivityTypes: React.FC = () => {
|
||||||
<strong>Kategori:</strong> {activity.category}
|
<strong>Kategori:</strong> {activity.category}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Varsayılan Süre:</strong>{" "}
|
<strong>Varsayılan Süre:</strong> {activity.defaultDuration} saat
|
||||||
{activity.defaultDuration} saat
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Durum:</strong>
|
<strong>Durum:</strong>
|
||||||
<span
|
<span
|
||||||
className={`ml-2 px-2 py-1 text-xs rounded-full ${
|
className={`ml-2 px-2 py-1 text-xs rounded-full ${
|
||||||
activity.isActive
|
activity.isActive
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{activity.isActive ? "Aktif" : "Pasif"}
|
{activity.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 rounded-lg p-2">
|
<div className="bg-gray-50 rounded-lg p-2">
|
||||||
<h5 className="font-medium text-gray-900 mb-2">
|
<h5 className="font-medium text-gray-900 mb-2">Zaman Bilgileri</h5>
|
||||||
Zaman Bilgileri
|
|
||||||
</h5>
|
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<strong>Oluşturulma:</strong>{" "}
|
<strong>Oluşturulma:</strong>{' '}
|
||||||
{activity.creationTime.toLocaleDateString("tr-TR")}
|
{activity.creationTime.toLocaleDateString('tr-TR')}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Son Güncelleme:</strong>{" "}
|
<strong>Son Güncelleme:</strong>{' '}
|
||||||
{activity.lastModificationTime.toLocaleDateString(
|
{activity.lastModificationTime.toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -184,45 +150,42 @@ const ActivityTypes: React.FC = () => {
|
||||||
|
|
||||||
<div className="bg-gray-50 rounded-lg p-2">
|
<div className="bg-gray-50 rounded-lg p-2">
|
||||||
<h5 className="font-medium text-gray-900 mb-2">Açıklama</h5>
|
<h5 className="font-medium text-gray-900 mb-2">Açıklama</h5>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">{activity.description}</p>
|
||||||
{activity.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-gray-900">Aktivite Türleri</h2>
|
<h2 className="text-xl font-bold text-gray-900">Aktivite Türleri</h2>
|
||||||
<p className="text-gray-600 mt-1">
|
<p className="text-gray-600 mt-1">Proje ve görev aktivitelerinin türlerini yönetin</p>
|
||||||
Proje ve görev aktivitelerinin türlerini yönetin
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
<div className="flex border border-gray-300 rounded-lg overflow-hidden">
|
<div className="flex border border-gray-300 rounded-lg overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("card")}
|
onClick={() => setViewMode('card')}
|
||||||
className={`px-2.5 py-1.5 text-sm ${
|
className={`px-2.5 py-1.5 text-sm ${
|
||||||
viewMode === "card"
|
viewMode === 'card'
|
||||||
? "bg-blue-600 text-white"
|
? 'bg-blue-600 text-white'
|
||||||
: "bg-white text-gray-700 hover:bg-gray-50"
|
: 'bg-white text-gray-700 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaTh className="w-4 h-4" />
|
<FaTh className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("list")}
|
onClick={() => setViewMode('list')}
|
||||||
className={`px-2.5 py-1.5 text-sm ${
|
className={`px-2.5 py-1.5 text-sm ${
|
||||||
viewMode === "list"
|
viewMode === 'list'
|
||||||
? "bg-blue-600 text-white"
|
? 'bg-blue-600 text-white'
|
||||||
: "bg-white text-gray-700 hover:bg-gray-50"
|
: 'bg-white text-gray-700 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaList className="w-4 h-4" />
|
<FaList className="w-4 h-4" />
|
||||||
|
|
@ -264,8 +227,7 @@ const ActivityTypes: React.FC = () => {
|
||||||
<Widget
|
<Widget
|
||||||
title="Ortalama Süre"
|
title="Ortalama Süre"
|
||||||
value={`${(
|
value={`${(
|
||||||
activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) /
|
activityTypes.reduce((acc, a) => acc + a.defaultDuration, 0) / activityTypes.length
|
||||||
activityTypes.length
|
|
||||||
).toFixed(1)}h`}
|
).toFixed(1)}h`}
|
||||||
color="orange"
|
color="orange"
|
||||||
icon="FaFlask"
|
icon="FaFlask"
|
||||||
|
|
@ -299,10 +261,10 @@ const ActivityTypes: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Activity Types - Card/List View */}
|
{/* 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">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||||
{filteredActivities.map((activity) => {
|
{filteredActivities.map((activity) => {
|
||||||
const ActivityIcon = getPsActivityTypeIcon(activity.activityType);
|
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -313,18 +275,14 @@ const ActivityTypes: React.FC = () => {
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div
|
<div
|
||||||
className={`p-2 rounded-lg ${getPsActivityTypeColor(
|
className={`p-2 rounded-lg ${getPsActivityTypeColor(
|
||||||
activity.activityType
|
activity.activityType,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<ActivityIcon className="w-4 h-4" />
|
<ActivityIcon className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-gray-900">
|
<h3 className="font-medium text-gray-900">{activity.name}</h3>
|
||||||
{activity.name}
|
<p className="text-sm text-gray-500">{activity.category}</p>
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-500">
|
|
||||||
{activity.category}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|
@ -342,43 +300,34 @@ const ActivityTypes: React.FC = () => {
|
||||||
>
|
>
|
||||||
<FaEdit className="w-4 h-4" />
|
<FaEdit className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="text-red-600 hover:text-red-900 p-1" title="Sil">
|
||||||
className="text-red-600 hover:text-red-900 p-1"
|
|
||||||
title="Sil"
|
|
||||||
>
|
|
||||||
<FaTrash className="w-4 h-4" />
|
<FaTrash className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-2">
|
<p className="text-sm text-gray-600 mb-2">{activity.description}</p>
|
||||||
{activity.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">Süre: {activity.defaultDuration}h</span>
|
||||||
Süre: {activity.defaultDuration}h
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 text-xs rounded-full ${
|
className={`px-2 py-1 text-xs rounded-full ${
|
||||||
activity.isActive
|
activity.isActive
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{activity.isActive ? "Aktif" : "Pasif"}
|
{activity.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white rounded-lg border border-gray-200">
|
<div className="bg-white rounded-lg border border-gray-200">
|
||||||
<div className="px-3 py-1.5 border-b border-gray-200">
|
<div className="px-3 py-1.5 border-b border-gray-200">
|
||||||
<h3 className="text-base font-medium text-gray-900">
|
<h3 className="text-base font-medium text-gray-900">Aktivite Türleri Listesi</h3>
|
||||||
Aktivite Türleri Listesi
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
|
|
@ -406,9 +355,7 @@ const ActivityTypes: React.FC = () => {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{filteredActivities.map((activity) => {
|
{filteredActivities.map((activity) => {
|
||||||
const ActivityIcon = getPsActivityTypeIcon(
|
const ActivityIcon = getPsActivityTypeIcon(activity.activityType)
|
||||||
activity.activityType
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={activity.id} className="hover:bg-gray-50 text-sm">
|
<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="flex items-center">
|
||||||
<div
|
<div
|
||||||
className={`p-2 rounded-lg mr-3 ${getPsActivityTypeColor(
|
className={`p-2 rounded-lg mr-3 ${getPsActivityTypeColor(
|
||||||
activity.activityType
|
activity.activityType,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<ActivityIcon className="w-4 h-4" />
|
<ActivityIcon className="w-4 h-4" />
|
||||||
|
|
@ -425,16 +372,12 @@ const ActivityTypes: React.FC = () => {
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{activity.name}
|
{activity.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">{activity.description}</div>
|
||||||
{activity.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">{activity.category}</div>
|
||||||
{activity.category}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-1.5 whitespace-nowrap">
|
<td className="px-2 py-1.5 whitespace-nowrap">
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">
|
||||||
|
|
@ -445,17 +388,15 @@ const ActivityTypes: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-1 text-xs rounded-full ${
|
className={`px-2 py-1 text-xs rounded-full ${
|
||||||
activity.isActive
|
activity.isActive
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: "bg-red-100 text-red-800"
|
: 'bg-red-100 text-red-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{activity.isActive ? "Aktif" : "Pasif"}
|
{activity.isActive ? 'Aktif' : 'Pasif'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-1.5 whitespace-nowrap text-sm text-gray-500">
|
<td className="px-2 py-1.5 whitespace-nowrap text-sm text-gray-500">
|
||||||
{activity.lastModificationTime.toLocaleDateString(
|
{activity.lastModificationTime.toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
<td className="px-2 py-1.5 whitespace-nowrap text-right text-sm font-medium">
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
|
@ -473,22 +414,20 @@ const ActivityTypes: React.FC = () => {
|
||||||
>
|
>
|
||||||
<FaEdit className="w-4 h-4" />
|
<FaEdit className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="text-red-600 hover:text-red-900" title="Sil">
|
||||||
className="text-red-600 hover:text-red-900"
|
|
||||||
title="Sil"
|
|
||||||
>
|
|
||||||
<FaTrash className="w-4 h-4" />
|
<FaTrash className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Activity Detail Modal */}
|
{/* Activity Detail Modal */}
|
||||||
<ActivityDetailModal />
|
<ActivityDetailModal />
|
||||||
|
|
@ -500,8 +439,8 @@ const ActivityTypes: React.FC = () => {
|
||||||
onClose={handleCloseForm}
|
onClose={handleCloseForm}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ActivityTypes;
|
export default ActivityTypes
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,141 +1,128 @@
|
||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
FaUser,
|
FaUser,
|
||||||
FaChevronDown,
|
FaChevronDown,
|
||||||
FaChevronRight,
|
FaChevronRight,
|
||||||
FaChevronLeft,
|
FaChevronLeft,
|
||||||
FaChevronRight as FaArrowRight,
|
FaChevronRight as FaArrowRight,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { PsGanttTask } from "../../../types/ps";
|
import { PsGanttTask } from '../../../types/ps'
|
||||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||||
import { mockProjects } from "../../../mocks/mockProjects";
|
import { mockProjects } from '../../../mocks/mockProjects'
|
||||||
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
|
import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
|
||||||
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
|
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
import { getPriorityColor, getProjectStatusColor } from "../../../utils/erp";
|
import { getPriorityColor, getProjectStatusColor } from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
interface ProjectGanttProps {
|
interface ProjectGanttProps {
|
||||||
employeeId?: string;
|
employeeId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
// İlk 2 projeyi ve fazlarını varsayılan olarak açık başlat
|
// İlk 2 projeyi ve fazlarını varsayılan olarak açık başlat
|
||||||
const getInitialExpandedItems = () => {
|
const getInitialExpandedItems = () => {
|
||||||
const expandedItems = new Set<string>();
|
const expandedItems = new Set<string>()
|
||||||
|
|
||||||
// İlk 2 projeyi aç
|
// İlk 2 projeyi aç
|
||||||
const firstTwoProjects = mockProjects.slice(0, 2);
|
const firstTwoProjects = mockProjects.slice(0, 2)
|
||||||
firstTwoProjects.forEach((project) => {
|
firstTwoProjects.forEach((project) => {
|
||||||
expandedItems.add(`project-${project.id}`);
|
expandedItems.add(`project-${project.id}`)
|
||||||
|
|
||||||
// Bu projelerin fazlarını da aç
|
// Bu projelerin fazlarını da aç
|
||||||
const projectPhases = mockProjectPhases.filter(
|
const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
|
||||||
(phase) => phase.projectId === project.id
|
|
||||||
);
|
|
||||||
projectPhases.forEach((phase) => {
|
projectPhases.forEach((phase) => {
|
||||||
expandedItems.add(`phase-${phase.id}`);
|
expandedItems.add(`phase-${phase.id}`)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
return expandedItems;
|
return expandedItems
|
||||||
};
|
}
|
||||||
|
|
||||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(getInitialExpandedItems())
|
||||||
getInitialExpandedItems()
|
|
||||||
);
|
|
||||||
const [selectedEmployee, setSelectedEmployee] = useState<string>(
|
const [selectedEmployee, setSelectedEmployee] = useState<string>(
|
||||||
employeeId || mockEmployees[0]?.id || ""
|
employeeId || mockEmployees[0]?.id || '',
|
||||||
);
|
)
|
||||||
const [viewMode, setViewMode] = useState<"day" | "week" | "month" | "year">(
|
const [viewMode, setViewMode] = useState<'day' | 'week' | 'month' | 'year'>('week')
|
||||||
"week"
|
const [currentDate, setCurrentDate] = useState<Date>(new Date())
|
||||||
);
|
|
||||||
const [currentDate, setCurrentDate] = useState<Date>(new Date());
|
|
||||||
|
|
||||||
// Gantt chart için tarih aralığı oluştur
|
// Gantt chart için tarih aralığı oluştur
|
||||||
const generateDateRange = () => {
|
const generateDateRange = () => {
|
||||||
const startDate = new Date(currentDate);
|
const startDate = new Date(currentDate)
|
||||||
|
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
// Günlük görünüm: seçili günden itibaren 24 saat
|
// Günlük görünüm: seçili günden itibaren 24 saat
|
||||||
const hours = [];
|
const hours = []
|
||||||
for (let i = 0; i < 24; i++) {
|
for (let i = 0; i < 24; i++) {
|
||||||
const hour = new Date(startDate);
|
const hour = new Date(startDate)
|
||||||
hour.setHours(i, 0, 0, 0);
|
hour.setHours(i, 0, 0, 0)
|
||||||
hours.push(hour);
|
hours.push(hour)
|
||||||
}
|
}
|
||||||
return hours;
|
return hours
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
// Haftalık görünüm: hafta başından itibaren 7 gün
|
// Haftalık görünüm: hafta başından itibaren 7 gün
|
||||||
const weekStart = new Date(startDate);
|
const weekStart = new Date(startDate)
|
||||||
const dayOfWeek = weekStart.getDay();
|
const dayOfWeek = weekStart.getDay()
|
||||||
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Pazartesi = 0, Pazar = 6
|
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1 // Pazartesi = 0, Pazar = 6
|
||||||
weekStart.setDate(weekStart.getDate() - daysToSubtract);
|
weekStart.setDate(weekStart.getDate() - daysToSubtract)
|
||||||
weekStart.setHours(0, 0, 0, 0);
|
weekStart.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
const dates = [];
|
const dates = []
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
const date = new Date(weekStart);
|
const date = new Date(weekStart)
|
||||||
date.setDate(weekStart.getDate() + i);
|
date.setDate(weekStart.getDate() + i)
|
||||||
dates.push(date);
|
dates.push(date)
|
||||||
}
|
}
|
||||||
return dates;
|
return dates
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
// Aylık görünüm: ay başından itibaren ay sonu
|
// Aylık görünüm: ay başından itibaren ay sonu
|
||||||
const monthStart = new Date(
|
const monthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)
|
||||||
currentDate.getFullYear(),
|
|
||||||
currentDate.getMonth(),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
const daysInMonth = new Date(
|
const daysInMonth = new Date(
|
||||||
currentDate.getFullYear(),
|
currentDate.getFullYear(),
|
||||||
currentDate.getMonth() + 1,
|
currentDate.getMonth() + 1,
|
||||||
0
|
0,
|
||||||
).getDate();
|
).getDate()
|
||||||
|
|
||||||
const dates = [];
|
const dates = []
|
||||||
for (let i = 0; i < daysInMonth; i++) {
|
for (let i = 0; i < daysInMonth; i++) {
|
||||||
const date = new Date(monthStart);
|
const date = new Date(monthStart)
|
||||||
date.setDate(monthStart.getDate() + i);
|
date.setDate(monthStart.getDate() + i)
|
||||||
dates.push(date);
|
dates.push(date)
|
||||||
}
|
}
|
||||||
return dates;
|
return dates
|
||||||
} else {
|
} else {
|
||||||
// Yıllık görünüm: yıl başından itibaren 12 ay
|
// Yıllık görünüm: yıl başından itibaren 12 ay
|
||||||
const yearStart = new Date(currentDate.getFullYear(), 0, 1);
|
const yearStart = new Date(currentDate.getFullYear(), 0, 1)
|
||||||
const months = [];
|
const months = []
|
||||||
for (let i = 0; i < 12; i++) {
|
for (let i = 0; i < 12; i++) {
|
||||||
const month = new Date(yearStart);
|
const month = new Date(yearStart)
|
||||||
month.setMonth(i);
|
month.setMonth(i)
|
||||||
months.push(month);
|
months.push(month)
|
||||||
|
}
|
||||||
|
return months
|
||||||
}
|
}
|
||||||
return months;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const dateRange = generateDateRange();
|
const dateRange = generateDateRange()
|
||||||
|
|
||||||
// Seçili çalışana göre görevleri filtrele
|
// Seçili çalışana göre görevleri filtrele
|
||||||
const filteredTasks = useMemo(() => {
|
const filteredTasks = useMemo(() => {
|
||||||
// Mock verilerden GanttTask formatına dönüştür
|
// Mock verilerden GanttTask formatına dönüştür
|
||||||
const createGanttTasks = (): PsGanttTask[] => {
|
const createGanttTasks = (): PsGanttTask[] => {
|
||||||
const ganttTasks: PsGanttTask[] = [];
|
const ganttTasks: PsGanttTask[] = []
|
||||||
|
|
||||||
mockProjects.forEach((project) => {
|
mockProjects.forEach((project) => {
|
||||||
const projectPhases = mockProjectPhases.filter(
|
const projectPhases = mockProjectPhases.filter((phase) => phase.projectId === project.id)
|
||||||
(phase) => phase.projectId === project.id
|
|
||||||
);
|
|
||||||
|
|
||||||
const projectChildren: PsGanttTask[] = [];
|
const projectChildren: PsGanttTask[] = []
|
||||||
|
|
||||||
projectPhases.forEach((phase) => {
|
projectPhases.forEach((phase) => {
|
||||||
const phaseTasks = mockProjectTasks.filter(
|
const phaseTasks = mockProjectTasks.filter((task) => task.phaseId === phase.id)
|
||||||
(task) => task.phaseId === phase.id
|
|
||||||
);
|
|
||||||
|
|
||||||
const phaseChildren: PsGanttTask[] = phaseTasks.map((task) => ({
|
const phaseChildren: PsGanttTask[] = phaseTasks.map((task) => ({
|
||||||
id: `task-${task.id}`,
|
id: `task-${task.id}`,
|
||||||
name: task.name,
|
name: task.name,
|
||||||
type: "task" as const,
|
type: 'task' as const,
|
||||||
startDate: task.startDate,
|
startDate: task.startDate,
|
||||||
endDate: task.endDate,
|
endDate: task.endDate,
|
||||||
progress: task.progress,
|
progress: task.progress,
|
||||||
|
|
@ -146,13 +133,13 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
children: [],
|
children: [],
|
||||||
estimatedHours: task.estimatedHours,
|
estimatedHours: task.estimatedHours,
|
||||||
hoursWorked: task.actualHours,
|
hoursWorked: task.actualHours,
|
||||||
}));
|
}))
|
||||||
|
|
||||||
if (phaseChildren.length > 0) {
|
if (phaseChildren.length > 0) {
|
||||||
projectChildren.push({
|
projectChildren.push({
|
||||||
id: `phase-${phase.id}`,
|
id: `phase-${phase.id}`,
|
||||||
name: phase.name,
|
name: phase.name,
|
||||||
type: "phase" as const,
|
type: 'phase' as const,
|
||||||
startDate: phase.startDate,
|
startDate: phase.startDate,
|
||||||
endDate: phase.endDate,
|
endDate: phase.endDate,
|
||||||
progress: phase.progress,
|
progress: phase.progress,
|
||||||
|
|
@ -160,15 +147,15 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
priority: PriorityEnum.Normal,
|
priority: PriorityEnum.Normal,
|
||||||
level: 1,
|
level: 1,
|
||||||
children: phaseChildren,
|
children: phaseChildren,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (projectChildren.length > 0) {
|
if (projectChildren.length > 0) {
|
||||||
ganttTasks.push({
|
ganttTasks.push({
|
||||||
id: `project-${project.id}`,
|
id: `project-${project.id}`,
|
||||||
name: project.name,
|
name: project.name,
|
||||||
type: "project" as const,
|
type: 'project' as const,
|
||||||
startDate: project.startDate,
|
startDate: project.startDate,
|
||||||
endDate: project.endDate,
|
endDate: project.endDate,
|
||||||
progress: project.progress,
|
progress: project.progress,
|
||||||
|
|
@ -176,265 +163,254 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
priority: project.priority,
|
priority: project.priority,
|
||||||
level: 0,
|
level: 0,
|
||||||
children: projectChildren,
|
children: projectChildren,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return ganttTasks;
|
return ganttTasks
|
||||||
};
|
}
|
||||||
|
|
||||||
const filterTasksByEmployee = (tasks: PsGanttTask[]): PsGanttTask[] => {
|
const filterTasksByEmployee = (tasks: PsGanttTask[]): PsGanttTask[] => {
|
||||||
return tasks
|
return tasks
|
||||||
.map((task) => {
|
.map((task) => {
|
||||||
if (task.type === "task" && task.assignee?.id !== selectedEmployee) {
|
if (task.type === 'task' && task.assignee?.id !== selectedEmployee) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.children) {
|
if (task.children) {
|
||||||
const filteredChildren = filterTasksByEmployee(
|
const filteredChildren = filterTasksByEmployee(task.children).filter(
|
||||||
task.children
|
Boolean,
|
||||||
).filter(Boolean) as PsGanttTask[];
|
) as PsGanttTask[]
|
||||||
if (filteredChildren.length > 0) {
|
if (filteredChildren.length > 0) {
|
||||||
return { ...task, children: filteredChildren };
|
return { ...task, children: filteredChildren }
|
||||||
} else if (task.type === "task") {
|
} else if (task.type === 'task') {
|
||||||
return task;
|
return task
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return task;
|
return task
|
||||||
})
|
})
|
||||||
.filter(Boolean) as PsGanttTask[];
|
.filter(Boolean) as PsGanttTask[]
|
||||||
};
|
}
|
||||||
|
|
||||||
const allGanttTasks = createGanttTasks();
|
const allGanttTasks = createGanttTasks()
|
||||||
return filterTasksByEmployee(allGanttTasks);
|
return filterTasksByEmployee(allGanttTasks)
|
||||||
}, [selectedEmployee]);
|
}, [selectedEmployee])
|
||||||
|
|
||||||
const toggleExpand = (id: string) => {
|
const toggleExpand = (id: string) => {
|
||||||
const newExpanded = new Set(expandedItems);
|
const newExpanded = new Set(expandedItems)
|
||||||
if (newExpanded.has(id)) {
|
if (newExpanded.has(id)) {
|
||||||
newExpanded.delete(id);
|
newExpanded.delete(id)
|
||||||
} else {
|
} else {
|
||||||
newExpanded.add(id);
|
newExpanded.add(id)
|
||||||
|
}
|
||||||
|
setExpandedItems(newExpanded)
|
||||||
}
|
}
|
||||||
setExpandedItems(newExpanded);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Navigasyon fonksiyonları
|
// Navigasyon fonksiyonları
|
||||||
const navigatePrevious = () => {
|
const navigatePrevious = () => {
|
||||||
const newDate = new Date(currentDate);
|
const newDate = new Date(currentDate)
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
newDate.setDate(newDate.getDate() - 1);
|
newDate.setDate(newDate.getDate() - 1)
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
newDate.setDate(newDate.getDate() - 7);
|
newDate.setDate(newDate.getDate() - 7)
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
newDate.setMonth(newDate.getMonth() - 1);
|
newDate.setMonth(newDate.getMonth() - 1)
|
||||||
} else {
|
} else {
|
||||||
newDate.setFullYear(newDate.getFullYear() - 1);
|
newDate.setFullYear(newDate.getFullYear() - 1)
|
||||||
|
}
|
||||||
|
setCurrentDate(newDate)
|
||||||
}
|
}
|
||||||
setCurrentDate(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateNext = () => {
|
const navigateNext = () => {
|
||||||
const newDate = new Date(currentDate);
|
const newDate = new Date(currentDate)
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
newDate.setDate(newDate.getDate() + 1);
|
newDate.setDate(newDate.getDate() + 1)
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
newDate.setDate(newDate.getDate() + 7);
|
newDate.setDate(newDate.getDate() + 7)
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
newDate.setMonth(newDate.getMonth() + 1);
|
newDate.setMonth(newDate.getMonth() + 1)
|
||||||
} else {
|
} else {
|
||||||
newDate.setFullYear(newDate.getFullYear() + 1);
|
newDate.setFullYear(newDate.getFullYear() + 1)
|
||||||
|
}
|
||||||
|
setCurrentDate(newDate)
|
||||||
}
|
}
|
||||||
setCurrentDate(newDate);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToday = () => {
|
const navigateToday = () => {
|
||||||
setCurrentDate(new Date());
|
setCurrentDate(new Date())
|
||||||
};
|
}
|
||||||
|
|
||||||
// Mevcut tarih bilgisi gösterimi
|
// Mevcut tarih bilgisi gösterimi
|
||||||
const getCurrentDateInfo = () => {
|
const getCurrentDateInfo = () => {
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
return currentDate.toLocaleDateString("tr-TR", {
|
return currentDate.toLocaleDateString('tr-TR', {
|
||||||
weekday: "long",
|
weekday: 'long',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
month: "long",
|
month: 'long',
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
});
|
})
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
const weekStart = new Date(currentDate);
|
const weekStart = new Date(currentDate)
|
||||||
const dayOfWeek = weekStart.getDay();
|
const dayOfWeek = weekStart.getDay()
|
||||||
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1
|
||||||
weekStart.setDate(weekStart.getDate() - daysToSubtract);
|
weekStart.setDate(weekStart.getDate() - daysToSubtract)
|
||||||
|
|
||||||
const weekEnd = new Date(weekStart);
|
const weekEnd = new Date(weekStart)
|
||||||
weekEnd.setDate(weekStart.getDate() + 6);
|
weekEnd.setDate(weekStart.getDate() + 6)
|
||||||
|
|
||||||
return `${weekStart.toLocaleDateString("tr-TR", {
|
return `${weekStart.toLocaleDateString('tr-TR', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short',
|
||||||
})} - ${weekEnd.toLocaleDateString("tr-TR", {
|
})} - ${weekEnd.toLocaleDateString('tr-TR', {
|
||||||
day: "numeric",
|
day: 'numeric',
|
||||||
month: "short",
|
month: 'short',
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
})}`;
|
})}`
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
return currentDate.toLocaleDateString("tr-TR", {
|
return currentDate.toLocaleDateString('tr-TR', {
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
month: "long",
|
month: 'long',
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
return currentDate.toLocaleDateString("tr-TR", {
|
return currentDate.toLocaleDateString('tr-TR', {
|
||||||
year: "numeric",
|
year: 'numeric',
|
||||||
});
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const calculateTaskPosition = (startDate: Date, endDate: Date) => {
|
const calculateTaskPosition = (startDate: Date, endDate: Date) => {
|
||||||
const chartStart = dateRange[0];
|
const chartStart = dateRange[0]
|
||||||
const chartEnd = dateRange[dateRange.length - 1];
|
const chartEnd = dateRange[dateRange.length - 1]
|
||||||
const taskStart = new Date(startDate);
|
const taskStart = new Date(startDate)
|
||||||
const taskEnd = new Date(endDate);
|
const taskEnd = new Date(endDate)
|
||||||
|
|
||||||
if (viewMode === "day") {
|
if (viewMode === 'day') {
|
||||||
// Günlük görünüm: saatlik hesaplama
|
// Günlük görünüm: saatlik hesaplama
|
||||||
const dayStart = new Date(chartStart);
|
const dayStart = new Date(chartStart)
|
||||||
dayStart.setHours(0, 0, 0, 0);
|
dayStart.setHours(0, 0, 0, 0)
|
||||||
const dayEnd = new Date(chartStart);
|
const dayEnd = new Date(chartStart)
|
||||||
dayEnd.setHours(23, 59, 59, 999);
|
dayEnd.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
// Eğer görev bu gün içinde değilse, görünürde olup olmadığını kontrol et
|
// Eğer görev bu gün içinde değilse, görünürde olup olmadığını kontrol et
|
||||||
if (taskEnd < dayStart || taskStart > dayEnd) {
|
if (taskEnd < dayStart || taskStart > dayEnd) {
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Görev başlangıcını hesapla
|
// Görev başlangıcını hesapla
|
||||||
let startHour = 0;
|
let startHour = 0
|
||||||
if (taskStart > dayStart) {
|
if (taskStart > dayStart) {
|
||||||
startHour = taskStart.getHours() + taskStart.getMinutes() / 60;
|
startHour = taskStart.getHours() + taskStart.getMinutes() / 60
|
||||||
}
|
}
|
||||||
|
|
||||||
// Görev bitişini hesapla
|
// Görev bitişini hesapla
|
||||||
let endHour = 24;
|
let endHour = 24
|
||||||
if (taskEnd < dayEnd) {
|
if (taskEnd < dayEnd) {
|
||||||
endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60;
|
endHour = taskEnd.getHours() + taskEnd.getMinutes() / 60
|
||||||
}
|
}
|
||||||
|
|
||||||
const left = (startHour / 24) * 100;
|
const left = (startHour / 24) * 100
|
||||||
const width = Math.max(1, ((endHour - startHour) / 24) * 100);
|
const width = Math.max(1, ((endHour - startHour) / 24) * 100)
|
||||||
|
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
} else if (viewMode === "week") {
|
} else if (viewMode === 'week') {
|
||||||
// Haftalık görünüm: günlük hesaplama
|
// Haftalık görünüm: günlük hesaplama
|
||||||
const weekStart = new Date(chartStart);
|
const weekStart = new Date(chartStart)
|
||||||
const weekEnd = new Date(chartEnd);
|
const weekEnd = new Date(chartEnd)
|
||||||
weekEnd.setHours(23, 59, 59, 999);
|
weekEnd.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
// Görev hafta dışındaysa gizle
|
// Görev hafta dışındaysa gizle
|
||||||
if (taskEnd < weekStart || taskStart > weekEnd) {
|
if (taskEnd < weekStart || taskStart > weekEnd) {
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Başlangıç gününü hesapla
|
// Başlangıç gününü hesapla
|
||||||
let startDay = 0;
|
let startDay = 0
|
||||||
if (taskStart > weekStart) {
|
if (taskStart > weekStart) {
|
||||||
startDay = Math.floor(
|
startDay = Math.floor((taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskStart.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitiş gününü hesapla
|
// Bitiş gününü hesapla
|
||||||
let endDay = 7;
|
let endDay = 7
|
||||||
if (taskEnd < weekEnd) {
|
if (taskEnd < weekEnd) {
|
||||||
endDay = Math.ceil(
|
endDay = Math.ceil((taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskEnd.getTime() - weekStart.getTime()) / (1000 * 60 * 60 * 24)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const left = (startDay / 7) * 100;
|
const left = (startDay / 7) * 100
|
||||||
const width = Math.max(1, ((endDay - startDay) / 7) * 100);
|
const width = Math.max(1, ((endDay - startDay) / 7) * 100)
|
||||||
|
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
} else if (viewMode === "month") {
|
} else if (viewMode === 'month') {
|
||||||
// Aylık görünüm: günlük hesaplama
|
// Aylık görünüm: günlük hesaplama
|
||||||
const monthStart = new Date(chartStart);
|
const monthStart = new Date(chartStart)
|
||||||
const monthEnd = new Date(chartEnd);
|
const monthEnd = new Date(chartEnd)
|
||||||
monthEnd.setHours(23, 59, 59, 999);
|
monthEnd.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
// Görev ay dışındaysa gizle
|
// Görev ay dışındaysa gizle
|
||||||
if (taskEnd < monthStart || taskStart > monthEnd) {
|
if (taskEnd < monthStart || taskStart > monthEnd) {
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const daysInMonth = dateRange.length;
|
const daysInMonth = dateRange.length
|
||||||
|
|
||||||
// Başlangıç gününü hesapla
|
// Başlangıç gününü hesapla
|
||||||
let startDay = 0;
|
let startDay = 0
|
||||||
if (taskStart > monthStart) {
|
if (taskStart > monthStart) {
|
||||||
startDay = Math.floor(
|
startDay = Math.floor((taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskStart.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitiş gününü hesapla
|
// Bitiş gününü hesapla
|
||||||
let endDay = daysInMonth;
|
let endDay = daysInMonth
|
||||||
if (taskEnd < monthEnd) {
|
if (taskEnd < monthEnd) {
|
||||||
endDay = Math.ceil(
|
endDay = Math.ceil((taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
(taskEnd.getTime() - monthStart.getTime()) / (1000 * 60 * 60 * 24)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const left = (startDay / daysInMonth) * 100;
|
const left = (startDay / daysInMonth) * 100
|
||||||
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100);
|
const width = Math.max(1, ((endDay - startDay) / daysInMonth) * 100)
|
||||||
|
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
} else {
|
} else {
|
||||||
// Yıllık görünüm: aylık hesaplama
|
// Yıllık görünüm: aylık hesaplama
|
||||||
const yearStart = new Date(chartStart);
|
const yearStart = new Date(chartStart)
|
||||||
const yearEnd = new Date(chartEnd);
|
const yearEnd = new Date(chartEnd)
|
||||||
yearEnd.setMonth(11, 31);
|
yearEnd.setMonth(11, 31)
|
||||||
yearEnd.setHours(23, 59, 59, 999);
|
yearEnd.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
// Görev yıl dışındaysa gizle
|
// Görev yıl dışındaysa gizle
|
||||||
if (taskEnd < yearStart || taskStart > yearEnd) {
|
if (taskEnd < yearStart || taskStart > yearEnd) {
|
||||||
return { left: "100%", width: "0%", isVisible: false };
|
return { left: '100%', width: '0%', isVisible: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Başlangıç ayını hesapla
|
// Başlangıç ayını hesapla
|
||||||
let startMonth = 0;
|
let startMonth = 0
|
||||||
if (taskStart > yearStart) {
|
if (taskStart > yearStart) {
|
||||||
startMonth = taskStart.getMonth();
|
startMonth = taskStart.getMonth()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitiş ayını hesapla
|
// Bitiş ayını hesapla
|
||||||
let endMonth = 12;
|
let endMonth = 12
|
||||||
if (taskEnd < yearEnd) {
|
if (taskEnd < yearEnd) {
|
||||||
endMonth = taskEnd.getMonth() + 1;
|
endMonth = taskEnd.getMonth() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const left = (startMonth / 12) * 100;
|
const left = (startMonth / 12) * 100
|
||||||
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100);
|
const width = Math.max(1, ((endMonth - startMonth) / 12) * 100)
|
||||||
|
|
||||||
return { left: `${left}%`, width: `${width}%`, isVisible: true };
|
return { left: `${left}%`, width: `${width}%`, isVisible: true }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const renderTask = (task: PsGanttTask): React.ReactNode => {
|
const renderTask = (task: PsGanttTask): React.ReactNode => {
|
||||||
const hasChildren = task.children && task.children.length > 0;
|
const hasChildren = task.children && task.children.length > 0
|
||||||
const isExpanded = expandedItems.has(task.id);
|
const isExpanded = expandedItems.has(task.id)
|
||||||
const indent = task.level * 20;
|
const indent = task.level * 20
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={task.id}>
|
<React.Fragment key={task.id}>
|
||||||
<div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[32px] sm:min-h-[36px]">
|
<div className="flex items-center border-b border-gray-100 hover:bg-gray-50 min-h-[32px] sm:min-h-[36px]">
|
||||||
{/* Task Info */}
|
{/* Task Info */}
|
||||||
<div className="w-1/3 sm:w-1/4 p-0.5 border-r border-gray-200">
|
<div className="w-1/3 sm:w-1/4 p-0.5 border-r border-gray-200">
|
||||||
<div
|
<div className="flex items-center" style={{ paddingLeft: `${Math.min(indent, 40)}px` }}>
|
||||||
className="flex items-center"
|
|
||||||
style={{ paddingLeft: `${Math.min(indent, 40)}px` }}
|
|
||||||
>
|
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleExpand(task.id)}
|
onClick={() => toggleExpand(task.id)}
|
||||||
|
|
@ -453,11 +429,11 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<span
|
<span
|
||||||
className={`font-medium text-xs sm:text-sm truncate ${
|
className={`font-medium text-xs sm:text-sm truncate ${
|
||||||
task.type === "project"
|
task.type === 'project'
|
||||||
? "text-blue-900 font-semibold"
|
? 'text-blue-900 font-semibold'
|
||||||
: task.type === "phase"
|
: task.type === 'phase'
|
||||||
? "text-blue-700"
|
? 'text-blue-700'
|
||||||
: "text-gray-900"
|
: 'text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
title={task.name}
|
title={task.name}
|
||||||
>
|
>
|
||||||
|
|
@ -465,14 +441,14 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`px-1 py-0.5 text-xs border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
|
className={`px-1 py-0.5 text-xs border rounded flex-shrink-0 hidden sm:inline ${getPriorityColor(
|
||||||
task.priority
|
task.priority,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{task.priority}
|
{task.priority}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.type === "task" && task.assignee && (
|
{task.type === 'task' && task.assignee && (
|
||||||
<div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600">
|
<div className="flex items-center gap-1 mt-0.5 text-xs text-gray-600">
|
||||||
<FaUser className="h-2 w-2 sm:h-2.5 sm:w-2.5 flex-shrink-0" />
|
<FaUser className="h-2 w-2 sm:h-2.5 sm:w-2.5 flex-shrink-0" />
|
||||||
<span className="truncate text-xs">
|
<span className="truncate text-xs">
|
||||||
|
|
@ -487,8 +463,8 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-1 text-xs text-gray-500 truncate hidden sm:block">
|
<div className="mt-1 text-xs text-gray-500 truncate hidden sm:block">
|
||||||
{task.startDate.toLocaleDateString("tr-TR")} -{" "}
|
{task.startDate.toLocaleDateString('tr-TR')} -{' '}
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
{task.endDate.toLocaleDateString('tr-TR')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -498,16 +474,13 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
<div className="w-2/3 sm:w-3/4 p-0.5 relative">
|
<div className="w-2/3 sm:w-3/4 p-0.5 relative">
|
||||||
<div className="relative h-5 sm:h-7 bg-gray-200 rounded">
|
<div className="relative h-5 sm:h-7 bg-gray-200 rounded">
|
||||||
{(() => {
|
{(() => {
|
||||||
const position = calculateTaskPosition(
|
const position = calculateTaskPosition(task.startDate, task.endDate)
|
||||||
task.startDate,
|
|
||||||
task.endDate
|
|
||||||
);
|
|
||||||
|
|
||||||
if (task.type === "task" && position.isVisible) {
|
if (task.type === 'task' && position.isVisible) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute h-4 sm:h-5 mt-0.5 rounded-sm ${getProjectStatusColor(
|
className={`absolute h-4 sm:h-5 mt-0.5 rounded-sm ${getProjectStatusColor(
|
||||||
task.status
|
task.status,
|
||||||
)} shadow-md group cursor-pointer border border-white border-opacity-20`}
|
)} shadow-md group cursor-pointer border border-white border-opacity-20`}
|
||||||
style={{ left: position.left, width: position.width }}
|
style={{ left: position.left, width: position.width }}
|
||||||
title={`${task.name} - ${task.progress}% tamamlandı`}
|
title={`${task.name} - ${task.progress}% tamamlandı`}
|
||||||
|
|
@ -528,24 +501,16 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
{/* Tooltip */}
|
{/* Tooltip */}
|
||||||
<div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
<div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
||||||
<div className="font-semibold">{task.name}</div>
|
<div className="font-semibold">{task.name}</div>
|
||||||
<div className="mt-1">
|
<div className="mt-1">İlerleme: {task.progress}% tamamlandı</div>
|
||||||
İlerleme: {task.progress}% tamamlandı
|
<div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
|
||||||
</div>
|
<div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
|
||||||
<div>
|
|
||||||
Planlanan Başlangıç:{" "}
|
|
||||||
{task.startDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Planlanan Bitiş:{" "}
|
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.type !== "task" && position.isVisible) {
|
if (task.type !== 'task' && position.isVisible) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="absolute h-2 sm:h-2.5 mt-1.5 sm:mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30"
|
className="absolute h-2 sm:h-2.5 mt-1.5 sm:mt-2 bg-gradient-to-r from-gray-400 to-gray-600 rounded-full group cursor-pointer shadow-sm border border-white border-opacity-30"
|
||||||
|
|
@ -555,48 +520,37 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
{/* Tooltip for non-task items - no progress */}
|
{/* Tooltip for non-task items - no progress */}
|
||||||
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white text-xs rounded py-2 px-3 opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-20 hidden sm:block">
|
||||||
<div className="font-semibold">{task.name}</div>
|
<div className="font-semibold">{task.name}</div>
|
||||||
<div>
|
<div>Planlanan Başlangıç: {task.startDate.toLocaleDateString('tr-TR')}</div>
|
||||||
Planlanan Başlangıç:{" "}
|
<div>Planlanan Bitiş: {task.endDate.toLocaleDateString('tr-TR')}</div>
|
||||||
{task.startDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Planlanan Bitiş:{" "}
|
|
||||||
{task.endDate.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-800"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Render children if expanded */}
|
{/* Render children if expanded */}
|
||||||
{hasChildren &&
|
{hasChildren && isExpanded && task.children?.map((child) => renderTask(child))}
|
||||||
isExpanded &&
|
|
||||||
task.children?.map((child) => renderTask(child))}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Container>
|
||||||
<div className="space-y-3 py-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-gray-900">
|
<h2 className="text-xl font-bold text-gray-900">İş Yükü ve Proje Takibi</h2>
|
||||||
İş Yükü ve Proje Takibi
|
<p className="text-gray-600 mt-1">
|
||||||
</h2>
|
|
||||||
<p className="text-sm sm:text-base text-gray-600 mt-1">
|
|
||||||
Tüm görevlerinizi merkezi bir noktadan yönetin.
|
Tüm görevlerinizi merkezi bir noktadan yönetin.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow-sm border h-full flex flex-col">
|
<div className="bg-white rounded-lg shadow-sm border h-full flex flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -645,11 +599,7 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={viewMode}
|
value={viewMode}
|
||||||
onChange={(e) =>
|
onChange={(e) => setViewMode(e.target.value as 'day' | 'week' | 'month' | 'year')}
|
||||||
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"
|
className="px-2 py-1.5 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<option value="day">Günlük</option>
|
<option value="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="w-2/3 sm:w-3/4 p-1.5 bg-gray-50">
|
||||||
<div className="flex justify-between text-xs font-semibold">
|
<div className="flex justify-between text-xs font-semibold">
|
||||||
{dateRange.map((date) => (
|
{dateRange.map((date) => (
|
||||||
<div
|
<div key={date.toISOString()} className="text-center min-w-0 flex-1">
|
||||||
key={date.toISOString()}
|
|
||||||
className="text-center min-w-0 flex-1"
|
|
||||||
>
|
|
||||||
<div className="truncate">
|
<div className="truncate">
|
||||||
{viewMode === "day"
|
{viewMode === 'day'
|
||||||
? date.toLocaleTimeString("tr-TR", {
|
? date.toLocaleTimeString('tr-TR', {
|
||||||
hour: "2-digit",
|
hour: '2-digit',
|
||||||
minute: "2-digit",
|
minute: '2-digit',
|
||||||
})
|
})
|
||||||
: viewMode === "week"
|
: viewMode === 'week'
|
||||||
? date.toLocaleDateString("tr-TR", { day: "2-digit" })
|
? date.toLocaleDateString('tr-TR', { day: '2-digit' })
|
||||||
: viewMode === "year"
|
: viewMode === 'year'
|
||||||
? date.toLocaleDateString("tr-TR", { month: "short" })
|
? date.toLocaleDateString('tr-TR', { month: 'short' })
|
||||||
: date.toLocaleDateString("tr-TR", { day: "2-digit" })}
|
: date.toLocaleDateString('tr-TR', { day: '2-digit' })}
|
||||||
</div>
|
</div>
|
||||||
{viewMode !== "day" && viewMode !== "year" && (
|
{viewMode !== 'day' && viewMode !== 'year' && (
|
||||||
<div className="text-xs text-gray-500 hidden sm:block">
|
<div className="text-xs text-gray-500 hidden sm:block">
|
||||||
{date.toLocaleDateString("tr-TR", { weekday: "short" })}
|
{date.toLocaleDateString('tr-TR', { weekday: 'short' })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{viewMode === "year" && (
|
{viewMode === 'year' && (
|
||||||
<div className="text-xs text-gray-500 hidden sm:block">
|
<div className="text-xs text-gray-500 hidden sm:block">
|
||||||
{date.getFullYear()}
|
{date.getFullYear()}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -706,8 +653,9 @@ const ProjectGantt: React.FC<ProjectGanttProps> = ({ employeeId }) => {
|
||||||
{filteredTasks.map((task) => renderTask(task))}
|
{filteredTasks.map((task) => renderTask(task))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ProjectGantt;
|
export default ProjectGantt
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom'
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
FaFolder,
|
FaFolder,
|
||||||
FaPlus,
|
FaPlus,
|
||||||
|
|
@ -18,15 +18,15 @@ import {
|
||||||
FaList,
|
FaList,
|
||||||
FaFlag,
|
FaFlag,
|
||||||
FaTasks,
|
FaTasks,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { ProjectStatusEnum, PsProject } from "../../../types/ps";
|
import { ProjectStatusEnum, PsProject } from '../../../types/ps'
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs'
|
||||||
import { mockProjects } from "../../../mocks/mockProjects";
|
import { mockProjects } from '../../../mocks/mockProjects'
|
||||||
import PhaseViewModal from "./PhaseViewModal";
|
import PhaseViewModal from './PhaseViewModal'
|
||||||
import TaskViewModal from "./TaskViewModal";
|
import TaskViewModal from './TaskViewModal'
|
||||||
import Widget from "../../../components/common/Widget";
|
import Widget from '../../../components/common/Widget'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
import {
|
import {
|
||||||
getProjectStatusColor,
|
getProjectStatusColor,
|
||||||
getProjectStatusIcon,
|
getProjectStatusIcon,
|
||||||
|
|
@ -34,59 +34,56 @@ import {
|
||||||
getPriorityColor,
|
getPriorityColor,
|
||||||
getPriorityText,
|
getPriorityText,
|
||||||
getProgressColor,
|
getProgressColor,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const ProjectList: React.FC = () => {
|
const ProjectList: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [filterStatus, setFilterStatus] = useState("all");
|
const [filterStatus, setFilterStatus] = useState('all')
|
||||||
const [filterPriority, setFilterPriority] = useState("all");
|
const [filterPriority, setFilterPriority] = useState('all')
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
const [viewMode, setViewMode] = useState<"card" | "list">("list");
|
const [viewMode, setViewMode] = useState<'card' | 'list'>('list')
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
const [phasesModalOpen, setPhasesModalOpen] = useState(false);
|
const [phasesModalOpen, setPhasesModalOpen] = useState(false)
|
||||||
const [tasksModalOpen, setTasksModalOpen] = useState(false);
|
const [tasksModalOpen, setTasksModalOpen] = useState(false)
|
||||||
const [selectedProject, setSelectedProject] = useState<PsProject | null>(
|
const [selectedProject, setSelectedProject] = useState<PsProject | null>(null)
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Modal functions
|
// Modal functions
|
||||||
const openPhasesModal = (project: PsProject) => {
|
const openPhasesModal = (project: PsProject) => {
|
||||||
setSelectedProject(project);
|
setSelectedProject(project)
|
||||||
setPhasesModalOpen(true);
|
setPhasesModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const openTasksModal = (project: PsProject) => {
|
const openTasksModal = (project: PsProject) => {
|
||||||
setSelectedProject(project);
|
setSelectedProject(project)
|
||||||
setTasksModalOpen(true);
|
setTasksModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeModals = () => {
|
const closeModals = () => {
|
||||||
setPhasesModalOpen(false);
|
setPhasesModalOpen(false)
|
||||||
setTasksModalOpen(false);
|
setTasksModalOpen(false)
|
||||||
setSelectedProject(null);
|
setSelectedProject(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: projects,
|
data: projects,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["projects", searchTerm, filterStatus, filterPriority],
|
queryKey: ['projects', searchTerm, filterStatus, filterPriority],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
return mockProjects.filter((project) => {
|
return mockProjects.filter((project) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
project.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
project.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
project.name.toLowerCase().includes(searchTerm.toLowerCase());
|
project.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
const matchesStatus =
|
const matchesStatus = filterStatus === 'all' || project.status === filterStatus
|
||||||
filterStatus === "all" || project.status === filterStatus;
|
const matchesPriority = filterPriority === 'all' || project.priority === filterPriority
|
||||||
const matchesPriority =
|
return matchesSearch && matchesStatus && matchesPriority
|
||||||
filterPriority === "all" || project.priority === filterPriority;
|
})
|
||||||
return matchesSearch && matchesStatus && matchesPriority;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
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>
|
<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>
|
<span className="ml-3 text-gray-600">Projeler yükleniyor...</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
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="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
<FaExclamationTriangle className="h-5 w-5 text-red-600 mr-2" />
|
||||||
<span className="text-red-800">
|
<span className="text-red-800">Projeler yüklenirken hata oluştu.</span>
|
||||||
Projeler yüklenirken hata oluştu.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 pt-2">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-gray-900">Proje Listesi</h2>
|
<h2 className="text-xl font-bold text-gray-900">Proje Listesi</h2>
|
||||||
|
|
@ -121,21 +117,21 @@ const ProjectList: React.FC = () => {
|
||||||
{/* View Toggle */}
|
{/* View Toggle */}
|
||||||
<div className="flex bg-gray-100 rounded-lg">
|
<div className="flex bg-gray-100 rounded-lg">
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode("card")}
|
onClick={() => setViewMode('card')}
|
||||||
className={`px-2.5 py-1.5 rounded-md flex items-center space-x-2 transition-colors ${
|
className={`px-2.5 py-1.5 rounded-md flex items-center space-x-2 transition-colors ${
|
||||||
viewMode === "card"
|
viewMode === 'card'
|
||||||
? "bg-white text-blue-600 shadow-sm"
|
? 'bg-white text-blue-600 shadow-sm'
|
||||||
: "text-gray-600 hover:text-gray-900"
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaTh className="w-4 h-4" />
|
<FaTh className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<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 ${
|
className={`px-2.5 py-1.5 rounded-md flex items-center space-x-2 transition-colors ${
|
||||||
viewMode === "list"
|
viewMode === 'list'
|
||||||
? "bg-white text-blue-600 shadow-sm"
|
? 'bg-white text-blue-600 shadow-sm'
|
||||||
: "text-gray-600 hover:text-gray-900"
|
: 'text-gray-600 hover:text-gray-900'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FaList className="w-4 h-4" />
|
<FaList className="w-4 h-4" />
|
||||||
|
|
@ -143,7 +139,7 @@ const ProjectList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<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"
|
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" />
|
<FaDownload size={16} className="mr-2" />
|
||||||
|
|
@ -180,10 +176,10 @@ const ProjectList: React.FC = () => {
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters(!showFilters)}
|
onClick={() => setShowFilters(!showFilters)}
|
||||||
className={classNames(
|
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
|
showFilters
|
||||||
? "border-blue-500 bg-blue-50 text-blue-700"
|
? 'border-blue-500 bg-blue-50 text-blue-700'
|
||||||
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
|
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FaFilter size={16} className="mr-2" />
|
<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="bg-white border border-gray-200 rounded-lg p-3">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">Durum</label>
|
||||||
Durum
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={filterStatus}
|
value={filterStatus}
|
||||||
onChange={(e) => setFilterStatus(e.target.value)}
|
onChange={(e) => setFilterStatus(e.target.value)}
|
||||||
|
|
@ -210,16 +204,12 @@ const ProjectList: React.FC = () => {
|
||||||
<option value={ProjectStatusEnum.Active}>Aktif</option>
|
<option value={ProjectStatusEnum.Active}>Aktif</option>
|
||||||
<option value={ProjectStatusEnum.OnHold}>Beklemede</option>
|
<option value={ProjectStatusEnum.OnHold}>Beklemede</option>
|
||||||
<option value={ProjectStatusEnum.Completed}>Tamamlandı</option>
|
<option value={ProjectStatusEnum.Completed}>Tamamlandı</option>
|
||||||
<option value={ProjectStatusEnum.Cancelled}>
|
<option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
İptal Edildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">Öncelik</label>
|
||||||
Öncelik
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={filterPriority}
|
value={filterPriority}
|
||||||
onChange={(e) => setFilterPriority(e.target.value)}
|
onChange={(e) => setFilterPriority(e.target.value)}
|
||||||
|
|
@ -236,9 +226,9 @@ const ProjectList: React.FC = () => {
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFilterStatus("all");
|
setFilterStatus('all')
|
||||||
setFilterPriority("all");
|
setFilterPriority('all')
|
||||||
setSearchTerm("");
|
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"
|
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 */}
|
{/* Statistics Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<Widget
|
<Widget title="Toplam Proje" value={projects?.length || 0} color="blue" icon="FaFolder" />
|
||||||
title="Toplam Proje"
|
|
||||||
value={projects?.length || 0}
|
|
||||||
color="blue"
|
|
||||||
icon="FaFolder"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Aktif Proje"
|
title="Aktif Proje"
|
||||||
value={
|
value={projects?.filter((p) => p.status === ProjectStatusEnum.Active).length || 0}
|
||||||
projects?.filter((p) => p.status === ProjectStatusEnum.Active)
|
|
||||||
.length || 0
|
|
||||||
}
|
|
||||||
color="green"
|
color="green"
|
||||||
icon="FaArrowUp"
|
icon="FaArrowUp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Toplam Bütçe"
|
title="Toplam Bütçe"
|
||||||
value={`₺${
|
value={`₺${projects?.reduce((acc, p) => acc + p.budget, 0).toLocaleString() || 0}`}
|
||||||
projects?.reduce((acc, p) => acc + p.budget, 0).toLocaleString() ||
|
|
||||||
0
|
|
||||||
}`}
|
|
||||||
color="purple"
|
color="purple"
|
||||||
icon="FaDollarSign"
|
icon="FaDollarSign"
|
||||||
/>
|
/>
|
||||||
|
|
@ -283,10 +262,9 @@ const ProjectList: React.FC = () => {
|
||||||
value={
|
value={
|
||||||
projects?.length
|
projects?.length
|
||||||
? `${Math.round(
|
? `${Math.round(
|
||||||
projects.reduce((acc, p) => acc + p.progress, 0) /
|
projects.reduce((acc, p) => acc + p.progress, 0) / projects.length,
|
||||||
projects.length
|
|
||||||
)}%`
|
)}%`
|
||||||
: "0%"
|
: '0%'
|
||||||
}
|
}
|
||||||
color="yellow"
|
color="yellow"
|
||||||
icon="FaBullseye"
|
icon="FaBullseye"
|
||||||
|
|
@ -294,7 +272,7 @@ const ProjectList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Projects Display */}
|
{/* Projects Display */}
|
||||||
{viewMode === "list" ? (
|
{viewMode === 'list' ? (
|
||||||
/* List View */
|
/* List View */
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||||
<div className="px-3 py-2 border-b 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">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{projects?.map((project) => (
|
{projects?.map((project) => (
|
||||||
<tr
|
<tr key={project.id} className="hover:bg-gray-50 transition-colors text-sm">
|
||||||
key={project.id}
|
|
||||||
className="hover:bg-gray-50 transition-colors text-sm"
|
|
||||||
>
|
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0 h-10 w-10">
|
<div className="flex-shrink-0 h-10 w-10">
|
||||||
|
|
@ -343,9 +318,7 @@ const ProjectList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<div className="text-sm font-medium text-gray-900">
|
<div className="text-sm font-medium text-gray-900">{project.code}</div>
|
||||||
{project.code}
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-gray-500 max-w-xs truncate">
|
<div className="text-sm text-gray-500 max-w-xs truncate">
|
||||||
{project.name}
|
{project.name}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -368,19 +341,17 @@ const ProjectList: React.FC = () => {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium",
|
'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium',
|
||||||
getProjectStatusColor(project.status)
|
getProjectStatusColor(project.status),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getProjectStatusIcon(project.status)}
|
{getProjectStatusIcon(project.status)}
|
||||||
<span className="ml-1">
|
<span className="ml-1">{getProjectStatusText(project.status)}</span>
|
||||||
{getProjectStatusText(project.status)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-sm font-medium",
|
'text-sm font-medium',
|
||||||
getPriorityColor(project.priority)
|
getPriorityColor(project.priority),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getPriorityText(project.priority)}
|
{getPriorityText(project.priority)}
|
||||||
|
|
@ -392,10 +363,10 @@ const ProjectList: React.FC = () => {
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center text-sm text-gray-900">
|
<div className="flex items-center text-sm text-gray-900">
|
||||||
<FaCalendar size={14} className="mr-1" />
|
<FaCalendar size={14} className="mr-1" />
|
||||||
{dayjs(project.startDate).format("DD.MM.YYYY")}
|
{dayjs(project.startDate).format('DD.MM.YYYY')}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -409,11 +380,7 @@ const ProjectList: React.FC = () => {
|
||||||
Harcanan: ₺{project.actualCost.toLocaleString()}
|
Harcanan: ₺{project.actualCost.toLocaleString()}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-blue-600">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -428,8 +395,8 @@ const ProjectList: React.FC = () => {
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"h-2 rounded-full",
|
'h-2 rounded-full',
|
||||||
getProgressColor(project.progress)
|
getProgressColor(project.progress),
|
||||||
)}
|
)}
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%` }}
|
||||||
/>
|
/>
|
||||||
|
|
@ -490,27 +457,23 @@ const ProjectList: React.FC = () => {
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
<FaFolder className="w-5 h-5 text-blue-600" />
|
<FaFolder className="w-5 h-5 text-blue-600" />
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">{project.code}</h3>
|
||||||
{project.code}
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mb-2">{project.name}</p>
|
<p className="text-sm text-gray-600 mb-2">{project.name}</p>
|
||||||
<div className="flex items-center space-x-2 mb-3">
|
<div className="flex items-center space-x-2 mb-3">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium",
|
'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium',
|
||||||
getProjectStatusColor(project.status)
|
getProjectStatusColor(project.status),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getProjectStatusIcon(project.status)}
|
{getProjectStatusIcon(project.status)}
|
||||||
<span className="ml-1">
|
<span className="ml-1">{getProjectStatusText(project.status)}</span>
|
||||||
{getProjectStatusText(project.status)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"px-2 py-0.5 rounded-full text-xs font-medium",
|
'px-2 py-0.5 rounded-full text-xs font-medium',
|
||||||
getPriorityColor(project.priority)
|
getPriorityColor(project.priority),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getPriorityText(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="bg-gray-50 rounded-lg p-2 mb-3">
|
||||||
<div className="flex items-center space-x-2 mb-1">
|
<div className="flex items-center space-x-2 mb-1">
|
||||||
<FaUser className="w-4 h-4 text-gray-600" />
|
<FaUser className="w-4 h-4 text-gray-600" />
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">Proje Yöneticisi</span>
|
||||||
Proje Yöneticisi
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700">{project.projectManager?.fullName}</p>
|
||||||
{project.projectManager?.fullName}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress */}
|
||||||
|
|
@ -571,10 +530,7 @@ const ProjectList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames('h-2 rounded-full', getProgressColor(project.progress))}
|
||||||
"h-2 rounded-full",
|
|
||||||
getProgressColor(project.progress)
|
|
||||||
)}
|
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -589,13 +545,13 @@ const ProjectList: React.FC = () => {
|
||||||
Başlangıç
|
Başlangıç
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{dayjs(project.startDate).format("DD.MM.YYYY")}
|
{dayjs(project.startDate).format('DD.MM.YYYY')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-gray-500">Bitiş</span>
|
<span className="text-gray-500">Bitiş</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{dayjs(project.endDate).format("DD.MM.YYYY")}
|
{dayjs(project.endDate).format('DD.MM.YYYY')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -606,15 +562,11 @@ const ProjectList: React.FC = () => {
|
||||||
<FaDollarSign className="w-4 h-4 mr-1" />
|
<FaDollarSign className="w-4 h-4 mr-1" />
|
||||||
Bütçe
|
Bütçe
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">₺{project.budget.toLocaleString()}</span>
|
||||||
₺{project.budget.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-gray-500">Harcanan</span>
|
<span className="text-gray-500">Harcanan</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">₺{project.actualCost.toLocaleString()}</span>
|
||||||
₺{project.actualCost.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -622,9 +574,7 @@ const ProjectList: React.FC = () => {
|
||||||
{/* Budget Usage */}
|
{/* Budget Usage */}
|
||||||
<div className="bg-blue-50 rounded-lg p-2">
|
<div className="bg-blue-50 rounded-lg p-2">
|
||||||
<div className="flex items-center justify-between text-sm">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<span className="text-blue-700 font-medium">
|
<span className="text-blue-700 font-medium">Bütçe Kullanımı</span>
|
||||||
Bütçe Kullanımı
|
|
||||||
</span>
|
|
||||||
<span className="text-blue-600 font-bold">
|
<span className="text-blue-600 font-bold">
|
||||||
%{Math.round((project.actualCost / project.budget) * 100)}
|
%{Math.round((project.actualCost / project.budget) * 100)}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -639,12 +589,8 @@ const ProjectList: React.FC = () => {
|
||||||
{(!projects || projects.length === 0) && (
|
{(!projects || projects.length === 0) && (
|
||||||
<div className="text-center py-10">
|
<div className="text-center py-10">
|
||||||
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
|
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Proje bulunamadı</h3>
|
||||||
Proje bulunamadı
|
<p className="mt-1 text-sm text-gray-500">Yeni proje oluşturarak başlayın.</p>
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
|
||||||
Yeni proje oluşturarak başlayın.
|
|
||||||
</p>
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link
|
<Link
|
||||||
to="/admin/projects/new"
|
to="/admin/projects/new"
|
||||||
|
|
@ -656,6 +602,7 @@ const ProjectList: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
{selectedProject && (
|
{selectedProject && (
|
||||||
|
|
@ -665,15 +612,11 @@ const ProjectList: React.FC = () => {
|
||||||
isOpen={phasesModalOpen}
|
isOpen={phasesModalOpen}
|
||||||
onClose={closeModals}
|
onClose={closeModals}
|
||||||
/>
|
/>
|
||||||
<TaskViewModal
|
<TaskViewModal project={selectedProject} isOpen={tasksModalOpen} onClose={closeModals} />
|
||||||
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 {
|
import {
|
||||||
FaPlus,
|
FaPlus,
|
||||||
FaSearch,
|
FaSearch,
|
||||||
|
|
@ -11,179 +11,163 @@ import {
|
||||||
FaEdit,
|
FaEdit,
|
||||||
FaSave,
|
FaSave,
|
||||||
FaProjectDiagram,
|
FaProjectDiagram,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import {
|
import { PhaseStatusEnum, PsProjectPhase, ProjectStatusEnum } from '../../../types/ps'
|
||||||
PhaseStatusEnum,
|
import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
|
||||||
PsProjectPhase,
|
import { mockProjects } from '../../../mocks/mockProjects'
|
||||||
ProjectStatusEnum,
|
import MultiSelectTeam from '../../../components/common/MultiSelectTeam'
|
||||||
} from "../../../types/ps";
|
import Widget from '../../../components/common/Widget'
|
||||||
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
|
|
||||||
import { mockProjects } from "../../../mocks/mockProjects";
|
|
||||||
import MultiSelectTeam from "../../../components/common/MultiSelectTeam";
|
|
||||||
import Widget from "../../../components/common/Widget";
|
|
||||||
import {
|
import {
|
||||||
getPhaseCategoryColor,
|
getPhaseCategoryColor,
|
||||||
getProgressColor,
|
getProgressColor,
|
||||||
getPhaseStatusIcon,
|
getPhaseStatusIcon,
|
||||||
getPhaseStatusText,
|
getPhaseStatusText,
|
||||||
getPhaseStatusColor,
|
getPhaseStatusColor,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const ProjectPhases: React.FC = () => {
|
const ProjectPhases: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState('')
|
||||||
const [selectedStatus, setSelectedStatus] = useState("All");
|
const [selectedStatus, setSelectedStatus] = useState('All')
|
||||||
const [selectedProject, setSelectedProject] = useState("All");
|
const [selectedProject, setSelectedProject] = useState('All')
|
||||||
const [selectedPhase, setSelectedPhase] = useState<PsProjectPhase | null>(
|
const [selectedPhase, setSelectedPhase] = useState<PsProjectPhase | null>(null)
|
||||||
null
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [editingPhase, setEditingPhase] = useState<PsProjectPhase | null>(null)
|
||||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
|
||||||
const [editingPhase, setEditingPhase] = useState<PsProjectPhase | null>(null);
|
|
||||||
|
|
||||||
// Form state for creating/editing phases
|
// Form state for creating/editing phases
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
phaseName: "",
|
phaseName: '',
|
||||||
description: "",
|
description: '',
|
||||||
projectId: "",
|
projectId: '',
|
||||||
status: PhaseStatusEnum.NotStarted,
|
status: PhaseStatusEnum.NotStarted,
|
||||||
startDate: "",
|
startDate: '',
|
||||||
endDate: "",
|
endDate: '',
|
||||||
budget: 0,
|
budget: 0,
|
||||||
category: "",
|
category: '',
|
||||||
assignedTeams: [] as string[],
|
assignedTeams: [] as string[],
|
||||||
});
|
})
|
||||||
|
|
||||||
const filteredPhases = mockProjectPhases.filter((phase) => {
|
const filteredPhases = mockProjectPhases.filter((phase) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
phase.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
phase.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
phase.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
phase.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
phase.description?.toLowerCase().includes(searchTerm.toLowerCase());
|
phase.description?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
const matchesStatus =
|
const matchesStatus = selectedStatus === 'All' || phase.status === selectedStatus
|
||||||
selectedStatus === "All" || phase.status === selectedStatus;
|
const matchesProject = selectedProject === 'All' || phase.projectId === selectedProject
|
||||||
const matchesProject =
|
|
||||||
selectedProject === "All" || phase.projectId === selectedProject;
|
|
||||||
|
|
||||||
return matchesSearch && matchesStatus && matchesProject;
|
return matchesSearch && matchesStatus && matchesProject
|
||||||
});
|
})
|
||||||
|
|
||||||
const calculateBudgetVariance = (budget: number, actualCost: number) => {
|
const calculateBudgetVariance = (budget: number, actualCost: number) => {
|
||||||
return ((actualCost - budget) / budget) * 100;
|
return ((actualCost - budget) / budget) * 100
|
||||||
};
|
}
|
||||||
|
|
||||||
const calculateScheduleVariance = (phase: PsProjectPhase) => {
|
const calculateScheduleVariance = (phase: PsProjectPhase) => {
|
||||||
const plannedDuration =
|
const plannedDuration = new Date(phase.endDate).getTime() - new Date(phase.startDate).getTime()
|
||||||
new Date(phase.endDate).getTime() - new Date(phase.startDate).getTime();
|
|
||||||
const actualDuration = phase.actualEndDate
|
const actualDuration = phase.actualEndDate
|
||||||
? new Date(phase.actualEndDate).getTime() -
|
? new Date(phase.actualEndDate).getTime() -
|
||||||
new Date(phase.actualStartDate || phase.startDate).getTime()
|
new Date(phase.actualStartDate || phase.startDate).getTime()
|
||||||
: Date.now() -
|
: Date.now() - new Date(phase.actualStartDate || phase.startDate).getTime()
|
||||||
new Date(phase.actualStartDate || phase.startDate).getTime();
|
|
||||||
|
|
||||||
return ((actualDuration - plannedDuration) / plannedDuration) * 100;
|
return ((actualDuration - plannedDuration) / plannedDuration) * 100
|
||||||
};
|
}
|
||||||
|
|
||||||
const openModal = (phase: PsProjectPhase) => {
|
const openModal = (phase: PsProjectPhase) => {
|
||||||
setSelectedPhase(phase);
|
setSelectedPhase(phase)
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setSelectedPhase(null);
|
setSelectedPhase(null)
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
const openCreateModal = () => {
|
const openCreateModal = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
phaseName: "",
|
phaseName: '',
|
||||||
description: "",
|
description: '',
|
||||||
projectId: "",
|
projectId: '',
|
||||||
status: PhaseStatusEnum.NotStarted,
|
status: PhaseStatusEnum.NotStarted,
|
||||||
startDate: "",
|
startDate: '',
|
||||||
endDate: "",
|
endDate: '',
|
||||||
budget: 0,
|
budget: 0,
|
||||||
category: "",
|
category: '',
|
||||||
assignedTeams: [],
|
assignedTeams: [],
|
||||||
});
|
})
|
||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeCreateModal = () => {
|
const closeCreateModal = () => {
|
||||||
setIsCreateModalOpen(false);
|
setIsCreateModalOpen(false)
|
||||||
};
|
}
|
||||||
|
|
||||||
const openEditModal = (phase: PsProjectPhase) => {
|
const openEditModal = (phase: PsProjectPhase) => {
|
||||||
setEditingPhase(phase);
|
setEditingPhase(phase)
|
||||||
setFormData({
|
setFormData({
|
||||||
phaseName: phase.name,
|
phaseName: phase.name,
|
||||||
description: phase.description || "",
|
description: phase.description || '',
|
||||||
projectId: phase.projectId,
|
projectId: phase.projectId,
|
||||||
status: phase.status,
|
status: phase.status,
|
||||||
startDate: phase.startDate.toISOString().split("T")[0],
|
startDate: phase.startDate.toISOString().split('T')[0],
|
||||||
endDate: phase.endDate.toISOString().split("T")[0],
|
endDate: phase.endDate.toISOString().split('T')[0],
|
||||||
budget: phase.budget,
|
budget: phase.budget,
|
||||||
category: phase.category,
|
category: phase.category,
|
||||||
assignedTeams: phase.assignedTeams || [],
|
assignedTeams: phase.assignedTeams || [],
|
||||||
});
|
})
|
||||||
setIsEditModalOpen(true);
|
setIsEditModalOpen(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const closeEditModal = () => {
|
const closeEditModal = () => {
|
||||||
setIsEditModalOpen(false);
|
setIsEditModalOpen(false)
|
||||||
setEditingPhase(null);
|
setEditingPhase(null)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
||||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]: value,
|
[name]: value,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleTeamChange = (teams: string[]) => {
|
const handleTeamChange = (teams: string[]) => {
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
assignedTeams: teams,
|
assignedTeams: teams,
|
||||||
}));
|
}))
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
// Here you would normally save to backend
|
// Here you would normally save to backend
|
||||||
console.log("Saving phase:", formData);
|
console.log('Saving phase:', formData)
|
||||||
alert(isEditModalOpen ? "Aşama güncellendi!" : "Yeni aşama oluşturuldu!");
|
alert(isEditModalOpen ? 'Aşama güncellendi!' : 'Yeni aşama oluşturuldu!')
|
||||||
if (isEditModalOpen) {
|
if (isEditModalOpen) {
|
||||||
closeEditModal();
|
closeEditModal()
|
||||||
} else {
|
} else {
|
||||||
closeCreateModal();
|
closeCreateModal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const generatePhaseCode = () => {
|
const generatePhaseCode = () => {
|
||||||
const phaseCount = mockProjectPhases.length + 1;
|
const phaseCount = mockProjectPhases.length + 1
|
||||||
return `PH-${phaseCount.toString().padStart(3, "0")}`;
|
return `PH-${phaseCount.toString().padStart(3, '0')}`
|
||||||
};
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Container>
|
||||||
<div className="mb-3 mt-2">
|
<div className="space-y-2">
|
||||||
{/* Header Section with Description */}
|
{/* Header Section with Description */}
|
||||||
<div className="rounded-xl mt-2">
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold text-gray-900">
|
<h2 className="text-xl font-bold text-gray-900">Proje Aşamaları</h2>
|
||||||
Proje Aşamaları
|
<p className="text-gray-600">Proje İlerleme Yönetimi</p>
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
Proje İlerleme Yönetimi
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -195,7 +179,6 @@ const ProjectPhases: React.FC = () => {
|
||||||
Yeni Aşama
|
Yeni Aşama
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Summary Cards */}
|
{/* Summary Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 mb-3">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 mb-3">
|
||||||
|
|
@ -208,22 +191,14 @@ const ProjectPhases: React.FC = () => {
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Aktif Aşama"
|
title="Aktif Aşama"
|
||||||
value={
|
value={mockProjectPhases.filter((p) => p.status === PhaseStatusEnum.InProgress).length}
|
||||||
mockProjectPhases.filter(
|
|
||||||
(p) => p.status === PhaseStatusEnum.InProgress
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
color="green"
|
color="green"
|
||||||
icon="FaBolt"
|
icon="FaBolt"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Widget
|
<Widget
|
||||||
title="Tamamlanan"
|
title="Tamamlanan"
|
||||||
value={
|
value={mockProjectPhases.filter((p) => p.status === PhaseStatusEnum.Completed).length}
|
||||||
mockProjectPhases.filter(
|
|
||||||
(p) => p.status === PhaseStatusEnum.Completed
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
color="blue"
|
color="blue"
|
||||||
icon="FaCheckCircle"
|
icon="FaCheckCircle"
|
||||||
/>
|
/>
|
||||||
|
|
@ -232,9 +207,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
title="Geciken"
|
title="Geciken"
|
||||||
value={
|
value={
|
||||||
mockProjectPhases.filter(
|
mockProjectPhases.filter(
|
||||||
(p) =>
|
(p) => p.status !== PhaseStatusEnum.Completed && new Date(p.endDate) < new Date(),
|
||||||
p.status !== PhaseStatusEnum.Completed &&
|
|
||||||
new Date(p.endDate) < new Date()
|
|
||||||
).length
|
).length
|
||||||
}
|
}
|
||||||
color="red"
|
color="red"
|
||||||
|
|
@ -281,7 +254,6 @@ const ProjectPhases: React.FC = () => {
|
||||||
<option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option>
|
<option value={ProjectStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Phase Timeline */}
|
{/* Phase Timeline */}
|
||||||
<h3 className="text-base font-semibold mb-2 flex items-center gap-2">
|
<h3 className="text-base font-semibold mb-2 flex items-center gap-2">
|
||||||
|
|
@ -291,10 +263,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{filteredPhases
|
{filteredPhases
|
||||||
.sort(
|
.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime())
|
||||||
(a, b) =>
|
|
||||||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
|
||||||
)
|
|
||||||
.map((phase) => (
|
.map((phase) => (
|
||||||
<div key={phase.id} className="relative">
|
<div key={phase.id} className="relative">
|
||||||
<div
|
<div
|
||||||
|
|
@ -304,9 +273,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="font-medium text-gray-900">
|
<h4 className="font-medium text-gray-900">{phase.name}</h4>
|
||||||
{phase.name}
|
|
||||||
</h4>
|
|
||||||
<span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
|
<span className="text-xs text-gray-500 bg-gray-100 px-1.5 py-0.5 rounded">
|
||||||
{phase.code}
|
{phase.code}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -323,16 +290,12 @@ const ProjectPhases: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-gray-600 mb-2">
|
<p className="text-xs text-gray-600 mb-2">{phase.description}</p>
|
||||||
{phase.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Assigned Teams */}
|
{/* Assigned Teams */}
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<FaUsers className="w-4 h-4 text-gray-500" />
|
<FaUsers className="w-4 h-4 text-gray-500" />
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">Atanmış Ekipler:</span>
|
||||||
Atanmış Ekipler:
|
|
||||||
</span>
|
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{phase.assignedTeams.map((team, index) => (
|
{phase.assignedTeams.map((team, index) => (
|
||||||
<span
|
<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-3 text-xs text-gray-500">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FaCalendar className="w-4 h-4" />
|
<FaCalendar className="w-4 h-4" />
|
||||||
{new Date(phase.startDate).toLocaleDateString(
|
{new Date(phase.startDate).toLocaleDateString('tr-TR')} -{' '}
|
||||||
"tr-TR"
|
{new Date(phase.endDate).toLocaleDateString('tr-TR')}
|
||||||
)} - {new Date(phase.endDate).toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<FaDollarSign className="w-4 h-4" />₺
|
<FaDollarSign className="w-4 h-4" />₺{phase.budget.toLocaleString()}
|
||||||
{phase.budget.toLocaleString()}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{phase.completedMilestones}/{phase.milestones} milestone
|
{phase.completedMilestones}/{phase.milestones} milestone
|
||||||
|
|
@ -366,7 +327,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="flex items-center gap-1.5 mb-1.5">
|
<div className="flex items-center gap-1.5 mb-1.5">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
|
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)}
|
{getPhaseStatusText(phase.status)}
|
||||||
|
|
@ -375,7 +336,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="flex items-center gap-1.5 mb-1.5">
|
<div className="flex items-center gap-1.5 mb-1.5">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
|
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
|
||||||
phase.category
|
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="flex items-center gap-2 mb-1">
|
||||||
<div className="w-20 bg-gray-200 rounded-full h-2">
|
<div className="w-20 bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full ${getProgressColor(
|
className={`h-2 rounded-full ${getProgressColor(phase.progress)}`}
|
||||||
phase.progress
|
|
||||||
)}`}
|
|
||||||
style={{ width: `${phase.progress}%` }}
|
style={{ width: `${phase.progress}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-medium text-gray-900">
|
<span className="text-xs font-medium text-gray-900">{phase.progress}%</span>
|
||||||
{phase.progress}%
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">Tamamlanma Oranı</div>
|
<div className="text-xs text-gray-500">Tamamlanma Oranı</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -400,6 +357,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Phase Detail Modal */}
|
{/* Phase Detail Modal */}
|
||||||
{isModalOpen && selectedPhase && (
|
{isModalOpen && selectedPhase && (
|
||||||
|
|
@ -412,10 +370,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
{selectedPhase.code} - {selectedPhase.name}
|
{selectedPhase.code} - {selectedPhase.name}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button onClick={closeModal} className="text-gray-400 hover:text-gray-600">
|
||||||
onClick={closeModal}
|
|
||||||
className="text-gray-400 hover:text-gray-600"
|
|
||||||
>
|
|
||||||
<FaTimesCircle className="w-5 h-5" />
|
<FaTimesCircle className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -424,22 +379,16 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<div className="space-y-3 pt-1">
|
<div className="space-y-3 pt-1">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold mb-2">
|
<h4 className="text-sm font-semibold mb-2">Aşama Bilgileri</h4>
|
||||||
Aşama Bilgileri
|
|
||||||
</h4>
|
|
||||||
<div className="bg-gray-50 p-2.5 rounded-lg">
|
<div className="bg-gray-50 p-2.5 rounded-lg">
|
||||||
<p className="text-sm text-gray-900 mb-3">
|
<p className="text-sm text-gray-900 mb-3">{selectedPhase.description}</p>
|
||||||
{selectedPhase.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2 mb-2">
|
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
Proje
|
Proje
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">{selectedPhase.project?.name}</p>
|
||||||
{selectedPhase.project?.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
|
|
@ -447,7 +396,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
|
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseCategoryColor(
|
||||||
selectedPhase.category
|
selectedPhase.category,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{selectedPhase.category}
|
{selectedPhase.category}
|
||||||
|
|
@ -462,7 +411,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
|
className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium ${getPhaseStatusColor(
|
||||||
selectedPhase.status
|
selectedPhase.status,
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getPhaseStatusIcon(selectedPhase.status)}
|
{getPhaseStatusIcon(selectedPhase.status)}
|
||||||
|
|
@ -477,7 +426,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="flex-1 bg-gray-200 rounded-full h-2">
|
<div className="flex-1 bg-gray-200 rounded-full h-2">
|
||||||
<div
|
<div
|
||||||
className={`h-2 rounded-full ${getProgressColor(
|
className={`h-2 rounded-full ${getProgressColor(
|
||||||
selectedPhase.progress
|
selectedPhase.progress,
|
||||||
)}`}
|
)}`}
|
||||||
style={{ width: `${selectedPhase.progress}%` }}
|
style={{ width: `${selectedPhase.progress}%` }}
|
||||||
/>
|
/>
|
||||||
|
|
@ -492,9 +441,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold mb-2">
|
<h4 className="text-sm font-semibold mb-2">Zaman Çizelgesi</h4>
|
||||||
Zaman Çizelgesi
|
|
||||||
</h4>
|
|
||||||
<div className="bg-gray-50 p-2.5 rounded-lg">
|
<div className="bg-gray-50 p-2.5 rounded-lg">
|
||||||
<div className="grid grid-cols-2 gap-2 mb-2">
|
<div className="grid grid-cols-2 gap-2 mb-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -502,9 +449,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
Planlanan Başlangıç
|
Planlanan Başlangıç
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">
|
||||||
{new Date(
|
{new Date(selectedPhase.startDate).toLocaleDateString('tr-TR')}
|
||||||
selectedPhase.startDate
|
|
||||||
).toLocaleDateString("tr-TR")}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -512,15 +457,12 @@ const ProjectPhases: React.FC = () => {
|
||||||
Planlanan Bitiş
|
Planlanan Bitiş
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">
|
||||||
{new Date(selectedPhase.endDate).toLocaleDateString(
|
{new Date(selectedPhase.endDate).toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(selectedPhase.actualStartDate ||
|
{(selectedPhase.actualStartDate || selectedPhase.actualEndDate) && (
|
||||||
selectedPhase.actualEndDate) && (
|
|
||||||
<div className="grid grid-cols-2 gap-2 mb-2 pt-2 border-t">
|
<div className="grid grid-cols-2 gap-2 mb-2 pt-2 border-t">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||||||
|
|
@ -528,10 +470,10 @@ const ProjectPhases: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">
|
||||||
{selectedPhase.actualStartDate
|
{selectedPhase.actualStartDate
|
||||||
? new Date(
|
? new Date(selectedPhase.actualStartDate).toLocaleDateString(
|
||||||
selectedPhase.actualStartDate
|
'tr-TR',
|
||||||
).toLocaleDateString("tr-TR")
|
)
|
||||||
: "-"}
|
: '-'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -540,10 +482,8 @@ const ProjectPhases: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">
|
||||||
{selectedPhase.actualEndDate
|
{selectedPhase.actualEndDate
|
||||||
? new Date(
|
? new Date(selectedPhase.actualEndDate).toLocaleDateString('tr-TR')
|
||||||
selectedPhase.actualEndDate
|
: '-'}
|
||||||
).toLocaleDateString("tr-TR")
|
|
||||||
: "-"}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -557,20 +497,15 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`text-sm font-semibold ${
|
className={`text-sm font-semibold ${
|
||||||
calculateScheduleVariance(selectedPhase) > 0
|
calculateScheduleVariance(selectedPhase) > 0
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: "text-green-600"
|
: 'text-green-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
{calculateScheduleVariance(selectedPhase) > 0 ? '+' : ''}
|
||||||
|
{calculateScheduleVariance(selectedPhase).toFixed(1)}%
|
||||||
{calculateScheduleVariance(selectedPhase) > 0
|
{calculateScheduleVariance(selectedPhase) > 0
|
||||||
? "+"
|
? ' (Gecikme)'
|
||||||
: ""}
|
: ' (Erken)'}
|
||||||
{calculateScheduleVariance(selectedPhase).toFixed(
|
|
||||||
1
|
|
||||||
)}
|
|
||||||
%
|
|
||||||
{calculateScheduleVariance(selectedPhase) > 0
|
|
||||||
? " (Gecikme)"
|
|
||||||
: " (Erken)"}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -578,9 +513,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold mb-2">
|
<h4 className="text-sm font-semibold mb-2">Bütçe Analizi</h4>
|
||||||
Bütçe Analizi
|
|
||||||
</h4>
|
|
||||||
<div className="bg-gray-50 p-2.5 rounded-lg">
|
<div className="bg-gray-50 p-2.5 rounded-lg">
|
||||||
<div className="grid grid-cols-2 gap-3 mb-3">
|
<div className="grid grid-cols-2 gap-3 mb-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -609,27 +542,25 @@ const ProjectPhases: React.FC = () => {
|
||||||
className={`text-lg font-semibold ${
|
className={`text-lg font-semibold ${
|
||||||
calculateBudgetVariance(
|
calculateBudgetVariance(
|
||||||
selectedPhase.budget,
|
selectedPhase.budget,
|
||||||
selectedPhase.actualCost
|
selectedPhase.actualCost,
|
||||||
) > 0
|
) > 0
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: "text-green-600"
|
: 'text-green-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
{calculateBudgetVariance(selectedPhase.budget, selectedPhase.actualCost) >
|
||||||
|
0
|
||||||
|
? '+'
|
||||||
|
: ''}
|
||||||
{calculateBudgetVariance(
|
{calculateBudgetVariance(
|
||||||
selectedPhase.budget,
|
selectedPhase.budget,
|
||||||
selectedPhase.actualCost
|
selectedPhase.actualCost,
|
||||||
) > 0
|
|
||||||
? "+"
|
|
||||||
: ""}
|
|
||||||
{calculateBudgetVariance(
|
|
||||||
selectedPhase.budget,
|
|
||||||
selectedPhase.actualCost
|
|
||||||
).toFixed(1)}
|
).toFixed(1)}
|
||||||
%
|
%
|
||||||
<span className="text-sm font-normal text-gray-600 ml-2">
|
<span className="text-sm font-normal text-gray-600 ml-2">
|
||||||
(₺
|
(₺
|
||||||
{Math.abs(
|
{Math.abs(
|
||||||
selectedPhase.actualCost - selectedPhase.budget
|
selectedPhase.actualCost - selectedPhase.budget,
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
)
|
)
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -641,10 +572,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
Kalan Bütçe
|
Kalan Bütçe
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-gray-900">
|
<p className="text-sm text-gray-900">
|
||||||
₺
|
₺{(selectedPhase.budget - selectedPhase.actualCost).toLocaleString()}
|
||||||
{(
|
|
||||||
selectedPhase.budget - selectedPhase.actualCost
|
|
||||||
).toLocaleString()}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -660,8 +588,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm text-gray-600">İlerleme</span>
|
<span className="text-sm text-gray-600">İlerleme</span>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{selectedPhase.completedMilestones}/
|
{selectedPhase.completedMilestones}/{selectedPhase.milestones}
|
||||||
{selectedPhase.milestones}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2 mb-3">
|
<div className="w-full bg-gray-200 rounded-full h-2 mb-3">
|
||||||
|
|
@ -669,25 +596,20 @@ const ProjectPhases: React.FC = () => {
|
||||||
className="bg-green-500 h-2 rounded-full"
|
className="bg-green-500 h-2 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
width: `${
|
width: `${
|
||||||
(selectedPhase.completedMilestones /
|
(selectedPhase.completedMilestones / selectedPhase.milestones) * 100
|
||||||
selectedPhase.milestones) *
|
|
||||||
100
|
|
||||||
}%`,
|
}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{selectedPhase.milestones -
|
{selectedPhase.milestones - selectedPhase.completedMilestones} milestone
|
||||||
selectedPhase.completedMilestones}{" "}
|
kaldı
|
||||||
milestone kaldı
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h4 className="text-sm font-semibold mb-2">
|
<h4 className="text-sm font-semibold mb-2">Atanmış Ekipler</h4>
|
||||||
Atanmış Ekipler
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{selectedPhase.assignedTeams.map((team, index) => (
|
{selectedPhase.assignedTeams.map((team, index) => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -710,9 +632,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
className="flex items-center gap-2 p-1.5 bg-gray-100 rounded"
|
className="flex items-center gap-2 p-1.5 bg-gray-100 rounded"
|
||||||
>
|
>
|
||||||
<FaBullseye className="w-4 h-4 text-green-500" />
|
<FaBullseye className="w-4 h-4 text-green-500" />
|
||||||
<span className="text-sm text-gray-900">
|
<span className="text-sm text-gray-900">{deliverable}</span>
|
||||||
{deliverable}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -728,9 +648,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
className="flex items-center gap-2 p-1.5 bg-red-50 rounded"
|
className="flex items-center gap-2 p-1.5 bg-red-50 rounded"
|
||||||
>
|
>
|
||||||
<FaExclamationTriangle className="w-4 h-4 text-red-500" />
|
<FaExclamationTriangle className="w-4 h-4 text-red-500" />
|
||||||
<span className="text-sm text-gray-900">
|
<span className="text-sm text-gray-900">{risk}</span>
|
||||||
{risk}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -749,8 +667,8 @@ const ProjectPhases: React.FC = () => {
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeModal();
|
closeModal()
|
||||||
openEditModal(selectedPhase!);
|
openEditModal(selectedPhase!)
|
||||||
}}
|
}}
|
||||||
className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2"
|
className="px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center gap-2"
|
||||||
>
|
>
|
||||||
|
|
@ -778,12 +696,12 @@ const ProjectPhases: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900">
|
<h3 className="text-lg font-bold text-gray-900">
|
||||||
{isEditModalOpen ? "Aşama Düzenle" : "Yeni Aşama Oluştur"}
|
{isEditModalOpen ? 'Aşama Düzenle' : 'Yeni Aşama Oluştur'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{isEditModalOpen
|
{isEditModalOpen
|
||||||
? "Mevcut aşama bilgilerini güncelleyin"
|
? 'Mevcut aşama bilgilerini güncelleyin'
|
||||||
: "Yeni bir proje aşaması tanımlayın"}
|
: 'Yeni bir proje aşaması tanımlayın'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -830,9 +748,9 @@ const ProjectPhases: React.FC = () => {
|
||||||
{formData.projectId &&
|
{formData.projectId &&
|
||||||
(() => {
|
(() => {
|
||||||
const selectedProject = mockProjects.find(
|
const selectedProject = mockProjects.find(
|
||||||
(p) => p.id === formData.projectId
|
(p) => p.id === formData.projectId,
|
||||||
);
|
)
|
||||||
if (!selectedProject) return null;
|
if (!selectedProject) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 p-2 bg-blue-50 border border-blue-200 rounded-lg">
|
<div className="mt-2 p-2 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
|
|
@ -846,39 +764,32 @@ const ProjectPhases: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-blue-800">
|
<span className="font-medium text-blue-800">Durum:</span>
|
||||||
Durum:
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className={`ml-1 px-2 py-1 rounded-full text-xs font-medium ${
|
className={`ml-1 px-2 py-1 rounded-full text-xs font-medium ${
|
||||||
selectedProject.status ===
|
selectedProject.status === ProjectStatusEnum.Active
|
||||||
ProjectStatusEnum.Active
|
? 'bg-green-100 text-green-800'
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: selectedProject.status ===
|
: selectedProject.status ===
|
||||||
ProjectStatusEnum.Planning
|
ProjectStatusEnum.Planning
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? 'bg-yellow-100 text-yellow-800'
|
||||||
: "bg-gray-100 text-gray-800"
|
: 'bg-gray-100 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedProject.status ===
|
{selectedProject.status === ProjectStatusEnum.Active
|
||||||
ProjectStatusEnum.Active
|
? 'Aktif'
|
||||||
? "Aktif"
|
: selectedProject.status === ProjectStatusEnum.Planning
|
||||||
: selectedProject.status ===
|
? 'Planlama'
|
||||||
ProjectStatusEnum.Planning
|
|
||||||
? "Planlama"
|
|
||||||
: selectedProject.status ===
|
: selectedProject.status ===
|
||||||
ProjectStatusEnum.Completed
|
ProjectStatusEnum.Completed
|
||||||
? "Tamamlandı"
|
? 'Tamamlandı'
|
||||||
: selectedProject.status ===
|
: selectedProject.status ===
|
||||||
ProjectStatusEnum.OnHold
|
ProjectStatusEnum.OnHold
|
||||||
? "Beklemede"
|
? 'Beklemede'
|
||||||
: "Iptal"}
|
: 'Iptal'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-blue-800">
|
<span className="font-medium text-blue-800">İlerleme:</span>
|
||||||
İlerleme:
|
|
||||||
</span>
|
|
||||||
<span className="ml-1 text-blue-700">
|
<span className="ml-1 text-blue-700">
|
||||||
%{selectedProject.progress}
|
%{selectedProject.progress}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -888,37 +799,27 @@ const ProjectPhases: React.FC = () => {
|
||||||
Başlangıç:
|
Başlangıç:
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-1 text-blue-700">
|
<span className="ml-1 text-blue-700">
|
||||||
{selectedProject.startDate.toLocaleDateString(
|
{selectedProject.startDate.toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-blue-800">
|
<span className="font-medium text-blue-800">Bitiş:</span>
|
||||||
Bitiş:
|
|
||||||
</span>
|
|
||||||
<span className="ml-1 text-blue-700">
|
<span className="ml-1 text-blue-700">
|
||||||
{selectedProject.endDate.toLocaleDateString(
|
{selectedProject.endDate.toLocaleDateString('tr-TR')}
|
||||||
"tr-TR"
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 text-right">
|
<div className="ml-4 text-right">
|
||||||
<div className="text-lg font-bold text-blue-900">
|
<div className="text-lg font-bold text-blue-900">
|
||||||
{selectedProject.budget.toLocaleString(
|
{selectedProject.budget.toLocaleString('tr-TR')}{' '}
|
||||||
"tr-TR"
|
|
||||||
)}{" "}
|
|
||||||
{selectedProject.currency}
|
{selectedProject.currency}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-blue-600">
|
<div className="text-xs text-blue-600">Bütçe</div>
|
||||||
Bütçe
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
);
|
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -979,21 +880,11 @@ const ProjectPhases: React.FC = () => {
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
className="w-full px-2.5 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors"
|
||||||
>
|
>
|
||||||
<option value={PhaseStatusEnum.NotStarted}>
|
<option value={PhaseStatusEnum.NotStarted}>Başlanmadı</option>
|
||||||
Başlanmadı
|
<option value={PhaseStatusEnum.InProgress}>Devam Ediyor</option>
|
||||||
</option>
|
<option value={PhaseStatusEnum.Completed}>Tamamlandı</option>
|
||||||
<option value={PhaseStatusEnum.InProgress}>
|
<option value={PhaseStatusEnum.OnHold}>Beklemede</option>
|
||||||
Devam Ediyor
|
<option value={PhaseStatusEnum.Cancelled}>İptal Edildi</option>
|
||||||
</option>
|
|
||||||
<option value={PhaseStatusEnum.Completed}>
|
|
||||||
Tamamlandı
|
|
||||||
</option>
|
|
||||||
<option value={PhaseStatusEnum.OnHold}>
|
|
||||||
Beklemede
|
|
||||||
</option>
|
|
||||||
<option value={PhaseStatusEnum.Cancelled}>
|
|
||||||
İptal Edildi
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1075,9 +966,7 @@ const ProjectPhases: React.FC = () => {
|
||||||
<FaProjectDiagram className="w-4 h-4" />
|
<FaProjectDiagram className="w-4 h-4" />
|
||||||
<span className="text-sm font-medium">Aşama Kodu:</span>
|
<span className="text-sm font-medium">Aşama Kodu:</span>
|
||||||
<span className="font-bold">
|
<span className="font-bold">
|
||||||
{isEditModalOpen
|
{isEditModalOpen ? editingPhase?.code : generatePhaseCode()}
|
||||||
? editingPhase?.code
|
|
||||||
: generatePhaseCode()}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1099,15 +988,15 @@ const ProjectPhases: React.FC = () => {
|
||||||
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 shadow-md hover:shadow-lg"
|
className="px-4 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2 shadow-md hover:shadow-lg"
|
||||||
>
|
>
|
||||||
<FaSave className="w-4 h-4" />
|
<FaSave className="w-4 h-4" />
|
||||||
{isEditModalOpen ? "Güncelle" : "Oluştur"}
|
{isEditModalOpen ? 'Güncelle' : 'Oluştur'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ProjectPhases;
|
export default ProjectPhases
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
getPriorityColor,
|
getPriorityColor,
|
||||||
getTaskTypeColor,
|
getTaskTypeColor,
|
||||||
} from "../../../utils/erp";
|
} from "../../../utils/erp";
|
||||||
|
import { Container } from "@/components/shared";
|
||||||
|
|
||||||
const ProjectTasks: React.FC = () => {
|
const ProjectTasks: React.FC = () => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
@ -166,12 +167,12 @@ const ProjectTasks: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Container>
|
||||||
<div className="mb-3 mt-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold text-gray-900">Görev Yönetimi</h2>
|
<h2 className="text-xl font-bold text-gray-900">Görev Yönetimi</h2>
|
||||||
<p className="text-gray-600 mt-1">
|
<p className="text-gray-600">
|
||||||
Proje görevlerinizi oluşturun, düzenleyin ve takip edin
|
Proje görevlerinizi oluşturun, düzenleyin ve takip edin
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1156,7 +1157,8 @@ const ProjectTasks: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Container>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { useParams, Link, useNavigate } from "react-router-dom";
|
import { useParams, Link, useNavigate } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
FaArrowLeft,
|
FaArrowLeft,
|
||||||
FaEdit,
|
FaEdit,
|
||||||
|
|
@ -16,14 +16,14 @@ import {
|
||||||
FaFolder,
|
FaFolder,
|
||||||
FaPhone,
|
FaPhone,
|
||||||
FaEnvelope,
|
FaEnvelope,
|
||||||
} from "react-icons/fa";
|
} from 'react-icons/fa'
|
||||||
import { TaskStatusEnum } from "../../../types/ps";
|
import { TaskStatusEnum } from '../../../types/ps'
|
||||||
import { mockProjects } from "../../../mocks/mockProjects";
|
import { mockProjects } from '../../../mocks/mockProjects'
|
||||||
import { mockProjectPhases } from "../../../mocks/mockProjectPhases";
|
import { mockProjectPhases } from '../../../mocks/mockProjectPhases'
|
||||||
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
|
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
|
||||||
import dayjs from "dayjs";
|
import dayjs from 'dayjs'
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames'
|
||||||
import { PriorityEnum } from "../../../types/common";
|
import { PriorityEnum } from '../../../types/common'
|
||||||
import {
|
import {
|
||||||
getProjectStatusColor,
|
getProjectStatusColor,
|
||||||
getProjectStatusIcon,
|
getProjectStatusIcon,
|
||||||
|
|
@ -33,27 +33,24 @@ import {
|
||||||
getPriorityText,
|
getPriorityText,
|
||||||
getProjectTypeColor,
|
getProjectTypeColor,
|
||||||
getProjectTypeText,
|
getProjectTypeText,
|
||||||
} from "../../../utils/erp";
|
} from '../../../utils/erp'
|
||||||
|
import { Container } from '@/components/shared'
|
||||||
|
|
||||||
const ProjectView: React.FC = () => {
|
const ProjectView: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate()
|
||||||
const [activeTab, setActiveTab] = useState("overview");
|
const [activeTab, setActiveTab] = useState('overview')
|
||||||
|
|
||||||
// Find the project by ID
|
// Find the project by ID
|
||||||
const project = mockProjects.find((p) => p.id === id);
|
const project = mockProjects.find((p) => p.id === id)
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
|
<FaFolder className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Proje bulunamadı</h3>
|
||||||
Proje bulunamadı
|
<p className="mt-1 text-sm text-gray-500">Belirtilen ID ile proje mevcut değil.</p>
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
|
||||||
Belirtilen ID ile proje mevcut değil.
|
|
||||||
</p>
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link
|
<Link
|
||||||
to="/admin/projects"
|
to="/admin/projects"
|
||||||
|
|
@ -65,35 +62,33 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: "overview", name: "Genel Bakış", icon: FaChartLine },
|
{ id: 'overview', name: 'Genel Bakış', icon: FaChartLine },
|
||||||
{ id: "phases", name: "Fazlar", icon: FaFlag },
|
{ id: 'phases', name: 'Fazlar', icon: FaFlag },
|
||||||
{ id: "tasks", name: "Görevler", icon: FaTasks },
|
{ id: 'tasks', name: 'Görevler', icon: FaTasks },
|
||||||
{ id: "documents", name: "Belgeler", icon: FaFileAlt },
|
{ id: 'documents', name: 'Belgeler', icon: FaFileAlt },
|
||||||
{ id: "risks", name: "Riskler", icon: FaExclamationCircle },
|
{ id: 'risks', name: 'Riskler', icon: FaExclamationCircle },
|
||||||
{ id: "financial", name: "Mali Özet", icon: FaDollarSign },
|
{ id: 'financial', name: 'Mali Özet', icon: FaDollarSign },
|
||||||
];
|
]
|
||||||
|
|
||||||
const budgetUsagePercentage = (project.actualCost / project.budget) * 100;
|
const budgetUsagePercentage = (project.actualCost / project.budget) * 100
|
||||||
const timeElapsed = dayjs().diff(dayjs(project.startDate), "day");
|
const timeElapsed = dayjs().diff(dayjs(project.startDate), 'day')
|
||||||
const totalDuration = dayjs(project.endDate).diff(
|
const totalDuration = dayjs(project.endDate).diff(dayjs(project.startDate), 'day')
|
||||||
dayjs(project.startDate),
|
const timeProgress = Math.min((timeElapsed / totalDuration) * 100, 100)
|
||||||
"day"
|
|
||||||
);
|
|
||||||
const timeProgress = Math.min((timeElapsed / totalDuration) * 100, 100);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white shadow-sm border-b border-gray-200">
|
<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 justify-between h-12">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<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"
|
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" />
|
<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" />
|
<FaFolder className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-lg font-bold text-gray-900">
|
<h1 className="text-lg font-bold text-gray-900">{project.code}</h1>
|
||||||
{project.code}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-gray-600">{project.name}</p>
|
<p className="text-sm text-gray-600">{project.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -114,14 +107,12 @@ const ProjectView: React.FC = () => {
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium border",
|
'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium border',
|
||||||
getProjectStatusColor(project.status)
|
getProjectStatusColor(project.status),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getProjectStatusIcon(project.status)}
|
{getProjectStatusIcon(project.status)}
|
||||||
<span className="ml-2">
|
<span className="ml-2">{getProjectStatusText(project.status)}</span>
|
||||||
{getProjectStatusText(project.status)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
to={`/admin/projects/edit/${project.id}`}
|
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">
|
<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" />
|
<FaBullseye className="w-5 h-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-bold text-blue-600">
|
<span className="text-lg font-bold text-blue-600">{project.progress}%</span>
|
||||||
{project.progress}%
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-medium text-gray-600 mb-1.5">
|
<h3 className="text-sm font-medium text-gray-600 mb-1.5">İlerleme</h3>
|
||||||
İlerleme
|
|
||||||
</h3>
|
|
||||||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames('h-1.5 rounded-full', getProgressColor(project.progress))}
|
||||||
"h-1.5 rounded-full",
|
|
||||||
getProgressColor(project.progress)
|
|
||||||
)}
|
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -172,12 +156,9 @@ const ProjectView: React.FC = () => {
|
||||||
{budgetUsagePercentage.toFixed(0)}%
|
{budgetUsagePercentage.toFixed(0)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-medium text-gray-600 mb-1">
|
<h3 className="text-sm font-medium text-gray-600 mb-1">Bütçe Kullanımı</h3>
|
||||||
Bütçe Kullanımı
|
|
||||||
</h3>
|
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
₺{project.actualCost.toLocaleString()} / ₺
|
₺{project.actualCost.toLocaleString()} / ₺{project.budget.toLocaleString()}
|
||||||
{project.budget.toLocaleString()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -191,9 +172,7 @@ const ProjectView: React.FC = () => {
|
||||||
{timeProgress.toFixed(0)}%
|
{timeProgress.toFixed(0)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-medium text-gray-600 mb-1">
|
<h3 className="text-sm font-medium text-gray-600 mb-1">Zaman İlerlemesi</h3>
|
||||||
Zaman İlerlemesi
|
|
||||||
</h3>
|
|
||||||
<div className="text-xs text-gray-500">
|
<div className="text-xs text-gray-500">
|
||||||
{timeElapsed} / {totalDuration} gün
|
{timeElapsed} / {totalDuration} gün
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -206,10 +185,7 @@ const ProjectView: React.FC = () => {
|
||||||
<FaFlag className="w-5 h-5 text-red-600" />
|
<FaFlag className="w-5 h-5 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames('text-lg font-bold', getPriorityColor(project.priority))}
|
||||||
"text-lg font-bold",
|
|
||||||
getPriorityColor(project.priority)
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{getPriorityText(project.priority)}
|
{getPriorityText(project.priority)}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -217,8 +193,8 @@ const ProjectView: React.FC = () => {
|
||||||
<h3 className="text-sm font-medium text-gray-600 mb-1">Öncelik</h3>
|
<h3 className="text-sm font-medium text-gray-600 mb-1">Öncelik</h3>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border",
|
'inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border',
|
||||||
getProjectTypeColor(project.projectType)
|
getProjectTypeColor(project.projectType),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getProjectTypeText(project.projectType)}
|
{getProjectTypeText(project.projectType)}
|
||||||
|
|
@ -231,28 +207,28 @@ const ProjectView: React.FC = () => {
|
||||||
<div className="border-b border-gray-200">
|
<div className="border-b border-gray-200">
|
||||||
<nav className="flex space-x-4 px-3" aria-label="Tabs">
|
<nav className="flex space-x-4 px-3" aria-label="Tabs">
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
const Icon = tab.icon;
|
const Icon = tab.icon
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={classNames(
|
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
|
activeTab === tab.id
|
||||||
? "border-blue-500 text-blue-600"
|
? 'border-blue-500 text-blue-600'
|
||||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="w-4 h-4" />
|
<Icon className="w-4 h-4" />
|
||||||
<span>{tab.name}</span>
|
<span>{tab.name}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
{activeTab === "overview" && (
|
{activeTab === 'overview' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Project Info Grid */}
|
{/* Project Info Grid */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
<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="bg-gray-50 rounded-lg p-2.5 space-y-1.5 text-sm">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">Proje Kodu:</span>
|
<span className="text-gray-600">Proje Kodu:</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">{project.code}</span>
|
||||||
{project.code}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">Proje Türü:</span>
|
<span className="text-gray-600">Proje Türü:</span>
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"px-2 py-0.5 rounded-full text-xs font-medium border",
|
'px-2 py-0.5 rounded-full text-xs font-medium border',
|
||||||
getProjectTypeColor(project.projectType)
|
getProjectTypeColor(project.projectType),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getProjectTypeText(project.projectType)}
|
{getProjectTypeText(project.projectType)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">Başlangıç Tarihi:</span>
|
||||||
Başlangıç Tarihi:
|
|
||||||
</span>
|
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{dayjs(project.startDate).format("DD.MM.YYYY")}
|
{dayjs(project.startDate).format('DD.MM.YYYY')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">Bitiş Tarihi:</span>
|
<span className="text-gray-600">Bitiş Tarihi:</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{dayjs(project.endDate).format("DD.MM.YYYY")}
|
{dayjs(project.endDate).format('DD.MM.YYYY')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-gray-600">
|
<span className="text-gray-600">Gerçek Başlangıç:</span>
|
||||||
Gerçek Başlangıç:
|
|
||||||
</span>
|
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{project.actualStartDate
|
{project.actualStartDate
|
||||||
? dayjs(project.actualStartDate).format(
|
? dayjs(project.actualStartDate).format('DD.MM.YYYY')
|
||||||
"DD.MM.YYYY"
|
: 'Henüz başlamadı'}
|
||||||
)
|
|
||||||
: "Henüz başlamadı"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -312,9 +280,7 @@ const ProjectView: React.FC = () => {
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
{project.description && (
|
{project.description && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold text-gray-900 mb-2">
|
<h3 className="text-base font-semibold text-gray-900 mb-2">Açıklama</h3>
|
||||||
Açıklama
|
|
||||||
</h3>
|
|
||||||
<div className="bg-gray-50 rounded-lg p-2.5">
|
<div className="bg-gray-50 rounded-lg p-2.5">
|
||||||
<p className="text-sm text-gray-700 leading-relaxed">
|
<p className="text-sm text-gray-700 leading-relaxed">
|
||||||
{project.description}
|
{project.description}
|
||||||
|
|
@ -342,8 +308,7 @@ const ProjectView: React.FC = () => {
|
||||||
{project.projectManager.fullName}
|
{project.projectManager.fullName}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
{project.projectManager.jobPosition?.name ||
|
{project.projectManager.jobPosition?.name || 'Proje Yöneticisi'}
|
||||||
"Proje Yöneticisi"}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center mt-1 text-sm text-gray-500">
|
<div className="flex items-center mt-1 text-sm text-gray-500">
|
||||||
<FaEnvelope className="w-3 h-3 mr-1" />
|
<FaEnvelope className="w-3 h-3 mr-1" />
|
||||||
|
|
@ -364,9 +329,7 @@ const ProjectView: React.FC = () => {
|
||||||
{/* Customer */}
|
{/* Customer */}
|
||||||
{project.customer && (
|
{project.customer && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-semibold text-gray-900 mb-2">
|
<h3 className="text-base font-semibold text-gray-900 mb-2">Müşteri</h3>
|
||||||
Müşteri
|
|
||||||
</h3>
|
|
||||||
<div className="bg-gray-50 rounded-lg p-2.5">
|
<div className="bg-gray-50 rounded-lg p-2.5">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
<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}
|
{project.customer.name}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">
|
||||||
{project.customer.primaryContact?.firstName}{" "}
|
{project.customer.primaryContact?.firstName}{' '}
|
||||||
{project.customer.primaryContact?.lastName}
|
{project.customer.primaryContact?.lastName}
|
||||||
</p>
|
</p>
|
||||||
{project.customer.primaryContact?.email && (
|
{project.customer.primaryContact?.email && (
|
||||||
|
|
@ -402,21 +365,17 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "phases" && (
|
{activeTab === 'phases' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Phases List */}
|
{/* Phases List */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{mockProjectPhases.filter(
|
{mockProjectPhases.filter((phase) => phase.projectId === project.id).length >
|
||||||
(phase) => phase.projectId === project.id
|
0 ? (
|
||||||
).length > 0 ? (
|
|
||||||
mockProjectPhases
|
mockProjectPhases
|
||||||
.filter((phase) => phase.projectId === project.id)
|
.filter((phase) => phase.projectId === project.id)
|
||||||
.map((phase) => (
|
.map((phase) => (
|
||||||
<div
|
<div key={phase.id} className="px-3 py-2 hover:bg-gray-50">
|
||||||
key={phase.id}
|
|
||||||
className="px-3 py-2 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1.5">
|
<div className="flex items-center space-x-2 mb-1.5">
|
||||||
|
|
@ -435,40 +394,26 @@ const ProjectView: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-1.5">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-1.5">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">Başlangıç</p>
|
||||||
Başlangıç
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{dayjs(phase.startDate).format(
|
{dayjs(phase.startDate).format('DD.MM.YYYY')}
|
||||||
"DD.MM.YYYY"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">Bitiş</p>
|
||||||
Bitiş
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{dayjs(phase.endDate).format(
|
{dayjs(phase.endDate).format('DD.MM.YYYY')}
|
||||||
"DD.MM.YYYY"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">Bütçe</p>
|
||||||
Bütçe
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
₺{phase.budget.toLocaleString()}
|
₺{phase.budget.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">İlerleme</p>
|
||||||
İlerleme
|
<p className="text-xs text-gray-600">%{phase.progress}</p>
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
|
||||||
%{phase.progress}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
<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">
|
<div className="px-3 py-5 text-center">
|
||||||
<FaFlag className="mx-auto h-12 w-12 text-gray-400" />
|
<FaFlag className="mx-auto h-12 w-12 text-gray-400" />
|
||||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
<h3 className="mt-2 text-sm font-medium text-gray-900">Faz bulunamadı</h3>
|
||||||
Faz bulunamadı
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
<p className="mt-1 text-sm text-gray-500">
|
||||||
Bu proje için henüz faz tanımlanmamış.
|
Bu proje için henüz faz tanımlanmamış.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -502,21 +445,17 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "tasks" && (
|
{activeTab === 'tasks' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Tasks List */}
|
{/* Tasks List */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{mockProjectTasks.filter(
|
{mockProjectTasks.filter((task) => task.projectId === project.id).length >
|
||||||
(task) => task.projectId === project.id
|
0 ? (
|
||||||
).length > 0 ? (
|
|
||||||
mockProjectTasks
|
mockProjectTasks
|
||||||
.filter((task) => task.projectId === project.id)
|
.filter((task) => task.projectId === project.id)
|
||||||
.map((task) => (
|
.map((task) => (
|
||||||
<div
|
<div key={task.id} className="px-3 py-2 hover:bg-gray-50">
|
||||||
key={task.id}
|
|
||||||
className="px-3 py-2 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1.5">
|
<div className="flex items-center space-x-2 mb-1.5">
|
||||||
|
|
@ -529,64 +468,52 @@ const ProjectView: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
task.status === TaskStatusEnum.Completed
|
task.status === TaskStatusEnum.Completed
|
||||||
? "bg-green-100 text-green-800"
|
? 'bg-green-100 text-green-800'
|
||||||
: task.status ===
|
: task.status === TaskStatusEnum.InProgress
|
||||||
TaskStatusEnum.InProgress
|
? 'bg-yellow-100 text-yellow-800'
|
||||||
? "bg-yellow-100 text-yellow-800"
|
: task.status === TaskStatusEnum.NotStarted
|
||||||
: task.status ===
|
? 'bg-gray-100 text-gray-800'
|
||||||
TaskStatusEnum.NotStarted
|
: 'bg-red-100 text-red-800'
|
||||||
? "bg-gray-100 text-gray-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{task.status === TaskStatusEnum.Completed
|
{task.status === TaskStatusEnum.Completed
|
||||||
? "Tamamlandı"
|
? 'Tamamlandı'
|
||||||
: task.status ===
|
: task.status === TaskStatusEnum.InProgress
|
||||||
TaskStatusEnum.InProgress
|
? 'Devam Ediyor'
|
||||||
? "Devam Ediyor"
|
: task.status === TaskStatusEnum.NotStarted
|
||||||
: task.status ===
|
? 'Başlamadı'
|
||||||
TaskStatusEnum.NotStarted
|
|
||||||
? "Başlamadı"
|
|
||||||
: task.status}
|
: task.status}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
task.priority === PriorityEnum.High
|
task.priority === PriorityEnum.High
|
||||||
? "bg-red-100 text-red-800"
|
? 'bg-red-100 text-red-800'
|
||||||
: task.priority === PriorityEnum.Normal
|
: task.priority === PriorityEnum.Normal
|
||||||
? "bg-orange-100 text-orange-800"
|
? 'bg-orange-100 text-orange-800'
|
||||||
: "bg-green-100 text-green-800"
|
: 'bg-green-100 text-green-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{task.priority === PriorityEnum.High
|
{task.priority === PriorityEnum.High
|
||||||
? "Yüksek"
|
? 'Yüksek'
|
||||||
: task.priority === PriorityEnum.Normal
|
: task.priority === PriorityEnum.Normal
|
||||||
? "Normal"
|
? 'Normal'
|
||||||
: task.priority === PriorityEnum.Urgent
|
: task.priority === PriorityEnum.Urgent
|
||||||
? "Acil"
|
? 'Acil'
|
||||||
: "Düşük"}
|
: 'Düşük'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mb-2">
|
<p className="text-sm text-gray-600 mb-2">{task.description}</p>
|
||||||
{task.description}
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-2">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-2">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">Başlangıç</p>
|
||||||
Başlangıç
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{dayjs(task.startDate).format(
|
{dayjs(task.startDate).format('DD.MM.YYYY')}
|
||||||
"DD.MM.YYYY"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">Bitiş</p>
|
||||||
Bitiş
|
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">
|
||||||
{dayjs(task.endDate).format("DD.MM.YYYY")}
|
{dayjs(task.endDate).format('DD.MM.YYYY')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -598,24 +525,20 @@ const ProjectView: React.FC = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-700">
|
<p className="text-xs font-medium text-gray-700">İlerleme</p>
|
||||||
İlerleme
|
<p className="text-xs text-gray-600">%{task.progress}</p>
|
||||||
</p>
|
|
||||||
<p className="text-xs text-gray-600">
|
|
||||||
%{task.progress}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
<div className="w-full bg-gray-200 rounded-full h-1.5">
|
||||||
<div
|
<div
|
||||||
className={`h-1.5 rounded-full ${
|
className={`h-1.5 rounded-full ${
|
||||||
task.progress >= 90
|
task.progress >= 90
|
||||||
? "bg-green-500"
|
? 'bg-green-500'
|
||||||
: task.progress >= 70
|
: task.progress >= 70
|
||||||
? "bg-blue-500"
|
? 'bg-blue-500'
|
||||||
: task.progress >= 50
|
: task.progress >= 50
|
||||||
? "bg-yellow-500"
|
? 'bg-yellow-500'
|
||||||
: "bg-red-500"
|
: 'bg-red-500'
|
||||||
}`}
|
}`}
|
||||||
style={{ width: `${task.progress}%` }}
|
style={{ width: `${task.progress}%` }}
|
||||||
/>
|
/>
|
||||||
|
|
@ -645,17 +568,14 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "documents" && (
|
{activeTab === 'documents' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Documents List */}
|
{/* Documents List */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{project.documents.length > 0 ? (
|
{project.documents.length > 0 ? (
|
||||||
project.documents.map((doc) => (
|
project.documents.map((doc) => (
|
||||||
<div
|
<div key={doc.id} className="px-3 py-2 hover:bg-gray-50">
|
||||||
key={doc.id}
|
|
||||||
className="px-3 py-2 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="w-7 h-7 bg-blue-100 rounded-lg flex items-center justify-center">
|
<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">
|
<div className="flex items-center space-x-3 text-xs text-gray-500 mt-1">
|
||||||
<span>{doc.documentType}</span>
|
<span>{doc.documentType}</span>
|
||||||
<span>{doc.fileSize} MB</span>
|
<span>{doc.fileSize} MB</span>
|
||||||
<span>
|
<span>{dayjs(doc.uploadedAt).format('DD.MM.YYYY')}</span>
|
||||||
{dayjs(doc.uploadedAt).format("DD.MM.YYYY")}
|
|
||||||
</span>
|
|
||||||
<span>Yükleyen: {doc.uploadedBy}</span>
|
<span>Yükleyen: {doc.uploadedBy}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -699,17 +617,14 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeTab === "risks" && (
|
{activeTab === 'risks' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Risks List */}
|
{/* Risks List */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
<div className="divide-y divide-gray-200">
|
<div className="divide-y divide-gray-200">
|
||||||
{project.risks.length > 0 ? (
|
{project.risks.length > 0 ? (
|
||||||
project.risks.map((risk) => (
|
project.risks.map((risk) => (
|
||||||
<div
|
<div key={risk.id} className="px-3 py-2 hover:bg-gray-50">
|
||||||
key={risk.id}
|
|
||||||
className="px-3 py-2 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center space-x-2 mb-1.5">
|
<div className="flex items-center space-x-2 mb-1.5">
|
||||||
|
|
@ -723,26 +638,19 @@ const ProjectView: React.FC = () => {
|
||||||
{risk.status}
|
{risk.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-600 mb-1.5">
|
<p className="text-sm text-gray-600 mb-1.5">{risk.description}</p>
|
||||||
{risk.description}
|
|
||||||
</p>
|
|
||||||
{risk.mitigationPlan && (
|
{risk.mitigationPlan && (
|
||||||
<div className="bg-gray-50 rounded-lg p-2 mb-2">
|
<div className="bg-gray-50 rounded-lg p-2 mb-2">
|
||||||
<p className="text-xs font-medium text-gray-700 mb-1">
|
<p className="text-xs font-medium text-gray-700 mb-1">
|
||||||
Önlem Planı:
|
Önlem Planı:
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-600">
|
<p className="text-xs text-gray-600">{risk.mitigationPlan}</p>
|
||||||
{risk.mitigationPlan}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center space-x-3 text-xs text-gray-500">
|
<div className="flex items-center space-x-3 text-xs text-gray-500">
|
||||||
<span>Risk Kodu: {risk.riskCode}</span>
|
<span>Risk Kodu: {risk.riskCode}</span>
|
||||||
<span>
|
<span>
|
||||||
Tanımlama:{" "}
|
Tanımlama: {dayjs(risk.identifiedDate).format('DD.MM.YYYY')}
|
||||||
{dayjs(risk.identifiedDate).format(
|
|
||||||
"DD.MM.YYYY"
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -771,7 +679,7 @@ const ProjectView: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Financial Summary Tab */}
|
{/* Financial Summary Tab */}
|
||||||
{activeTab === "financial" && (
|
{activeTab === 'financial' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Overview Cards */}
|
{/* Overview Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3">
|
<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" />
|
<FaDollarSign className="h-5 w-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<p className="text-sm font-medium text-gray-500">
|
<p className="text-sm font-medium text-gray-500">Toplam Bütçe</p>
|
||||||
Toplam Bütçe
|
|
||||||
</p>
|
|
||||||
<p className="text-base font-semibold text-gray-900">
|
<p className="text-base font-semibold text-gray-900">
|
||||||
₺{project.budget.toLocaleString()}
|
₺{project.budget.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -797,9 +703,7 @@ const ProjectView: React.FC = () => {
|
||||||
<FaDollarSign className="h-5 w-5 text-red-600" />
|
<FaDollarSign className="h-5 w-5 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<p className="text-sm font-medium text-gray-500">
|
<p className="text-sm font-medium text-gray-500">Harcanan</p>
|
||||||
Harcanan
|
|
||||||
</p>
|
|
||||||
<p className="text-base font-semibold text-gray-900">
|
<p className="text-base font-semibold text-gray-900">
|
||||||
₺{project.actualCost.toLocaleString()}
|
₺{project.actualCost.toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -813,14 +717,9 @@ const ProjectView: React.FC = () => {
|
||||||
<FaDollarSign className="h-5 w-5 text-green-600" />
|
<FaDollarSign className="h-5 w-5 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<p className="text-sm font-medium text-gray-500">
|
<p className="text-sm font-medium text-gray-500">Kalan Bütçe</p>
|
||||||
Kalan Bütçe
|
|
||||||
</p>
|
|
||||||
<p className="text-base font-semibold text-gray-900">
|
<p className="text-base font-semibold text-gray-900">
|
||||||
₺
|
₺{(project.budget - project.actualCost).toLocaleString()}
|
||||||
{(
|
|
||||||
project.budget - project.actualCost
|
|
||||||
).toLocaleString()}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -832,9 +731,7 @@ const ProjectView: React.FC = () => {
|
||||||
<FaChartLine className="h-5 w-5 text-orange-600" />
|
<FaChartLine className="h-5 w-5 text-orange-600" />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2">
|
<div className="ml-2">
|
||||||
<p className="text-sm font-medium text-gray-500">
|
<p className="text-sm font-medium text-gray-500">Kullanım Oranı</p>
|
||||||
Kullanım Oranı
|
|
||||||
</p>
|
|
||||||
<p className="text-base font-semibold text-gray-900">
|
<p className="text-base font-semibold text-gray-900">
|
||||||
%{budgetUsagePercentage.toFixed(1)}
|
%{budgetUsagePercentage.toFixed(1)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -850,19 +747,17 @@ const ProjectView: React.FC = () => {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span>
|
<span>Harcanan: ₺{project.actualCost.toLocaleString()}</span>
|
||||||
Harcanan: ₺{project.actualCost.toLocaleString()}
|
|
||||||
</span>
|
|
||||||
<span>Toplam: ₺{project.budget.toLocaleString()}</span>
|
<span>Toplam: ₺{project.budget.toLocaleString()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
<div className="w-full bg-gray-200 rounded-full h-2.5">
|
||||||
<div
|
<div
|
||||||
className={`h-2.5 rounded-full transition-all duration-300 ${
|
className={`h-2.5 rounded-full transition-all duration-300 ${
|
||||||
budgetUsagePercentage > 90
|
budgetUsagePercentage > 90
|
||||||
? "bg-red-500"
|
? 'bg-red-500'
|
||||||
: budgetUsagePercentage > 70
|
: budgetUsagePercentage > 70
|
||||||
? "bg-orange-500"
|
? 'bg-orange-500'
|
||||||
: "bg-green-500"
|
: 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.min(budgetUsagePercentage, 100)}%`,
|
width: `${Math.min(budgetUsagePercentage, 100)}%`,
|
||||||
|
|
@ -872,17 +767,17 @@ const ProjectView: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`text-xs font-medium ${
|
className={`text-xs font-medium ${
|
||||||
budgetUsagePercentage > 90
|
budgetUsagePercentage > 90
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: budgetUsagePercentage > 70
|
: budgetUsagePercentage > 70
|
||||||
? "text-orange-600"
|
? 'text-orange-600'
|
||||||
: "text-green-600"
|
: 'text-green-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{budgetUsagePercentage > 90
|
{budgetUsagePercentage > 90
|
||||||
? "Kritik Seviye - Bütçe Aşımı Riski"
|
? 'Kritik Seviye - Bütçe Aşımı Riski'
|
||||||
: budgetUsagePercentage > 70
|
: budgetUsagePercentage > 70
|
||||||
? "Dikkat - Yüksek Kullanım"
|
? 'Dikkat - Yüksek Kullanım'
|
||||||
: "Normal Seviye"}
|
: 'Normal Seviye'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -922,8 +817,7 @@ const ProjectView: React.FC = () => {
|
||||||
{mockProjectPhases
|
{mockProjectPhases
|
||||||
.filter((phase) => phase.projectId === project.id)
|
.filter((phase) => phase.projectId === project.id)
|
||||||
.map((phase) => {
|
.map((phase) => {
|
||||||
const phaseUsage =
|
const phaseUsage = (phase.actualCost / phase.budget) * 100
|
||||||
(phase.actualCost / phase.budget) * 100;
|
|
||||||
return (
|
return (
|
||||||
<tr key={phase.id} className="hover:bg-gray-50">
|
<tr key={phase.id} className="hover:bg-gray-50">
|
||||||
<td className="px-3 py-2 whitespace-nowrap">
|
<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">
|
<div className="text-sm font-medium text-gray-900">
|
||||||
{phase.name}
|
{phase.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">{phase.code}</div>
|
||||||
{phase.code}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
|
@ -943,10 +835,7 @@ const ProjectView: React.FC = () => {
|
||||||
₺{phase.actualCost.toLocaleString()}
|
₺{phase.actualCost.toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 whitespace-nowrap text-sm text-gray-900">
|
<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>
|
||||||
<td className="px-3 py-2 whitespace-nowrap">
|
<td className="px-3 py-2 whitespace-nowrap">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -954,16 +843,13 @@ const ProjectView: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
className={`h-1.5 rounded-full ${
|
className={`h-1.5 rounded-full ${
|
||||||
phaseUsage > 90
|
phaseUsage > 90
|
||||||
? "bg-red-500"
|
? 'bg-red-500'
|
||||||
: phaseUsage > 70
|
: phaseUsage > 70
|
||||||
? "bg-orange-500"
|
? 'bg-orange-500'
|
||||||
: "bg-green-500"
|
: 'bg-green-500'
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.min(
|
width: `${Math.min(phaseUsage, 100)}%`,
|
||||||
phaseUsage,
|
|
||||||
100
|
|
||||||
)}%`,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -976,25 +862,25 @@ const ProjectView: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
className={`inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium ${
|
||||||
phaseUsage > 100
|
phaseUsage > 100
|
||||||
? "bg-red-100 text-red-800"
|
? 'bg-red-100 text-red-800'
|
||||||
: phaseUsage > 90
|
: phaseUsage > 90
|
||||||
? "bg-orange-100 text-orange-800"
|
? 'bg-orange-100 text-orange-800'
|
||||||
: phaseUsage > 70
|
: phaseUsage > 70
|
||||||
? "bg-yellow-100 text-yellow-800"
|
? 'bg-yellow-100 text-yellow-800'
|
||||||
: "bg-green-100 text-green-800"
|
: 'bg-green-100 text-green-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{phaseUsage > 100
|
{phaseUsage > 100
|
||||||
? "Bütçe Aşımı"
|
? 'Bütçe Aşımı'
|
||||||
: phaseUsage > 90
|
: phaseUsage > 90
|
||||||
? "Kritik"
|
? 'Kritik'
|
||||||
: phaseUsage > 70
|
: phaseUsage > 70
|
||||||
? "Dikkat"
|
? 'Dikkat'
|
||||||
: "Normal"}
|
: 'Normal'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
)
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -1009,33 +895,25 @@ const ProjectView: React.FC = () => {
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Personel Maliyeti</span>
|
||||||
Personel Maliyeti
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺{(project.actualCost * 0.6).toLocaleString()}
|
₺{(project.actualCost * 0.6).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Malzeme Maliyeti</span>
|
||||||
Malzeme Maliyeti
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺{(project.actualCost * 0.25).toLocaleString()}
|
₺{(project.actualCost * 0.25).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">İş Merkezi Maliyeti</span>
|
||||||
İş Merkezi Maliyeti
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺{(project.actualCost * 0.1).toLocaleString()}
|
₺{(project.actualCost * 0.1).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Diğer Giderler</span>
|
||||||
Diğer Giderler
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺{(project.actualCost * 0.05).toLocaleString()}
|
₺{(project.actualCost * 0.05).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -1050,13 +928,11 @@ const ProjectView: React.FC = () => {
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between mb-1">
|
<div className="flex justify-between mb-1">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Ortalama Günlük Harcama</span>
|
||||||
Ortalama Günlük Harcama
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺
|
₺
|
||||||
{Math.round(
|
{Math.round(
|
||||||
project.actualCost / Math.max(timeElapsed, 1)
|
project.actualCost / Math.max(timeElapsed, 1),
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1069,29 +945,23 @@ const ProjectView: React.FC = () => {
|
||||||
<span className="text-sm font-medium text-gray-900">
|
<span className="text-sm font-medium text-gray-900">
|
||||||
₺
|
₺
|
||||||
{Math.round(
|
{Math.round(
|
||||||
(project.actualCost /
|
(project.actualCost / Math.max(project.progress, 1)) * 100,
|
||||||
Math.max(project.progress, 1)) *
|
|
||||||
100
|
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between mb-1">
|
<div className="flex justify-between mb-1">
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">Maliyet Varyansı</span>
|
||||||
Maliyet Varyansı
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium ${
|
className={`text-sm font-medium ${
|
||||||
project.actualCost > project.budget
|
project.actualCost > project.budget
|
||||||
? "text-red-600"
|
? 'text-red-600'
|
||||||
: "text-green-600"
|
: 'text-green-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{project.actualCost > project.budget ? "+" : ""}₺
|
{project.actualCost > project.budget ? '+' : ''}₺
|
||||||
{(
|
{(project.actualCost - project.budget).toLocaleString()}
|
||||||
project.actualCost - project.budget
|
|
||||||
).toLocaleString()}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1103,8 +973,8 @@ const ProjectView: React.FC = () => {
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-medium ${
|
className={`text-sm font-medium ${
|
||||||
project.budget / project.actualCost >= 1
|
project.budget / project.actualCost >= 1
|
||||||
? "text-green-600"
|
? 'text-green-600'
|
||||||
: "text-red-600"
|
: 'text-red-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{(project.budget / project.actualCost).toFixed(2)}
|
{(project.budget / project.actualCost).toFixed(2)}
|
||||||
|
|
@ -1120,7 +990,8 @@ const ProjectView: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Container>
|
||||||
};
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default ProjectView;
|
export default ProjectView
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,77 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from 'react'
|
||||||
import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from "react-icons/fa";
|
import { FaPlus, FaSave, FaEdit, FaTrash, FaCalendarAlt } from 'react-icons/fa'
|
||||||
import {
|
import { PsTaskDailyUpdate, WorkTypeEnum, DailyUpdateStatusEnum } from '../../../types/ps'
|
||||||
PsTaskDailyUpdate,
|
import { mockProjectTasks } from '../../../mocks/mockProjectTasks'
|
||||||
WorkTypeEnum,
|
import { mockEmployees } from '../../../mocks/mockEmployees'
|
||||||
DailyUpdateStatusEnum,
|
import { mockProjectTaskDailyUpdates } from '../../../mocks/mockProjectTaskDailyUpdates'
|
||||||
} from "../../../types/ps";
|
import Widget from '../../../components/common/Widget'
|
||||||
import { mockProjectTasks } from "../../../mocks/mockProjectTasks";
|
import { getDailyUpdateStatusColor, getWorkTypeColor } from '../../../utils/erp'
|
||||||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
import { Container } from '@/components/shared'
|
||||||
import { mockProjectTaskDailyUpdates } from "../../../mocks/mockProjectTaskDailyUpdates";
|
|
||||||
import Widget from "../../../components/common/Widget";
|
|
||||||
import {
|
|
||||||
getDailyUpdateStatusColor,
|
|
||||||
getWorkTypeColor,
|
|
||||||
} from "../../../utils/erp";
|
|
||||||
|
|
||||||
interface TaskDailyUpdatesFormData {
|
interface TaskDailyUpdatesFormData {
|
||||||
taskId: string;
|
taskId: string
|
||||||
date: string;
|
date: string
|
||||||
hoursWorked: number;
|
hoursWorked: number
|
||||||
description: string;
|
description: string
|
||||||
workType: WorkTypeEnum;
|
workType: WorkTypeEnum
|
||||||
progress: number;
|
progress: number
|
||||||
challenges?: string;
|
challenges?: string
|
||||||
nextSteps?: string;
|
nextSteps?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialFormData: TaskDailyUpdatesFormData = {
|
const initialFormData: TaskDailyUpdatesFormData = {
|
||||||
taskId: "",
|
taskId: '',
|
||||||
date: new Date().toISOString().split("T")[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
hoursWorked: 0,
|
hoursWorked: 0,
|
||||||
description: "",
|
description: '',
|
||||||
workType: WorkTypeEnum.Development,
|
workType: WorkTypeEnum.Development,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
challenges: "",
|
challenges: '',
|
||||||
nextSteps: "",
|
nextSteps: '',
|
||||||
};
|
}
|
||||||
|
|
||||||
const TaskDailyUpdates: React.FC = () => {
|
const TaskDailyUpdates: React.FC = () => {
|
||||||
const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>(
|
const [updates, setUpdates] = useState<PsTaskDailyUpdate[]>(mockProjectTaskDailyUpdates)
|
||||||
mockProjectTaskDailyUpdates
|
const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>(null)
|
||||||
);
|
const [formData, setFormData] = useState<TaskDailyUpdatesFormData>(initialFormData)
|
||||||
const [editingUpdate, setEditingUpdate] = useState<PsTaskDailyUpdate | null>(
|
const [selectedDate, setSelectedDate] = useState<string>(new Date().toISOString().split('T')[0])
|
||||||
null
|
const [selectedEmployee, setSelectedEmployee] = useState<string>(mockEmployees[0].id)
|
||||||
);
|
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||||
const [formData, setFormData] =
|
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||||
useState<TaskDailyUpdatesFormData>(initialFormData);
|
const [selectedEmployeeForForm, setSelectedEmployeeForForm] = useState<string>(
|
||||||
const [selectedDate, setSelectedDate] = useState<string>(
|
mockEmployees[0].id,
|
||||||
new Date().toISOString().split("T")[0]
|
)
|
||||||
);
|
|
||||||
const [selectedEmployee, setSelectedEmployee] = useState<string>(
|
|
||||||
mockEmployees[0].id
|
|
||||||
);
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
||||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
|
||||||
const [selectedEmployeeForForm, setSelectedEmployeeForForm] =
|
|
||||||
useState<string>(mockEmployees[0].id);
|
|
||||||
|
|
||||||
// Filter updates based on selected filters
|
// Filter updates based on selected filters
|
||||||
const filteredUpdates = updates.filter((update) => {
|
const filteredUpdates = updates.filter((update) => {
|
||||||
const matchesDate =
|
const matchesDate = !selectedDate || update.date.toISOString().split('T')[0] === selectedDate
|
||||||
!selectedDate || update.date.toISOString().split("T")[0] === selectedDate;
|
const matchesEmployee = update.employeeId === selectedEmployee
|
||||||
const matchesEmployee = update.employeeId === selectedEmployee;
|
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
!searchQuery ||
|
!searchQuery ||
|
||||||
update.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
update.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
update.task?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
update.task?.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
update.employee?.firstName
|
update.employee?.firstName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
?.toLowerCase()
|
update.employee?.lastName?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
.includes(searchQuery.toLowerCase()) ||
|
return matchesDate && matchesEmployee && matchesSearch
|
||||||
update.employee?.lastName
|
})
|
||||||
?.toLowerCase()
|
|
||||||
.includes(searchQuery.toLowerCase());
|
|
||||||
return matchesDate && matchesEmployee && matchesSearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get tasks for the selected employee in form
|
// Get tasks for the selected employee in form
|
||||||
const formUserTasks = mockProjectTasks.filter(
|
const formUserTasks = mockProjectTasks.filter(
|
||||||
(task) => task.assignedTo === selectedEmployeeForForm
|
(task) => task.assignedTo === selectedEmployeeForForm,
|
||||||
);
|
)
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
|
||||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
||||||
>
|
|
||||||
) => {
|
) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target
|
||||||
setFormData((prev) => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[name]:
|
[name]: name === 'hoursWorked' || name === 'progress' ? Number(value) : value,
|
||||||
name === "hoursWorked" || name === "progress" ? Number(value) : value,
|
}))
|
||||||
}));
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
const newUpdate: PsTaskDailyUpdate = {
|
const newUpdate: PsTaskDailyUpdate = {
|
||||||
id: editingUpdate ? editingUpdate.id : Date.now().toString(),
|
id: editingUpdate ? editingUpdate.id : Date.now().toString(),
|
||||||
|
|
@ -112,86 +90,268 @@ const TaskDailyUpdates: React.FC = () => {
|
||||||
attachments: [],
|
attachments: [],
|
||||||
creationTime: editingUpdate ? editingUpdate.creationTime : new Date(),
|
creationTime: editingUpdate ? editingUpdate.creationTime : new Date(),
|
||||||
lastModificationTime: new Date(),
|
lastModificationTime: new Date(),
|
||||||
};
|
}
|
||||||
|
|
||||||
if (editingUpdate) {
|
if (editingUpdate) {
|
||||||
setUpdates((prev) =>
|
setUpdates((prev) =>
|
||||||
prev.map((update) =>
|
prev.map((update) => (update.id === editingUpdate.id ? newUpdate : update)),
|
||||||
update.id === editingUpdate.id ? newUpdate : update
|
|
||||||
)
|
)
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setUpdates((prev) => [...prev, newUpdate]);
|
setUpdates((prev) => [...prev, newUpdate])
|
||||||
}
|
}
|
||||||
|
|
||||||
resetForm();
|
resetForm()
|
||||||
};
|
}
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormData(initialFormData);
|
setFormData(initialFormData)
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false)
|
||||||
setEditingUpdate(null);
|
setEditingUpdate(null)
|
||||||
setSelectedEmployeeForForm(mockEmployees[0].id);
|
setSelectedEmployeeForForm(mockEmployees[0].id)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleEdit = (update: PsTaskDailyUpdate) => {
|
const handleEdit = (update: PsTaskDailyUpdate) => {
|
||||||
setEditingUpdate(update);
|
setEditingUpdate(update)
|
||||||
setSelectedEmployeeForForm(update.employeeId);
|
setSelectedEmployeeForForm(update.employeeId)
|
||||||
setFormData({
|
setFormData({
|
||||||
taskId: update.taskId,
|
taskId: update.taskId,
|
||||||
date: update.date.toISOString().split("T")[0],
|
date: update.date.toISOString().split('T')[0],
|
||||||
hoursWorked: update.hoursWorked,
|
hoursWorked: update.hoursWorked,
|
||||||
description: update.description,
|
description: update.description,
|
||||||
workType: update.workType,
|
workType: update.workType,
|
||||||
progress: update.progress,
|
progress: update.progress,
|
||||||
challenges: update.challenges || "",
|
challenges: update.challenges || '',
|
||||||
nextSteps: update.nextSteps || "",
|
nextSteps: update.nextSteps || '',
|
||||||
});
|
})
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
if (
|
if (window.confirm('Bu günlük raporu silmek istediğinizden emin misiniz?')) {
|
||||||
window.confirm("Bu günlük raporu silmek istediğinizden emin misiniz?")
|
setUpdates((prev) => prev.filter((update) => update.id !== id))
|
||||||
) {
|
}
|
||||||
setUpdates((prev) => prev.filter((update) => update.id !== id));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleStatusChange = (id: string, status: DailyUpdateStatusEnum) => {
|
const handleStatusChange = (id: string, status: DailyUpdateStatusEnum) => {
|
||||||
setUpdates((prev) =>
|
setUpdates((prev) => prev.map((update) => (update.id === id ? { ...update, status } : update)))
|
||||||
prev.map((update) => (update.id === id ? { ...update, status } : update))
|
}
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const todayTotalHours = filteredUpdates
|
const todayTotalHours = filteredUpdates
|
||||||
.filter(
|
.filter(
|
||||||
(update) =>
|
(update) =>
|
||||||
update.date.toISOString().split("T")[0] ===
|
update.date.toISOString().split('T')[0] === new Date().toISOString().split('T')[0],
|
||||||
new Date().toISOString().split("T")[0]
|
|
||||||
)
|
)
|
||||||
.reduce((sum, update) => sum + update.hoursWorked, 0);
|
.reduce((sum, update) => sum + update.hoursWorked, 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<Container>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-4 mt-2">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-900">Günlük Aktivite Takibi</h2>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
Günlük işlerinizi planlayın, tamamlanan görevleri işaretleyin
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsModalVisible(true)}
|
||||||
|
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2 transition-colors shadow-sm"
|
||||||
|
>
|
||||||
|
<FaPlus className="w-4 h-4" />
|
||||||
|
Yeni Günlük Rapor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-4 gap-2">
|
||||||
|
<Widget
|
||||||
|
title="Bugün Toplam"
|
||||||
|
value={`${todayTotalHours}h`}
|
||||||
|
color="blue"
|
||||||
|
icon="FaClock"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Widget
|
||||||
|
title="Onaylanan"
|
||||||
|
value={
|
||||||
|
filteredUpdates.filter((update) => update.status === DailyUpdateStatusEnum.Approved)
|
||||||
|
.length
|
||||||
|
}
|
||||||
|
color="green"
|
||||||
|
icon="FaCheckCircle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Widget
|
||||||
|
title="Beklemede"
|
||||||
|
value={
|
||||||
|
filteredUpdates.filter(
|
||||||
|
(update) => update.status === DailyUpdateStatusEnum.Submitted,
|
||||||
|
).length
|
||||||
|
}
|
||||||
|
color="yellow"
|
||||||
|
icon="FaHourglassHalf"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Widget
|
||||||
|
title="Taslak Rapor"
|
||||||
|
value={
|
||||||
|
filteredUpdates.filter((update) => update.status === DailyUpdateStatusEnum.Draft)
|
||||||
|
.length
|
||||||
|
}
|
||||||
|
color="gray"
|
||||||
|
icon="FaFileAlt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="bg-white grid grid-cols-6 gap-2 p-2 rounded-lg shadow-sm border border-gray-200 mt-3 mb-3">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<select
|
||||||
|
value={selectedEmployee}
|
||||||
|
onChange={(e) => setSelectedEmployee(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
>
|
||||||
|
{mockEmployees.map((employee) => (
|
||||||
|
<option key={employee.id} value={employee.id}>
|
||||||
|
{employee.firstName} {employee.lastName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Açıklama, görev veya çalışan adı..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1">
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={selectedDate}
|
||||||
|
onChange={(e) => setSelectedDate(e.target.value)}
|
||||||
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Updates List */}
|
||||||
|
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<div className="p-1.5 border-b border-gray-200">
|
||||||
|
<h3 className="text-base font-semibold text-gray-900 ml-1.5">Günlük Raporlar</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="divide-y divide-gray-200">
|
||||||
|
{filteredUpdates.length === 0 ? (
|
||||||
|
<div className="p-4 text-center text-gray-500">
|
||||||
|
<FaCalendarAlt className="h-10 w-10 mx-auto text-gray-300 mb-2" />
|
||||||
|
<p className="text-sm">Henüz günlük rapor bulunmuyor.</p>
|
||||||
|
<p className="text-sm">Yeni bir rapor eklemek için yukarıdaki butonu kullanın.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredUpdates.map((update) => (
|
||||||
|
<div key={update.id} className="p-3 hover:bg-gray-50">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1.5">
|
||||||
|
<h4 className="font-medium text-sm text-gray-900">
|
||||||
|
{update.task?.name || 'Bilinmeyen Görev'}
|
||||||
|
</h4>
|
||||||
|
<span
|
||||||
|
className={`px-2 py-1 text-xs rounded-full ${getDailyUpdateStatusColor(
|
||||||
|
update.status,
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
{update.status}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`px-2 py-1 text-xs rounded-full ${getWorkTypeColor(
|
||||||
|
update.workType,
|
||||||
|
)}`}
|
||||||
|
>
|
||||||
|
{update.workType}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-3 mb-2 text-xs text-gray-600">
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Tarih:</span>{' '}
|
||||||
|
{update.date.toLocaleDateString('tr-TR')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Çalışılan Saat:</span> {update.hoursWorked}h
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">İlerleme:</span> %{update.progress}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-700 mb-2">{update.description}</p>
|
||||||
|
|
||||||
|
{update.challenges && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-xs font-medium text-red-700">Zorluklar:</span>
|
||||||
|
<p className="text-sm text-gray-600">{update.challenges}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{update.nextSteps && (
|
||||||
|
<div>
|
||||||
|
<span className="text-xs font-medium text-blue-700">
|
||||||
|
Sonraki Adımlar:
|
||||||
|
</span>
|
||||||
|
<p className="text-sm text-gray-600">{update.nextSteps}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{update.status === DailyUpdateStatusEnum.Draft && (
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
handleStatusChange(update.id, DailyUpdateStatusEnum.Submitted)
|
||||||
|
}
|
||||||
|
className="px-2 py-0.5 text-xs bg-blue-600 text-white hover:bg-blue-700 rounded"
|
||||||
|
>
|
||||||
|
Gönder
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => handleEdit(update)}
|
||||||
|
className="p-1 text-gray-400 hover:text-blue-600"
|
||||||
|
>
|
||||||
|
<FaEdit className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDelete(update.id)}
|
||||||
|
className="p-1 text-gray-400 hover:text-red-600"
|
||||||
|
>
|
||||||
|
<FaTrash className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{isModalVisible && (
|
{isModalVisible && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div className="bg-white rounded-lg shadow-xl p-3 w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
<div className="bg-white rounded-lg shadow-xl p-3 w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h3 className="text-base font-semibold text-gray-900">
|
<h3 className="text-base font-semibold text-gray-900">
|
||||||
{editingUpdate ? "Günlük Rapor Düzenle" : "Yeni Günlük Rapor"}
|
{editingUpdate ? 'Günlük Rapor Düzenle' : 'Yeni Günlük Rapor'}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button onClick={resetForm} className="text-gray-400 hover:text-gray-600">
|
||||||
onClick={resetForm}
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
className="text-gray-400 hover:text-gray-600"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
|
|
@ -211,8 +371,8 @@ const TaskDailyUpdates: React.FC = () => {
|
||||||
<select
|
<select
|
||||||
value={selectedEmployeeForForm}
|
value={selectedEmployeeForForm}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedEmployeeForForm(e.target.value);
|
setSelectedEmployeeForForm(e.target.value)
|
||||||
setFormData((prev) => ({ ...prev, taskId: "" })); // Reset task selection
|
setFormData((prev) => ({ ...prev, taskId: '' })) // Reset task selection
|
||||||
}}
|
}}
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||||
>
|
>
|
||||||
|
|
@ -239,8 +399,8 @@ const TaskDailyUpdates: React.FC = () => {
|
||||||
<option value="">Görev Seçin</option>
|
<option value="">Görev Seçin</option>
|
||||||
{formUserTasks.map((task) => (
|
{formUserTasks.map((task) => (
|
||||||
<option key={task.id} value={task.id}>
|
<option key={task.id} value={task.id}>
|
||||||
{task.phase?.project?.name || "Proje"} →{" "}
|
{task.phase?.project?.name || 'Proje'} → {task.phase?.name || 'Aşama'} →{' '}
|
||||||
{task.phase?.name || "Aşama"} → {task.name}
|
{task.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -263,9 +423,7 @@ const TaskDailyUpdates: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">İş Türü</label>
|
||||||
İş Türü
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
name="workType"
|
name="workType"
|
||||||
value={formData.workType}
|
value={formData.workType}
|
||||||
|
|
@ -371,231 +529,15 @@ const TaskDailyUpdates: React.FC = () => {
|
||||||
className="bg-blue-600 text-white py-1 px-3 text-sm rounded-md hover:bg-blue-700 flex items-center justify-center gap-2"
|
className="bg-blue-600 text-white py-1 px-3 text-sm rounded-md hover:bg-blue-700 flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<FaSave className="h-4 w-4" />
|
<FaSave className="h-4 w-4" />
|
||||||
{editingUpdate ? "Güncelle" : "Kaydet"}
|
{editingUpdate ? 'Güncelle' : 'Kaydet'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</Container>
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-4 mt-2">
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-lg font-bold text-gray-900">
|
|
||||||
Günlük Aktivite Takibi
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
Günlük işlerinizi planlayın, tamamlanan görevleri işaretleyin
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsModalVisible(true)}
|
|
||||||
className="bg-blue-600 text-white px-3 py-1.5 text-sm rounded-lg hover:bg-blue-700 flex items-center gap-2 transition-colors shadow-sm"
|
|
||||||
>
|
|
||||||
<FaPlus className="w-4 h-4" />
|
|
||||||
Yeni Günlük Rapor
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats */}
|
|
||||||
<div className="grid grid-cols-4 gap-2">
|
|
||||||
<Widget
|
|
||||||
title="Bugün Toplam"
|
|
||||||
value={`${todayTotalHours}h`}
|
|
||||||
color="blue"
|
|
||||||
icon="FaClock"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Widget
|
|
||||||
title="Onaylanan"
|
|
||||||
value={
|
|
||||||
filteredUpdates.filter(
|
|
||||||
(update) => update.status === DailyUpdateStatusEnum.Approved
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
color="green"
|
|
||||||
icon="FaCheckCircle"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Widget
|
|
||||||
title="Beklemede"
|
|
||||||
value={
|
|
||||||
filteredUpdates.filter(
|
|
||||||
(update) => update.status === DailyUpdateStatusEnum.Submitted
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
color="yellow"
|
|
||||||
icon="FaHourglassHalf"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Widget
|
|
||||||
title="Taslak Rapor"
|
|
||||||
value={
|
|
||||||
filteredUpdates.filter(
|
|
||||||
(update) => update.status === DailyUpdateStatusEnum.Draft
|
|
||||||
).length
|
|
||||||
}
|
|
||||||
color="gray"
|
|
||||||
icon="FaFileAlt"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filters */}
|
|
||||||
<div className="bg-white grid grid-cols-6 gap-2 p-2 rounded-lg shadow-sm border border-gray-200 mt-3 mb-3">
|
|
||||||
<div className="col-span-2">
|
|
||||||
<select
|
|
||||||
value={selectedEmployee}
|
|
||||||
onChange={(e) => setSelectedEmployee(e.target.value)}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
>
|
|
||||||
{mockEmployees.map((employee) => (
|
|
||||||
<option key={employee.id} value={employee.id}>
|
|
||||||
{employee.firstName} {employee.lastName}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-3">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Açıklama, görev veya çalışan adı..."
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-1">
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
value={selectedDate}
|
|
||||||
onChange={(e) => setSelectedDate(e.target.value)}
|
|
||||||
className="w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Updates List */}
|
|
||||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
|
|
||||||
<div className="p-1.5 border-b border-gray-200">
|
|
||||||
<h3 className="text-base font-semibold text-gray-900 ml-1.5">
|
|
||||||
Günlük Raporlar
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="divide-y divide-gray-200">
|
|
||||||
{filteredUpdates.length === 0 ? (
|
|
||||||
<div className="p-4 text-center text-gray-500">
|
|
||||||
<FaCalendarAlt className="h-10 w-10 mx-auto text-gray-300 mb-2" />
|
|
||||||
<p className="text-sm">Henüz günlük rapor bulunmuyor.</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
Yeni bir rapor eklemek için yukarıdaki butonu kullanın.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
filteredUpdates.map((update) => (
|
|
||||||
<div key={update.id} className="p-3 hover:bg-gray-50">
|
|
||||||
<div className="flex items-start justify-between gap-3">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-1.5">
|
|
||||||
<h4 className="font-medium text-sm text-gray-900">
|
|
||||||
{update.task?.name || "Bilinmeyen Görev"}
|
|
||||||
</h4>
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 text-xs rounded-full ${getDailyUpdateStatusColor(
|
|
||||||
update.status
|
|
||||||
)}`}
|
|
||||||
>
|
|
||||||
{update.status}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 text-xs rounded-full ${getWorkTypeColor(
|
|
||||||
update.workType
|
|
||||||
)}`}
|
|
||||||
>
|
|
||||||
{update.workType}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-3 mb-2 text-xs text-gray-600">
|
|
||||||
<div>
|
|
||||||
<span className="font-medium">Tarih:</span>{" "}
|
|
||||||
{update.date.toLocaleDateString("tr-TR")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="font-medium">Çalışılan Saat:</span>{" "}
|
|
||||||
{update.hoursWorked}h
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="font-medium">İlerleme:</span> %
|
|
||||||
{update.progress}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-sm text-gray-700 mb-2">
|
|
||||||
{update.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{update.challenges && (
|
|
||||||
<div className="mb-2">
|
|
||||||
<span className="text-xs font-medium text-red-700">
|
|
||||||
Zorluklar:
|
|
||||||
</span>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{update.challenges}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{update.nextSteps && (
|
|
||||||
<div>
|
|
||||||
<span className="text-xs font-medium text-blue-700">
|
|
||||||
Sonraki Adımlar:
|
|
||||||
</span>
|
|
||||||
<p className="text-sm text-gray-600">
|
|
||||||
{update.nextSteps}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{update.status === DailyUpdateStatusEnum.Draft && (
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
handleStatusChange(
|
|
||||||
update.id,
|
|
||||||
DailyUpdateStatusEnum.Submitted
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="px-2 py-0.5 text-xs bg-blue-600 text-white hover:bg-blue-700 rounded"
|
|
||||||
>
|
|
||||||
Gönder
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => handleEdit(update)}
|
|
||||||
className="p-1 text-gray-400 hover:text-blue-600"
|
|
||||||
>
|
|
||||||
<FaEdit className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => handleDelete(update.id)}
|
|
||||||
className="p-1 text-gray-400 hover:text-red-600"
|
|
||||||
>
|
|
||||||
<FaTrash className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TaskDailyUpdates;
|
export default TaskDailyUpdates
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ const ProgressBar: React.FC<{ progress: number }> = ({ progress }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="font-medium">İlerleme</span>
|
<span className="font-medium">İlerleme</span>
|
||||||
<span>%{progress}</span>
|
<span>%{progress}</span>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue