535 lines
19 KiB
TypeScript
535 lines
19 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
||
import {
|
||
FaUsers,
|
||
FaArrowLeft,
|
||
FaSave,
|
||
FaTimes,
|
||
FaPlus,
|
||
FaTrash,
|
||
FaMapMarkerAlt,
|
||
} from "react-icons/fa";
|
||
import { useNavigate, useParams } from "react-router-dom";
|
||
import { CrmTerritory } from "../../../types/crm";
|
||
import mockSalesTeams from "../../../mocks/mockSalesTeams";
|
||
import { mockEmployees } from "../../../mocks/mockEmployees";
|
||
import { Team, TeamMember, TeamRoleEnum } from "../../../types/common";
|
||
|
||
const SalesTeamEdit: React.FC = () => {
|
||
const navigate = useNavigate();
|
||
const { id } = useParams<{ id: string }>();
|
||
|
||
const [formData, setFormData] = useState({
|
||
teamCode: "",
|
||
name: "",
|
||
description: "",
|
||
managerId: "",
|
||
isActive: true,
|
||
});
|
||
|
||
const [members, setMembers] = useState<Partial<TeamMember>[]>([]);
|
||
const [territories, setTerritories] = useState<Partial<CrmTerritory>[]>([]);
|
||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
// Load team data on component mount
|
||
useEffect(() => {
|
||
const team = mockSalesTeams.find((t) => t.id === id);
|
||
if (team) {
|
||
setFormData({
|
||
teamCode: team.code,
|
||
name: team.name,
|
||
description: team.description || "",
|
||
managerId: team.managerId,
|
||
isActive: team.isActive,
|
||
});
|
||
setMembers(team.members || []);
|
||
setTerritories(team.territories || []);
|
||
}
|
||
setLoading(false);
|
||
}, [id]);
|
||
|
||
const handleInputChange = (field: string, value: string | boolean) => {
|
||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||
if (errors[field]) {
|
||
setErrors((prev) => ({ ...prev, [field]: "" }));
|
||
}
|
||
};
|
||
|
||
const addMember = () => {
|
||
setMembers((prev) => [
|
||
...prev,
|
||
{
|
||
employeeId: "",
|
||
role: TeamRoleEnum.Member,
|
||
isActive: true,
|
||
},
|
||
]);
|
||
};
|
||
|
||
const removeMember = (index: number) => {
|
||
setMembers((prev) => prev.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const updateMember = (
|
||
index: number,
|
||
field: string,
|
||
value: string | TeamRoleEnum | boolean
|
||
) => {
|
||
setMembers((prev) =>
|
||
prev.map((member, i) =>
|
||
i === index ? { ...member, [field]: value } : member
|
||
)
|
||
);
|
||
};
|
||
|
||
const addTerritory = () => {
|
||
setTerritories((prev) => [
|
||
...prev,
|
||
{
|
||
territoryCode: "",
|
||
name: "",
|
||
description: "",
|
||
region: "",
|
||
countries: [],
|
||
cities: [],
|
||
isActive: true,
|
||
},
|
||
]);
|
||
};
|
||
|
||
const removeTerritory = (index: number) => {
|
||
setTerritories((prev) => prev.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const updateTerritory = (
|
||
index: number,
|
||
field: string,
|
||
value: string | string[] | boolean
|
||
) => {
|
||
setTerritories((prev) =>
|
||
prev.map((territory, i) =>
|
||
i === index ? { ...territory, [field]: value } : territory
|
||
)
|
||
);
|
||
};
|
||
|
||
const validateForm = () => {
|
||
const newErrors: Record<string, string> = {};
|
||
|
||
if (!formData.teamCode.trim()) {
|
||
newErrors.teamCode = "Takım kodu zorunludur";
|
||
}
|
||
if (!formData.name.trim()) {
|
||
newErrors.name = "Takım adı zorunludur";
|
||
}
|
||
if (!formData.managerId.trim()) {
|
||
newErrors.managerId = "Takım lideri seçimi zorunludur";
|
||
}
|
||
|
||
setErrors(newErrors);
|
||
return Object.keys(newErrors).length === 0;
|
||
};
|
||
|
||
const handleSave = () => {
|
||
if (!validateForm()) return;
|
||
|
||
const updatedTeam: Partial<Team> = {
|
||
id,
|
||
...formData,
|
||
members: members.map((member, index) => ({
|
||
...member,
|
||
id: member.id || `member-updated-${index}`,
|
||
teamId: id || "",
|
||
joinDate: member.joinDate || new Date(),
|
||
})) as TeamMember[],
|
||
territories: territories.map((territory, index) => ({
|
||
...territory,
|
||
id: territory.id || `territory-updated-${index}`,
|
||
assignedTeamId: id || "",
|
||
})) as CrmTerritory[],
|
||
lastModificationTime: new Date(),
|
||
};
|
||
|
||
console.log("Updating sales team:", updatedTeam);
|
||
alert("Satış ekibi başarıyla güncellendi!");
|
||
navigate("/admin/crm/sales-teams");
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
if (
|
||
confirm("Değişiklikler kaydedilmedi. Çıkmak istediğinizden emin misiniz?")
|
||
) {
|
||
navigate("/admin/crm/sales-teams");
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-96">
|
||
<div className="text-center">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
|
||
<p className="mt-4 text-gray-600">Yükleniyor...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const team = mockSalesTeams.find((t) => t.id === id);
|
||
if (!team) {
|
||
return (
|
||
<div className="text-center py-12">
|
||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||
Satış ekibi bulunamadı
|
||
</h3>
|
||
<p className="text-gray-500 mb-4">
|
||
Aradığınız satış ekibi mevcut değil veya silinmiş olabilir.
|
||
</p>
|
||
<button
|
||
onClick={() => navigate("/admin/crm/sales-teams")}
|
||
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||
>
|
||
Geri Dön
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-4 pt-2">
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
<button
|
||
onClick={() => navigate("/admin/crm/sales-teams")}
|
||
className="p-1.5 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
|
||
>
|
||
<FaArrowLeft className="w-5 h-5" />
|
||
</button>
|
||
<div>
|
||
<h2 className="text-xl font-bold text-gray-900">
|
||
Satış Ekibi Düzenle
|
||
</h2>
|
||
<p className="text-sm text-gray-600 mt-1">
|
||
{team.name} ekibini düzenleyin
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={handleCancel}
|
||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 transition-colors"
|
||
>
|
||
<FaTimes className="w-4 h-4" />
|
||
İptal
|
||
</button>
|
||
<button
|
||
onClick={handleSave}
|
||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||
>
|
||
<FaSave className="w-4 h-4" />
|
||
Güncelle
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Form */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||
{/* Basic Information */}
|
||
<div className="lg:col-span-2">
|
||
<div className="bg-white rounded-lg shadow-sm border p-4">
|
||
<h3 className="text-base font-semibold text-gray-900 mb-4 flex items-center">
|
||
<FaUsers className="w-5 h-5 mr-2" />
|
||
Temel Bilgiler
|
||
</h3>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Takım Kodu *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.teamCode}
|
||
onChange={(e) =>
|
||
handleInputChange("teamCode", e.target.value)
|
||
}
|
||
className={`w-full px-3 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||
errors.teamCode ? "border-red-500" : "border-gray-300"
|
||
}`}
|
||
placeholder="ST-001"
|
||
/>
|
||
{errors.teamCode && (
|
||
<p className="text-red-500 text-sm mt-1">{errors.teamCode}</p>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Takım Lideri *
|
||
</label>
|
||
|
||
<select
|
||
value={formData.managerId}
|
||
onChange={(e) =>
|
||
handleInputChange("managerId", e.target.value)
|
||
}
|
||
className={`w-full px-3 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||
errors.managerId ? "border-red-500" : "border-gray-300"
|
||
}`}
|
||
>
|
||
<option value="">Lider seçin</option>
|
||
{mockEmployees.map((employee) => (
|
||
<option key={employee.id} value={employee.id}>
|
||
{employee.fullName}
|
||
</option>
|
||
))}
|
||
</select>
|
||
{errors.managerId && (
|
||
<p className="text-red-500 text-sm mt-1">
|
||
{errors.managerId}
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-4">
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Takım Adı *
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={formData.name}
|
||
onChange={(e) => handleInputChange("name", e.target.value)}
|
||
className={`w-full px-3 py-1.5 text-sm border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||
errors.name ? "border-red-500" : "border-gray-300"
|
||
}`}
|
||
placeholder="Kurumsal Satış Ekibi"
|
||
/>
|
||
{errors.name && (
|
||
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
|
||
)}
|
||
</div>
|
||
|
||
<div className="mt-4">
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Açıklama
|
||
</label>
|
||
<textarea
|
||
value={formData.description}
|
||
onChange={(e) =>
|
||
handleInputChange("description", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
rows={3}
|
||
placeholder="Ekip açıklaması..."
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-4">
|
||
<label className="flex items-center">
|
||
<input
|
||
type="checkbox"
|
||
checked={formData.isActive}
|
||
onChange={(e) =>
|
||
handleInputChange("isActive", e.target.checked)
|
||
}
|
||
className="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||
/>
|
||
<span className="ml-2 text-sm text-gray-700">Aktif</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Team Members */}
|
||
<div className="bg-white rounded-lg shadow-sm border p-4 mt-4">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-base font-semibold text-gray-900 flex items-center">
|
||
<FaUsers className="w-5 h-5 mr-2" />
|
||
Ekip Üyeleri
|
||
</h3>
|
||
<button
|
||
onClick={addMember}
|
||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-blue-600 border border-blue-200 rounded-md hover:bg-blue-50 transition-colors"
|
||
>
|
||
<FaPlus className="w-4 h-4" />
|
||
Üye Ekle
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
{members.map((member, index) => (
|
||
<div
|
||
key={index}
|
||
className="flex items-center gap-3 p-3 border border-gray-200 rounded-lg"
|
||
>
|
||
<div className="flex-1 grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Çalışan
|
||
</label>
|
||
|
||
<select
|
||
value={member.employeeId || ""}
|
||
onChange={(e) =>
|
||
updateMember(index, "employeeId", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
>
|
||
<option value="">Çalışan seçin</option>
|
||
{mockEmployees.map((employee) => (
|
||
<option key={employee.id} value={employee.id}>
|
||
{employee.fullName}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Rol
|
||
</label>
|
||
<select
|
||
value={member.role || TeamRoleEnum.Member}
|
||
onChange={(e) =>
|
||
updateMember(
|
||
index,
|
||
"role",
|
||
e.target.value as TeamRoleEnum
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
>
|
||
<option value={TeamRoleEnum.Member}>Üye</option>
|
||
<option value={TeamRoleEnum.Lead}>Takım Lideri</option>
|
||
<option value={TeamRoleEnum.Manager}>Yönetici</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => removeMember(index)}
|
||
className="p-1.5 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||
>
|
||
<FaTrash className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
|
||
{members.length === 0 && (
|
||
<div className="text-center py-8 text-gray-500">
|
||
<FaUsers className="w-8 h-8 mx-auto mb-2 text-gray-400" />
|
||
<p>Henüz ekip üyesi yok</p>
|
||
<p className="text-sm">
|
||
Üye eklemek için yukarıdaki butona tıklayın
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Territories */}
|
||
<div>
|
||
<div className="bg-white rounded-lg shadow-sm border p-4">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-base font-semibold text-gray-900 flex items-center">
|
||
<FaMapMarkerAlt className="w-5 h-5 mr-2" />
|
||
Bölgeler
|
||
</h3>
|
||
<button
|
||
onClick={addTerritory}
|
||
className="flex items-center gap-2 px-3 py-1.5 text-sm text-blue-600 border border-blue-200 rounded-md hover:bg-blue-50 transition-colors"
|
||
>
|
||
<FaPlus className="w-4 h-4" />
|
||
Bölge Ekle
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
{territories.map((territory, index) => (
|
||
<div
|
||
key={index}
|
||
className="p-3 border border-gray-200 rounded-lg"
|
||
>
|
||
<div className="flex justify-between items-start mb-3">
|
||
<h4 className="font-medium text-gray-900">
|
||
{territory.name || `Bölge ${index + 1}`}
|
||
</h4>
|
||
<button
|
||
onClick={() => removeTerritory(index)}
|
||
className="p-1.5 text-red-600 hover:bg-red-50 rounded"
|
||
>
|
||
<FaTrash className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Bölge Kodu
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={territory.territoryCode || ""}
|
||
onChange={(e) =>
|
||
updateTerritory(
|
||
index,
|
||
"territoryCode",
|
||
e.target.value
|
||
)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
placeholder="TR-IST"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Bölge Adı
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={territory.name || ""}
|
||
onChange={(e) =>
|
||
updateTerritory(index, "name", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
placeholder="İstanbul Anadolu"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-medium text-gray-700 mb-1">
|
||
Bölge
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={territory.region || ""}
|
||
onChange={(e) =>
|
||
updateTerritory(index, "region", e.target.value)
|
||
}
|
||
className="w-full px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
placeholder="Marmara"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
|
||
{territories.length === 0 && (
|
||
<div className="text-center py-8 text-gray-500">
|
||
<FaMapMarkerAlt className="w-8 h-8 mx-auto mb-2 text-gray-400" />
|
||
<p>Henüz bölge yok</p>
|
||
<p className="text-sm">
|
||
Bölge eklemek için yukarıdaki butona tıklayın
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SalesTeamEdit;
|