import React, { useState, useEffect } from 'react' import { AnimatePresence } from 'framer-motion' import dayjs from 'dayjs' import 'dayjs/locale/tr' import relativeTime from 'dayjs/plugin/relativeTime' import isBetween from 'dayjs/plugin/isBetween' // Widgets import TodayBirthdays from './widgets/TodayBirthdays' import UpcomingEvents from './widgets/UpcomingEvents' import RecentDocuments from './widgets/RecentDocuments' import ImportantAnnouncements from './widgets/ImportantAnnouncements' import PriorityTasks from './widgets/PriorityTasks' import MealWeeklyMenu from './widgets/MealWeeklyMenu' import ShuttleSchedule from './widgets/ShuttleSchedule' import LeaveManagement from './widgets/LeaveManagement' import OvertimeManagement from './widgets/OvertimeManagement' import ExpenseManagement from './widgets/ExpenseManagement' import UpcomingTrainings from './widgets/UpcomingTrainings' import ActiveReservations from './widgets/ActiveReservations' import ActiveSurveys from './widgets/ActiveSurveys' import Visitors from './widgets/Visitors' // Modals import SurveyModal from './modals/SurveyModal' import LeaveRequestModal from './modals/LeaveRequestModal' import OvertimeRequestModal from './modals/OvertimeRequestModal' import ExpenseRequestModal from './modals/ExpenseRequestModal' import ReservationRequestModal from './modals/ReservationRequestModal' import AnnouncementDetailModal from './modals/AnnouncementDetailModal' // Social Wall import SocialWall from './SocialWall' import { Announcement, Survey, SurveyAnswer } from '@/types/intranet' import { Container } from '@/components/shared' import { usePermission } from '@/utils/hooks/usePermission' dayjs.locale('tr') dayjs.extend(relativeTime) dayjs.extend(isBetween) interface WidgetConfig { id: string permission: string component: React.ReactNode column: 'left' | 'center' | 'right' } const WIDGET_ORDER_KEY = 'dashboard-widget-order' const IntranetDashboard: React.FC = () => { const { checkPermission } = usePermission() const [selectedAnnouncement, setSelectedAnnouncement] = useState(null) const [selectedSurvey, setSelectedSurvey] = useState(null) const [showSurveyModal, setShowSurveyModal] = useState(false) const [showLeaveModal, setShowLeaveModal] = useState(false) const [showOvertimeModal, setShowOvertimeModal] = useState(false) const [showExpenseModal, setShowExpenseModal] = useState(false) const [showReservationModal, setShowReservationModal] = useState(false) const [isDesignMode, setIsDesignMode] = useState(false) const [widgetOrder, setWidgetOrder] = useState>({ left: [], center: [], right: [], }) // Drag state'leri birleştirildi const [dragState, setDragState] = useState<{ draggedId: string | null targetColumn: string | null targetIndex: number | null }>({ draggedId: null, targetColumn: null, targetIndex: null, }) const handleTakeSurvey = (survey: Survey) => { setSelectedSurvey(survey) setShowSurveyModal(true) } const handleSubmitSurvey = (answers: SurveyAnswer[]) => { setShowSurveyModal(false) setSelectedSurvey(null) } const handleSubmitLeave = () => { setShowLeaveModal(false) } const handleSubmitOvertime = () => { setShowOvertimeModal(false) } const handleSubmitExpense = () => { setShowExpenseModal(false) } const handleSubmitReservation = () => { setShowReservationModal(false) } // Widget metadata (component'lar yerine sadece meta bilgiler) const widgetMetadata = [ { id: 'upcoming-events', permission: 'App.Intranet.Events.Event.Widget', column: 'left' }, { id: 'today-birthdays', permission: 'App.Hr.Employee.Widget', column: 'left' }, { id: 'recent-documents', permission: 'App.Files.Widget', column: 'left' }, { id: 'upcoming-trainings', permission: 'App.Hr.Training.Widget', column: 'left' }, { id: 'active-reservations', permission: 'App.Intranet.Reservation.Widget', column: 'left' }, { id: 'active-surveys', permission: 'App.Intranet.Survey.Widget', column: 'left' }, { id: 'visitors', permission: 'App.Intranet.Visitor.Widget', column: 'left' }, { id: 'expense-management', permission: 'App.Hr.Expense.Widget', column: 'left' }, { id: 'social-wall', permission: 'App.Intranet.SocialPost.Widget', column: 'center' }, { id: 'important-announcements', permission: 'App.Intranet.Announcement.Widget', column: 'right', }, { id: 'priority-tasks', permission: 'App.Projects.Tasks.Widget', column: 'right' }, { id: 'meal-weekly-menu', permission: 'App.Intranet.Meal.Widget', column: 'right' }, { id: 'shuttle-schedule', permission: 'App.Intranet.ShuttleRoute.Widget', column: 'right' }, { id: 'leave-management', permission: 'App.Hr.Leave.Widget', column: 'right' }, { id: 'overtime-management', permission: 'App.Hr.Overtime.Widget', column: 'right' }, ] // Widget sıralamasını yükle useEffect(() => { const savedOrder = localStorage.getItem(WIDGET_ORDER_KEY) if (savedOrder) { try { const parsed = JSON.parse(savedOrder) as Record // Duplicate key'leri temizle const cleanedOrder: Record = { left: [...new Set((parsed.left || []) as string[])], center: [...new Set((parsed.center || []) as string[])], right: [...new Set((parsed.right || []) as string[])], } setWidgetOrder(cleanedOrder) } catch (error) { console.error('Widget order parse error:', error) initializeDefaultOrder() } } else { initializeDefaultOrder() } }, []) const initializeDefaultOrder = () => { const defaultOrder = { left: widgetMetadata .filter((w) => w.column === 'left' && checkPermission(w.permission)) .map((w) => w.id), center: widgetMetadata .filter((w) => w.column === 'center' && checkPermission(w.permission)) .map((w) => w.id), right: widgetMetadata .filter((w) => w.column === 'right' && checkPermission(w.permission)) .map((w) => w.id), } setWidgetOrder(defaultOrder) } // Widget sıralamasını kaydet const saveWidgetOrder = (newOrder: Record) => { setWidgetOrder(newOrder) localStorage.setItem(WIDGET_ORDER_KEY, JSON.stringify(newOrder)) } const handleDragStart = (e: React.DragEvent, widgetId: string, column: string) => { setDragState({ draggedId: widgetId, targetColumn: null, targetIndex: null }) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('widgetId', widgetId) e.dataTransfer.setData('sourceColumn', column) } const handleDragOver = (e: React.DragEvent) => { e.preventDefault() e.dataTransfer.dropEffect = 'move' } const handleDragEnterWidget = (e: React.DragEvent, column: string, index: number) => { // Sadece widget'ın üst kısmına yakınsa indicator göster const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() const mouseY = e.clientY const widgetTop = rect.top const widgetHeight = rect.height const threshold = widgetHeight * 0.3 // Üst %30'luk alan if (mouseY - widgetTop < threshold) { // Üst kısma yakın - indicator göster setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: index })) } else { // Widget'ın ortasında veya altında - indicator gösterme setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null })) } } const handleDragEnterColumn = (column: string) => { setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null })) } const handleDragLeaveColumn = () => { setDragState((prev) => ({ ...prev, targetColumn: null, targetIndex: null })) } const handleDrop = (e: React.DragEvent, targetColumn: string, targetIndex?: number) => { e.preventDefault() e.stopPropagation() const widgetId = e.dataTransfer.getData('widgetId') const sourceColumn = e.dataTransfer.getData('sourceColumn') if (!widgetId || !sourceColumn) return const newOrder = { ...widgetOrder } // ÖNCE tüm kolonlardan bu widget'ı kaldır (duplicate önleme) Object.keys(newOrder).forEach((col) => { newOrder[col] = newOrder[col].filter((id) => id !== widgetId) }) // SONRA hedef kolona ekle if (targetIndex !== undefined) { newOrder[targetColumn].splice(targetIndex, 0, widgetId) } else { newOrder[targetColumn].push(widgetId) } // Duplicate'leri temizle Object.keys(newOrder).forEach((col) => { newOrder[col] = [...new Set(newOrder[col])] }) saveWidgetOrder(newOrder) setDragState({ draggedId: null, targetColumn: null, targetIndex: null }) } const handleDragEnd = () => { setDragState({ draggedId: null, targetColumn: null, targetIndex: null }) } // Widget component'ını render et const renderWidgetComponent = (widgetId: string) => { switch (widgetId) { case 'upcoming-events': return case 'today-birthdays': return case 'recent-documents': return case 'upcoming-trainings': return case 'active-reservations': return setShowReservationModal(true)} /> case 'active-surveys': return case 'visitors': return case 'expense-management': return setShowExpenseModal(true)} /> case 'social-wall': return case 'important-announcements': return case 'priority-tasks': return case 'meal-weekly-menu': return case 'shuttle-schedule': return case 'leave-management': return setShowLeaveModal(true)} /> case 'overtime-management': return setShowOvertimeModal(true)} /> default: return null } } // Widget'ları render et const renderWidgets = (column: 'left' | 'center' | 'right') => { const columnWidgets = widgetOrder[column] || [] // Duplicate'leri filtrele const uniqueWidgets = [...new Set(columnWidgets)] return uniqueWidgets .map((widgetId, index) => { const metadata = widgetMetadata.find((w) => w.id === widgetId) if (!metadata || !checkPermission(metadata.permission)) return null const isDragging = dragState.draggedId === widgetId const isDropTarget = dragState.targetColumn === column && dragState.targetIndex === index return (
{/* Drop indicator - SADECE widget'ların arasına (üst %30'luk alana) gelince göster */} {isDesignMode && isDropTarget && !isDragging && (
{/* Çizgi */}
{/* Badge */}
Buraya Bırak
)}
{ if (isDesignMode) { handleDragStart(e, widgetId, column) // Drag ghost image'i gizle const ghost = document.createElement('div') ghost.style.opacity = '0' e.dataTransfer.setDragImage(ghost, 0, 0) } }} onDragOver={(e) => { if (!isDesignMode) return e.preventDefault() e.stopPropagation() // Throttle: Sadece düzenli aralıklarla güncelle const now = Date.now() if ( !e.currentTarget.dataset.lastUpdate || now - parseInt(e.currentTarget.dataset.lastUpdate) > 150 ) { e.currentTarget.dataset.lastUpdate = now.toString() handleDragEnterWidget(e, column, index) } }} onDragLeave={(e) => { // Widget'tan çıkınca indicator'ı kaldır if (isDesignMode) { setDragState((prev) => ({ ...prev, targetColumn: prev.targetColumn, targetIndex: null, })) } }} onDrop={(e) => { if (!isDesignMode) return e.stopPropagation() // Drop pozisyonunu hesapla const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() const mouseY = e.clientY const widgetTop = rect.top const widgetHeight = rect.height const threshold = widgetHeight * 0.3 if (mouseY - widgetTop < threshold) { // Üst kısma bırak - mevcut index'e ekle handleDrop(e, column, index) } else { // Alt kısma bırak - sonraki index'e ekle handleDrop(e, column, index + 1) } }} onDragEnd={handleDragEnd} className={` relative ${ isDesignMode ? `border-2 border-dashed rounded-lg cursor-move ${ isDragging ? 'border-blue-400 opacity-70 bg-blue-50/30 dark:bg-blue-900/10' : 'border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:shadow-md' } transition-all duration-300 ease-out` : 'border-0 transition-none' } `} style={{ touchAction: 'none', transition: 'border-color 0.3s ease-out, opacity 0.3s ease-out, box-shadow 0.3s ease-out', willChange: isDragging ? 'opacity' : 'auto', }} > {/* Dragging overlay - daha minimal */} {isDesignMode && isDragging && (
Taşınıyor
)}
{renderWidgetComponent(widgetId)}
) }) .filter(Boolean) } return (
{/* Design Mode Toggle */}
{/* Reset Button - Sadece design mode aktifken görünsün */} {isDesignMode && ( )}

Hoş geldiniz,{' '} {dayjs().format('DD MMMM YYYY dddd')}

{ if (!isDesignMode) return e.preventDefault() // Throttle: Sadece her 150ms'de bir güncelle const now = Date.now() const target = e.currentTarget as HTMLElement if ( !target.dataset.lastColumnUpdate || now - parseInt(target.dataset.lastColumnUpdate) > 150 ) { target.dataset.lastColumnUpdate = now.toString() handleDragEnterColumn('left') } }} onDragLeave={() => { if (isDesignMode) handleDragLeaveColumn() }} onDrop={(e) => { if (!isDesignMode) return e.stopPropagation() const columnWidgets = widgetOrder['left'] || [] handleDrop(e, 'left', columnWidgets.length) }} > {renderWidgets('left')} {isDesignMode && dragState.targetColumn === 'left' && widgetOrder['left']?.length === 0 && (

Widget'ı buraya bırakın

)}
{ if (!isDesignMode) return e.preventDefault() const now = Date.now() const target = e.currentTarget as HTMLElement if ( !target.dataset.lastColumnUpdate || now - parseInt(target.dataset.lastColumnUpdate) > 150 ) { target.dataset.lastColumnUpdate = now.toString() handleDragEnterColumn('center') } }} onDragLeave={() => { if (isDesignMode) handleDragLeaveColumn() }} onDrop={(e) => { if (!isDesignMode) return e.stopPropagation() const columnWidgets = widgetOrder['center'] || [] handleDrop(e, 'center', columnWidgets.length) }} > {renderWidgets('center')} {isDesignMode && dragState.targetColumn === 'center' && widgetOrder['center']?.length === 0 && (

Widget'ı buraya bırakın

)}
{ if (!isDesignMode) return e.preventDefault() const now = Date.now() const target = e.currentTarget as HTMLElement if ( !target.dataset.lastColumnUpdate || now - parseInt(target.dataset.lastColumnUpdate) > 150 ) { target.dataset.lastColumnUpdate = now.toString() handleDragEnterColumn('right') } }} onDragLeave={() => { if (isDesignMode) handleDragLeaveColumn() }} onDrop={(e) => { if (!isDesignMode) return e.stopPropagation() const columnWidgets = widgetOrder['right'] || [] handleDrop(e, 'right', columnWidgets.length) }} > {renderWidgets('right')} {isDesignMode && dragState.targetColumn === 'right' && widgetOrder['right']?.length === 0 && (

Widget'ı buraya bırakın

)}
{showSurveyModal && selectedSurvey && ( setShowSurveyModal(false)} onSubmit={handleSubmitSurvey} /> )} {showLeaveModal && ( setShowLeaveModal(false)} onSubmit={handleSubmitLeave} /> )} {showOvertimeModal && ( setShowOvertimeModal(false)} onSubmit={handleSubmitOvertime} /> )} {showExpenseModal && ( setShowExpenseModal(false)} onSubmit={handleSubmitExpense} /> )} {showReservationModal && ( setShowReservationModal(false)} onSubmit={handleSubmitReservation} /> )} {selectedAnnouncement && ( setSelectedAnnouncement(null)} /> )}
) } export default IntranetDashboard