erp-platform/ui/src/views/maintenance/components/MaintenanceTeams.tsx
2025-09-16 00:29:07 +03:00

476 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react'
import {
FaPlus,
FaSearch,
FaFilter,
FaUsers,
FaUser,
FaEdit,
FaTrash,
FaEye,
FaPhone,
FaEnvelope,
FaAward,
FaClock,
} from 'react-icons/fa'
import { TeamRoleEnum } from '../../../types/common'
import { mockMaintenanceTeams } from '../../../mocks/mockMaintenanceTeams'
import NewTeamModal from './NewTeamModal'
import ViewTeamModal from './ViewTeamModal'
import EditTeamModal from './EditTeamModal'
import AssignWorkOrderModal from './AssignWorkOrderModal'
import TeamStatusChangeModal from './TeamStatusChangeModal'
import Widget from '../../../components/common/Widget'
import { Team } from '../../../types/common'
import { getTeamRoleColor, getTeamRoleIcon, getTeamRoleText } from '../../../utils/erp'
import { Container } from '@/components/shared'
const MaintenanceTeams: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('')
const [roleFilter, setRoleFilter] = useState<TeamRoleEnum | 'all'>('all')
const [statusFilter, setStatusFilter] = useState<'active' | 'inactive' | 'all'>('all')
// Modal states
const [showNewTeamModal, setShowNewTeamModal] = useState(false)
const [showViewTeamModal, setShowViewTeamModal] = useState(false)
const [showEditTeamModal, setShowEditTeamModal] = useState(false)
const [showAssignWorkOrderModal, setShowAssignWorkOrderModal] = useState(false)
const [showStatusChangeModal, setShowStatusChangeModal] = useState(false)
// Selected data
const [viewingTeam, setViewingTeam] = useState<Team | null>(null)
const [editingTeam, setEditingTeam] = useState<Team | null>(null)
const [selectedTeams, setSelectedTeams] = useState<string[]>([])
// Mock data - replace with actual API calls
const [teams, setTeams] = useState<Team[]>(mockMaintenanceTeams)
const filteredTeams = teams.filter((team) => {
const matchesSearch =
team.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
team.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
team.members.some(
(member) =>
member.employee?.firstName.toLowerCase().includes(searchTerm.toLowerCase()) ||
member.employee?.lastName.toLowerCase().includes(searchTerm.toLowerCase()),
)
const matchesRole =
roleFilter === 'all' || team.members.some((member) => member.role === roleFilter)
const matchesStatus =
statusFilter === 'all' ||
(statusFilter === 'active' && team.isActive) ||
(statusFilter === 'inactive' && !team.isActive)
return matchesSearch && matchesRole && matchesStatus
})
const getTeamLeader = (team: Team) => {
return team.members.find((member) => member.role === TeamRoleEnum.Lead)
}
const getActiveMembers = (team: Team) => {
return team.members.filter((member) => member.isActive)
}
const handleAddTeam = () => {
setEditingTeam(null)
setShowNewTeamModal(true)
}
const handleViewTeam = (team: Team) => {
setViewingTeam(team)
setShowViewTeamModal(true)
}
const handleEditTeam = (team: Team) => {
setEditingTeam(team)
setShowEditTeamModal(true)
}
const handleSelectTeam = (teamId: string) => {
setSelectedTeams((prev) =>
prev.includes(teamId) ? prev.filter((id) => id !== teamId) : [...prev, teamId],
)
}
const handleNewTeamSave = (newTeam: Partial<Team>) => {
if (newTeam.id) {
setTeams((prev) => [...prev, newTeam as Team])
}
}
const handleEditTeamSave = (updatedTeam: Team) => {
setTeams((prev) => prev.map((team) => (team.id === updatedTeam.id ? updatedTeam : team)))
}
const handleAssignWorkOrders = (assignments: { teamId: string; planIds: string[] }[]) => {
// Here you would typically make API calls to assign work orders
console.log('Assigning work orders:', assignments)
// Show success message or handle the actual assignment logic
}
const handleStatusChange = (teamIds: string[], newStatus: boolean, reason?: string) => {
setTeams((prev) =>
prev.map((team) =>
teamIds.includes(team.id)
? { ...team, isActive: newStatus, lastModificationTime: new Date() }
: team,
),
)
setSelectedTeams([])
// Here you would typically log the status change with reason
console.log('Status change:', { teamIds, newStatus, reason })
}
const selectedTeamObjects = teams.filter((team) => selectedTeams.includes(team.id))
return (
<Container>
<div className="space-y-2">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-gray-900">Bakım Ekipleri</h2>
<p className="text-gray-600">Bakım ekiplerini ve üyelerini yönetin</p>
</div>
<button
onClick={handleAddTeam}
className="bg-blue-600 text-white px-3 py-1.5 rounded-lg hover:bg-blue-700 flex items-center space-x-2 text-sm"
>
<FaPlus className="w-4 h-4" />
<span>Yeni Ekip</span>
</button>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
<Widget title="Toplam Ekip" value={teams.length} color="blue" icon="FaUsers" />
<Widget
title="Aktif Ekip"
value={teams.filter((t) => t.isActive).length}
color="green"
icon="FaCheckCircle"
/>
<Widget
title="Toplam Üye"
value={teams.reduce((total, team) => total + getActiveMembers(team).length, 0)}
color="orange"
icon="FaUser"
/>
<Widget
title="Uzmanlık"
value={teams.reduce((total, team) => total + (team.specializations?.length || 0), 0)}
color="purple"
icon="FaWrench"
/>
</div>
{/* Filters */}
<div className="flex space-x-3">
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Ekip veya üye ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-9 pr-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div className="relative">
<FaFilter className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<select
value={roleFilter}
onChange={(e) => setRoleFilter(e.target.value as TeamRoleEnum | 'all')}
className="pl-10 pr-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Roller</option>
<option value={TeamRoleEnum.Lead}>Lider</option>
<option value={TeamRoleEnum.Specialist}>Uzman</option>
<option value={TeamRoleEnum.Member}>Üye</option>
</select>
</div>
<div className="relative">
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as 'active' | 'inactive' | 'all')}
className="pl-4 pr-4 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="all">Tüm Durumlar</option>
<option value="active">Aktif</option>
<option value="inactive">Pasif</option>
</select>
</div>
</div>
{/* Teams Grid */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{filteredTeams.map((team) => {
const leader = getTeamLeader(team)
const activeMembers = getActiveMembers(team)
return (
<div
key={team.id}
className="bg-white rounded-lg shadow-md border border-gray-200 p-4 hover:shadow-lg transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<input
type="checkbox"
checked={selectedTeams.includes(team.id)}
onChange={() => handleSelectTeam(team.id)}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-0.5"
/>
<h3 className="text-lg font-semibold text-gray-900">{team.code}</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${
team.isActive
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'
}`}
>
{team.isActive ? 'Aktif' : 'Pasif'}
</span>
</div>
<h4 className="font-medium text-gray-700 mb-1 text-sm">{team.name}</h4>
<p className="text-xs text-gray-500 mb-2">{team.description}</p>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleViewTeam(team)}
className="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-md transition-colors"
>
<FaEye className="w-4 h-4" />
</button>
<button
onClick={() => handleEditTeam(team)}
className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors"
>
<FaEdit className="w-4 h-4" />
</button>
<button className="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors">
<FaTrash className="w-4 h-4" />
</button>
</div>
</div>
{/* Team Leader */}
{leader && (
<div className="bg-purple-50 rounded-lg p-2 mb-3">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-purple-600 rounded-full flex items-center justify-center">
<FaAward className="w-5 h-5 text-white" />
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<h5 className="font-medium text-gray-900">
{leader.employee?.firstName} {leader.employee?.lastName}
</h5>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getTeamRoleColor(
leader.role,
)}`}
>
{getTeamRoleIcon(leader.role)}
<span>{getTeamRoleText(leader.role)}</span>
</span>
</div>
<p className="text-sm text-gray-600">
{leader.employee?.jobPosition?.code}
</p>
<div className="flex items-center space-x-4 mt-1 text-xs text-gray-500">
{leader.employee?.email && (
<div className="flex items-center space-x-1">
<FaEnvelope className="w-3 h-3" />
<span>{leader.employee.email}</span>
</div>
)}
{leader.employee?.phone && (
<div className="flex items-center space-x-1">
<FaPhone className="w-3 h-3" />
<span>{leader.employee.phone}</span>
</div>
)}
</div>
</div>
</div>
</div>
)}
{/* Team Members */}
<div className="space-y-2 mb-3">
<h5 className="text-xs font-medium text-gray-700">
Ekip Üyeleri ({activeMembers.length})
</h5>
<div className="space-y-2">
{activeMembers
.filter((member) => member.role !== TeamRoleEnum.Lead)
.slice(0, 3)
.map((member) => (
<div
key={member.id}
className="flex items-center space-x-2 p-1.5 bg-gray-50 rounded-lg"
>
<div className="w-8 h-8 bg-gray-400 rounded-full flex items-center justify-center">
<FaUser className="w-4 h-4 text-white" />
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-900">
{member.employee?.firstName} {member.employee?.lastName}
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getTeamRoleColor(
member.role,
)}`}
>
{getTeamRoleIcon(member.role)}
<span>{getTeamRoleText(member.role)}</span>
</span>
</div>
<p className="text-xs text-gray-600">
{member.employee?.jobPosition?.code}
</p>
</div>
</div>
))}
{activeMembers.filter((member) => member.role !== TeamRoleEnum.Lead).length >
3 && (
<div className="text-center text-xs text-gray-500 p-1.5">
+
{activeMembers.filter((member) => member.role !== TeamRoleEnum.Lead)
.length - 3}{' '}
kişi daha...
</div>
)}
</div>
</div>
{/* Specializations */}
<div className="border-t border-gray-100 pt-2">
<h5 className="text-xs font-medium text-gray-700 mb-2">Uzmanlık Alanları</h5>
<div className="flex flex-wrap gap-1">
{team.specializations ??
[].slice(0, 6).map((specialization, index) => (
<span
key={index}
className="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full"
>
{specialization}
</span>
))}
{(team.specializations?.length ?? 0) > 6 && (
<span className="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full">
+{(team.specializations?.length ?? 0) - 6}
</span>
)}
</div>
</div>
<div className="border-t border-gray-100 pt-2 mt-2">
<div className="flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center space-x-2">
<FaClock className="w-3 h-3" />
<span>Oluşturuldu: {team.creationTime.toLocaleDateString('tr-TR')}</span>
</div>
<div className="flex items-center space-x-2">
<span>
Son Güncelleme: {team.lastModificationTime.toLocaleDateString('tr-TR')}
</span>
</div>
</div>
</div>
</div>
)
})}
</div>
{filteredTeams.length === 0 && (
<div className="text-center py-12">
<FaUsers className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Ekip bulunamadı</h3>
<p className="text-gray-500 mb-4">
Arama kriterlerinizi değiştirin veya yeni bir ekip oluşturun.
</p>
<button
onClick={handleAddTeam}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
Yeni Ekip Oluştur
</button>
</div>
)}
{/* Bulk Actions */}
{selectedTeams.length > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-lg border border-gray-200 p-4">
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-600">{selectedTeams.length} ekip seçildi</span>
<div className="flex space-x-2">
<button
onClick={() => setShowAssignWorkOrderModal(true)}
className="bg-green-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-green-700"
>
İş Emri Ata
</button>
<button
onClick={() => setShowStatusChangeModal(true)}
className="bg-blue-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-blue-700"
>
Durum Değiştir
</button>
<button
onClick={() => setSelectedTeams([])}
className="bg-gray-600 text-white px-2.5 py-1.5 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
</div>
</div>
)}
</div>
{/* Modals */}
<NewTeamModal
isOpen={showNewTeamModal}
onClose={() => setShowNewTeamModal(false)}
onSave={handleNewTeamSave}
/>
<ViewTeamModal
isOpen={showViewTeamModal}
onClose={() => setShowViewTeamModal(false)}
onEdit={handleEditTeam}
team={viewingTeam}
/>
<EditTeamModal
isOpen={showEditTeamModal}
onClose={() => setShowEditTeamModal(false)}
onSave={handleEditTeamSave}
team={editingTeam}
/>
<AssignWorkOrderModal
isOpen={showAssignWorkOrderModal}
onClose={() => setShowAssignWorkOrderModal(false)}
onSave={handleAssignWorkOrders}
selectedTeams={selectedTeamObjects}
/>
<TeamStatusChangeModal
isOpen={showStatusChangeModal}
onClose={() => setShowStatusChangeModal(false)}
onSave={handleStatusChange}
selectedTeams={selectedTeamObjects}
/>
</Container>
)
}
export default MaintenanceTeams