688 lines
27 KiB
TypeScript
688 lines
27 KiB
TypeScript
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<Announcement | null>(null)
|
||
const [selectedSurvey, setSelectedSurvey] = useState<Survey | null>(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<Record<string, string[]>>({
|
||
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<string, unknown[]>
|
||
// Duplicate key'leri temizle
|
||
const cleanedOrder: Record<string, string[]> = {
|
||
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<string, string[]>) => {
|
||
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 <UpcomingEvents />
|
||
case 'today-birthdays':
|
||
return <TodayBirthdays />
|
||
case 'recent-documents':
|
||
return <RecentDocuments />
|
||
case 'upcoming-trainings':
|
||
return <UpcomingTrainings />
|
||
case 'active-reservations':
|
||
return <ActiveReservations onNewReservation={() => setShowReservationModal(true)} />
|
||
case 'active-surveys':
|
||
return <ActiveSurveys onTakeSurvey={handleTakeSurvey} />
|
||
case 'visitors':
|
||
return <Visitors />
|
||
case 'expense-management':
|
||
return <ExpenseManagement onNewExpense={() => setShowExpenseModal(true)} />
|
||
case 'social-wall':
|
||
return <SocialWall />
|
||
case 'important-announcements':
|
||
return <ImportantAnnouncements onAnnouncementClick={setSelectedAnnouncement} />
|
||
case 'priority-tasks':
|
||
return <PriorityTasks />
|
||
case 'meal-weekly-menu':
|
||
return <MealWeeklyMenu />
|
||
case 'shuttle-schedule':
|
||
return <ShuttleSchedule />
|
||
case 'leave-management':
|
||
return <LeaveManagement onNewLeave={() => setShowLeaveModal(true)} />
|
||
case 'overtime-management':
|
||
return <OvertimeManagement onNewOvertime={() => 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 (
|
||
<div key={`${column}-${widgetId}-${index}`} className="relative group">
|
||
{/* Drop indicator - SADECE widget'ların arasına (üst %30'luk alana) gelince göster */}
|
||
{isDesignMode && isDropTarget && !isDragging && (
|
||
<div className="absolute -top-5 left-0 right-0 z-20 animate-in fade-in slide-in-from-top-2 duration-300">
|
||
{/* Çizgi */}
|
||
<div className="h-2 bg-gradient-to-r from-transparent via-blue-500 to-transparent rounded-full shadow-lg" />
|
||
{/* Badge */}
|
||
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-xs px-4 py-2 rounded-full whitespace-nowrap shadow-xl font-semibold flex items-center gap-2 border-2 border-white dark:border-gray-800">
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M19 14l-7 7m0 0l-7-7m7 7V3"
|
||
/>
|
||
</svg>
|
||
<span>Buraya Bırak</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div
|
||
draggable={isDesignMode}
|
||
onDragStart={(e) => {
|
||
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 && (
|
||
<div className="absolute inset-0 bg-white/60 dark:bg-gray-900/40 rounded-lg z-10 flex items-center justify-center backdrop-blur-[1px] transition-opacity duration-300">
|
||
<div className="bg-gradient-to-r from-blue-500 to-blue-600 text-white px-4 py-2 rounded-lg font-medium shadow-lg flex items-center gap-2">
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"
|
||
/>
|
||
</svg>
|
||
<span>Taşınıyor</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div
|
||
className={`${isDesignMode ? 'p-1.5' : ''} transition-all duration-500 ease-out`}
|
||
>
|
||
{renderWidgetComponent(widgetId)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})
|
||
.filter(Boolean)
|
||
}
|
||
|
||
return (
|
||
<Container>
|
||
<div className="mx-auto space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
{/* Design Mode Toggle */}
|
||
<div className="flex items-center gap-2">
|
||
<label
|
||
htmlFor="design-mode-toggle"
|
||
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
|
||
>
|
||
🎨 Dizayn Modu
|
||
</label>
|
||
<button
|
||
id="design-mode-toggle"
|
||
onClick={() => setIsDesignMode(!isDesignMode)}
|
||
className={`
|
||
relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
||
${isDesignMode ? 'bg-blue-600' : 'bg-gray-300 dark:bg-gray-600'}
|
||
`}
|
||
role="switch"
|
||
aria-checked={isDesignMode}
|
||
>
|
||
<span
|
||
className={`
|
||
inline-block h-4 w-4 transform rounded-full bg-white transition-transform
|
||
${isDesignMode ? 'translate-x-6' : 'translate-x-1'}
|
||
`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Reset Button - Sadece design mode aktifken görünsün */}
|
||
{isDesignMode && (
|
||
<button
|
||
onClick={() => {
|
||
localStorage.removeItem(WIDGET_ORDER_KEY)
|
||
initializeDefaultOrder()
|
||
}}
|
||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors flex items-center gap-1"
|
||
title="Widget düzenini varsayılana döndür"
|
||
>
|
||
🔄 Sıfırla
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||
<span className="font-medium">Hoş geldiniz,</span>{' '}
|
||
{dayjs().format('DD MMMM YYYY dddd')}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-12">
|
||
<div
|
||
className={`lg:col-span-3 space-y-6 min-h-[100px] rounded-xl p-1
|
||
${
|
||
isDesignMode && dragState.targetColumn === 'left' && dragState.targetIndex === null
|
||
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
|
||
: 'bg-transparent'
|
||
}
|
||
transition-all duration-700 ease-out
|
||
`}
|
||
onDragOver={(e) => {
|
||
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 && (
|
||
<div className="flex items-center justify-center h-40 border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
|
||
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
|
||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||
/>
|
||
</svg>
|
||
<span>Widget'ı buraya bırakın</span>
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div
|
||
className={`lg:col-span-6 space-y-6 min-h-[100px] rounded-xl p-1
|
||
${
|
||
isDesignMode &&
|
||
dragState.targetColumn === 'center' &&
|
||
dragState.targetIndex === null
|
||
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
|
||
: 'bg-transparent'
|
||
}
|
||
transition-all duration-700 ease-out
|
||
`}
|
||
onDragOver={(e) => {
|
||
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 && (
|
||
<div className="flex items-center justify-center rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
|
||
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
|
||
<span>Widget'ı buraya bırakın</span>
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div
|
||
className={`lg:col-span-3 space-y-6 min-h-[100px] rounded-xl p-1
|
||
${
|
||
isDesignMode && dragState.targetColumn === 'right' && dragState.targetIndex === null
|
||
? 'bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-blue-300 dark:ring-blue-600 shadow-lg'
|
||
: 'bg-transparent'
|
||
}
|
||
transition-all duration-700 ease-out
|
||
`}
|
||
onDragOver={(e) => {
|
||
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 && (
|
||
<div className="flex items-center justify-center h-40 border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-xl bg-blue-50/50 dark:bg-blue-900/10 transition-all duration-500 ease-out">
|
||
<p className="text-blue-600 dark:text-blue-400 font-medium flex items-center gap-2">
|
||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||
/>
|
||
</svg>
|
||
<span>Widget'ı buraya bırakın</span>
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<AnimatePresence>
|
||
{showSurveyModal && selectedSurvey && (
|
||
<SurveyModal
|
||
survey={selectedSurvey}
|
||
onClose={() => setShowSurveyModal(false)}
|
||
onSubmit={handleSubmitSurvey}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{showLeaveModal && (
|
||
<LeaveRequestModal
|
||
onClose={() => setShowLeaveModal(false)}
|
||
onSubmit={handleSubmitLeave}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{showOvertimeModal && (
|
||
<OvertimeRequestModal
|
||
onClose={() => setShowOvertimeModal(false)}
|
||
onSubmit={handleSubmitOvertime}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{showExpenseModal && (
|
||
<ExpenseRequestModal
|
||
onClose={() => setShowExpenseModal(false)}
|
||
onSubmit={handleSubmitExpense}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{showReservationModal && (
|
||
<ReservationRequestModal
|
||
onClose={() => setShowReservationModal(false)}
|
||
onSubmit={handleSubmitReservation}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{selectedAnnouncement && (
|
||
<AnnouncementDetailModal
|
||
announcement={selectedAnnouncement}
|
||
onClose={() => setSelectedAnnouncement(null)}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
</Container>
|
||
)
|
||
}
|
||
|
||
export default IntranetDashboard
|