erp-platform/ui/src/views/maintenance/components/FaultNotifications.tsx
2025-09-17 11:58:20 +03:00

555 lines
22 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,
FaExclamationTriangle,
FaClock,
FaUser,
FaMapMarkerAlt,
FaEdit,
FaTrash,
FaEye,
FaFileAlt,
FaCamera,
FaPhone,
} from 'react-icons/fa'
import { NotificationStatusEnum, PmFaultNotification, PmWorkCenter } from '../../../types/pm'
import { mockFaultNotifications } from '../../../mocks/mockFaultNotifications'
import NewFaultNotificationModal from './NewFaultNotificationModal'
import ViewFaultNotificationModal from './ViewFaultNotificationModal'
import EditFaultNotificationModal from './EditFaultNotificationModal'
import CreateWorkOrderFromNotificationModal from './CreateWorkOrderFromNotificationModal'
import AssignNotificationModal from './AssignNotificationModal'
import ChangeNotificationStatusModal from './ChangeNotificationStatusModal'
import Widget from '../../../components/common/Widget'
import { PriorityEnum } from '../../../types/common'
import {
getFaultTypeColor,
getFaultTypeText,
getPriorityColor,
getPriorityText,
getCriticalityLevelColor,
getNotificationStatusColor,
getNotificationStatusIcon,
getNotificationStatusText,
} from '../../../utils/erp'
import { Container } from '@/components/shared'
interface AssignmentData {
notificationIds: string[]
assignedTo?: string
teamId?: string
}
interface StatusChangeData {
notificationIds: string[]
status: NotificationStatusEnum
}
const FaultNotifications: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState<NotificationStatusEnum | 'all'>('all')
const [priorityFilter, setPriorityFilter] = useState<PriorityEnum | 'all'>('all')
const [showModal, setShowModal] = useState(false)
const [editingNotification, setEditingNotification] = useState<PmFaultNotification | null>(null)
const [viewingNotification, setViewingNotification] = useState<PmFaultNotification | null>(null)
const [selectedNotifications, setSelectedNotifications] = useState<string[]>([])
const [showCreateWorkOrderModal, setShowCreateWorkOrderModal] = useState(false)
const [showAssignModal, setShowAssignModal] = useState(false)
const [showStatusChangeModal, setShowStatusChangeModal] = useState(false)
// Mock data - replace with actual API calls
const [notifications, setNotifications] = useState<PmFaultNotification[]>(mockFaultNotifications)
const filteredNotifications = notifications.filter((notification) => {
const matchesSearch =
notification.notificationCode.toLowerCase().includes(searchTerm.toLowerCase()) ||
notification.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
notification.workCenter.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
notification.reportedBy.toLowerCase().includes(searchTerm.toLowerCase())
const matchesStatus = statusFilter === 'all' || notification.status === statusFilter
const matchesPriority = priorityFilter === 'all' || notification.priority === priorityFilter
return matchesSearch && matchesStatus && matchesPriority
})
const getTimeAgo = (date: Date) => {
const now = new Date()
const diffInMs = now.getTime() - date.getTime()
const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60))
const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24))
if (diffInHours < 1) {
const diffInMinutes = Math.floor(diffInMs / (1000 * 60))
return `${diffInMinutes} dakika önce`
} else if (diffInHours < 24) {
return `${diffInHours} saat önce`
} else {
return `${diffInDays} gün önce`
}
}
const handleAddNotification = () => {
setEditingNotification(null)
setShowModal(true)
}
const handleEdit = (notification: PmFaultNotification) => {
setEditingNotification(notification)
setShowModal(true)
}
const handleView = (notification: PmFaultNotification) => {
setViewingNotification(notification)
}
const handleSelectNotification = (notificationId: string) => {
setSelectedNotifications((prev) =>
prev.includes(notificationId)
? prev.filter((id) => id !== notificationId)
: [...prev, notificationId],
)
}
const handleSaveNotification = (notificationData: Partial<PmFaultNotification>) => {
if (editingNotification) {
// Update existing notification
setNotifications((prev) =>
prev.map((n) => (n.id === editingNotification.id ? { ...n, ...notificationData } : n)),
)
} else {
// Add new notification
setNotifications((prev) => [...prev, notificationData as PmFaultNotification])
}
}
const handleCreateWorkOrder = () => {
setShowCreateWorkOrderModal(true)
}
const handleAssignNotifications = () => {
setShowAssignModal(true)
}
const handleChangeStatus = () => {
setShowStatusChangeModal(true)
}
const handleWorkOrderSave = (workOrderData: PmWorkCenter) => {
console.log('İş emri oluşturuldu:', workOrderData)
// Here you would typically save to backend
setSelectedNotifications([])
}
const handleAssignmentSave = (assignmentData: AssignmentData) => {
console.log('Atama yapıldı:', assignmentData)
// Here you would typically save to backend
// Update notifications with assignment
setNotifications((prev) =>
prev.map((n) =>
assignmentData.notificationIds.includes(n.id)
? {
...n,
assignedTo: assignmentData.assignedTo,
status: NotificationStatusEnum.Assigned,
}
: n,
),
)
setSelectedNotifications([])
}
const handleStatusChangeSave = (statusChangeData: StatusChangeData) => {
console.log('Durum değiştirildi:', statusChangeData)
// Here you would typically save to backend
// Update notifications with new status
setNotifications((prev) =>
prev.map((n) =>
statusChangeData.notificationIds.includes(n.id)
? {
...n,
status: statusChangeData.status,
lastModificationTime: new Date(),
}
: n,
),
)
setSelectedNotifications([])
}
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">Arıza Bildirimleri</h2>
<p className="text-gray-600">İş merkezi arızalarını takip edin ve yönetin</p>
</div>
<button
onClick={handleAddNotification}
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 Bildirim</span>
</button>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
<Widget
title="Toplam"
value={notifications.length}
color="gray"
icon="FaExclamationTriangle"
/>
<Widget
title="Açık"
value={notifications.filter((n) => n.status === NotificationStatusEnum.Open).length}
color="red"
icon="FaExclamationTriangle"
/>
<Widget
title="Devam Ediyor"
value={
notifications.filter((n) => n.status === NotificationStatusEnum.InProgress).length
}
color="orange"
icon="FaClock"
/>
<Widget
title="Çözüldü"
value={notifications.filter((n) => n.status === NotificationStatusEnum.Resolved).length}
color="green"
icon="FaCheckCircle"
/>
<Widget
title="Acil"
value={notifications.filter((n) => n.priority === PriorityEnum.Urgent).length}
color="red"
icon="FaTimesCircle"
/>
</div>
{/* Filters */}
<div className="flex space-x-4">
<div className="flex-1 relative">
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-5 h-5" />
<input
type="text"
placeholder="Bildirim ara..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full 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"
/>
</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={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as NotificationStatusEnum | '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"
>
{Object.values(NotificationStatusEnum).map((status) => (
<option key={status} value={status}>
{getNotificationStatusText(status)}
</option>
))}
</select>
</div>
<div className="relative">
<select
value={priorityFilter}
onChange={(e) => setPriorityFilter(e.target.value as PriorityEnum | '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 Öncelikler</option>
{Object.values(PriorityEnum).map((priority) => (
<option key={priority} value={priority}>
{getPriorityText(priority)}
</option>
))}
</select>
</div>
</div>
{/* Notifications List */}
<div className="space-y-3 pt-2">
{filteredNotifications.map((notification) => (
<div
key={notification.id}
className={`bg-white rounded-lg shadow-md border-l-4 p-4 hover:shadow-lg transition-shadow ${getCriticalityLevelColor(
notification.severity,
)}`}
>
<div className="flex items-start justify-between">
<div className="flex items-start space-x-4 flex-1">
<input
type="checkbox"
checked={selectedNotifications.includes(notification.id)}
onChange={() => handleSelectNotification(notification.id)}
className="mt-1 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<div className="flex-1">
<div className="flex items-center space-x-3 mb-2">
<h3 className="text-lg font-semibold text-gray-900">
{notification.notificationCode}
</h3>
<span
className={`px-2 py-1 rounded-full text-xs font-medium flex items-center space-x-1 ${getNotificationStatusColor(
notification.status,
)}`}
>
{getNotificationStatusIcon(notification.status)}
<span>{getNotificationStatusText(notification.status)}</span>
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(
notification.priority,
)}`}
>
{getPriorityText(notification.priority)}
</span>
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${getFaultTypeColor(
notification.faultType,
)}`}
>
{getFaultTypeText(notification.faultType)}
</span>
</div>
<h4 className="font-medium text-gray-800 mb-2">{notification.title}</h4>
<p className="text-sm text-gray-600 mb-3">
{notification.description.length > 150
? `${notification.description.substring(0, 150)}...`
: notification.description}
</p>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 text-sm mb-3">
<div>
<span className="text-gray-500">İş Merkezi:</span>
<p className="font-medium text-gray-900">{notification.workCenter.code}</p>
<p className="text-xs text-gray-600">{notification.workCenter.name}</p>
</div>
<div>
<span className="text-gray-500">Konum:</span>
<p className="font-medium text-gray-900 flex items-center">
<FaMapMarkerAlt className="w-3 h-3 mr-1" />
{notification.location}
</p>
</div>
<div>
<span className="text-gray-500">Bildiren:</span>
<p className="font-medium text-gray-900">{notification.reportedBy}</p>
</div>
<div>
<span className="text-gray-500">Süre:</span>
<p className="font-medium text-gray-900">
{notification.estimatedRepairTime
? `~${notification.estimatedRepairTime} dk`
: '-'}
</p>
</div>
</div>
{notification.assignedTo && (
<div className="bg-blue-50 rounded-lg p-3 mb-3">
<div className="flex items-center space-x-2 text-sm">
<FaUser className="w-4 h-4 text-blue-600" />
<span className="text-gray-600">Atanan:</span>
<span className="font-medium text-gray-900">
{notification.assignedTo}
</span>
</div>
</div>
)}
{notification.images && notification.images.length > 0 && (
<div className="flex items-center space-x-2 text-sm text-gray-500 mb-3">
<FaCamera className="w-4 h-4" />
<span>{notification.images.length} fotoğraf</span>
</div>
)}
{notification.resolutionNotes && (
<div className="bg-green-50 rounded-lg p-3 mb-3">
<div className="flex items-start space-x-2 text-sm">
<FaFileAlt className="w-4 h-4 text-green-600 mt-0.5" />
<div>
<span className="text-gray-600">Çözüm Notları:</span>
<p className="text-gray-900 mt-1">{notification.resolutionNotes}</p>
</div>
</div>
</div>
)}
<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>Bildirilme: {getTimeAgo(notification.reportedAt)}</span>
</div>
{notification.workOrderId && (
<div className="flex items-center space-x-2">
<span>İş Emri: {notification.workOrderId}</span>
</div>
)}
{notification.followUpRequired && (
<div className="flex items-center space-x-2 text-orange-600">
<FaExclamationTriangle className="w-3 h-3" />
<span>Takip Gerekli</span>
</div>
)}
</div>
</div>
</div>
<div className="flex space-x-1">
<button
onClick={() => handleView(notification)}
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={() => handleEdit(notification)}
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>
<button className="p-1.5 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded-md transition-colors">
<FaPhone className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
{filteredNotifications.length === 0 && (
<div className="text-center py-12">
<FaExclamationTriangle className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">Bildirim bulunamadı</h3>
<p className="text-gray-500 mb-4">
Arama kriterlerinizi değiştirin veya yeni bir bildirim oluşturun.
</p>
<button
onClick={handleAddNotification}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
Yeni Bildirim Oluştur
</button>
</div>
)}
{/* Bulk Actions */}
{selectedNotifications.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">
{selectedNotifications.length} bildirim seçildi
</span>
<div className="flex space-x-2">
<button
onClick={handleCreateWorkOrder}
className="bg-green-600 text-white px-3 py-2 rounded text-sm hover:bg-green-700"
>
İş Emri Oluştur
</button>
<button
onClick={handleAssignNotifications}
className="bg-blue-600 text-white px-3 py-2 rounded text-sm hover:bg-blue-700"
>
Atama Yap
</button>
<button
onClick={handleChangeStatus}
className="bg-orange-600 text-white px-3 py-2 rounded text-sm hover:bg-orange-700"
>
Durum Değiştir
</button>
<button
onClick={() => setSelectedNotifications([])}
className="bg-gray-600 text-white px-3 py-2 rounded text-sm hover:bg-gray-700"
>
Temizle
</button>
</div>
</div>
</div>
)}
</div>
{/* Modals */}
{showModal && !editingNotification && (
<NewFaultNotificationModal
isOpen={showModal}
onClose={() => setShowModal(false)}
onSave={handleSaveNotification}
/>
)}
{editingNotification && (
<EditFaultNotificationModal
isOpen={showModal}
onClose={() => {
setShowModal(false)
setEditingNotification(null)
}}
onSave={handleSaveNotification}
notification={editingNotification}
/>
)}
{viewingNotification && (
<ViewFaultNotificationModal
isOpen={!!viewingNotification}
onClose={() => setViewingNotification(null)}
onEdit={(notification) => {
setViewingNotification(null)
setEditingNotification(notification)
setShowModal(true)
}}
notification={viewingNotification}
/>
)}
{showCreateWorkOrderModal && selectedNotifications.length > 0 && (
<CreateWorkOrderFromNotificationModal
isOpen={showCreateWorkOrderModal}
onClose={() => setShowCreateWorkOrderModal(false)}
onSave={handleWorkOrderSave}
notifications={notifications.filter((n) => selectedNotifications.includes(n.id))}
/>
)}
{showAssignModal && selectedNotifications.length > 0 && (
<AssignNotificationModal
isOpen={showAssignModal}
onClose={() => setShowAssignModal(false)}
onSave={handleAssignmentSave}
notifications={notifications.filter((n) => selectedNotifications.includes(n.id))}
/>
)}
{showStatusChangeModal && selectedNotifications.length > 0 && (
<ChangeNotificationStatusModal
isOpen={showStatusChangeModal}
onClose={() => setShowStatusChangeModal(false)}
onSave={handleStatusChangeSave}
notifications={notifications.filter((n) => selectedNotifications.includes(n.id))}
/>
)}
</Container>
)
}
export default FaultNotifications