638 lines
23 KiB
TypeScript
638 lines
23 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import { AnimatePresence } from 'framer-motion'
|
||
import dayjs from 'dayjs'
|
||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||
import isBetween from 'dayjs/plugin/isBetween'
|
||
import localizedFormat from 'dayjs/plugin/localizedFormat'
|
||
|
||
import SurveyModal from './widgets/SurveyModal'
|
||
import AnnouncementModal from './widgets/AnnouncementModal'
|
||
import EventModal from './widgets/EventModal'
|
||
|
||
import { Container } from '@/components/shared'
|
||
import { usePermission } from '@/utils/hooks/usePermission'
|
||
import {
|
||
AnnouncementDto,
|
||
EventDto,
|
||
IntranetDashboardDto,
|
||
SurveyAnswerDto,
|
||
SurveyDto,
|
||
} from '@/proxy/intranet/models'
|
||
import { intranetService } from '@/services/intranet.service'
|
||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||
import useLocale from '@/utils/hooks/useLocale'
|
||
import { currentLocalDate } from '@/utils/dateUtils'
|
||
import { useStoreActions, useStoreState } from '@/store/store'
|
||
import Button from '@/components/ui/Button'
|
||
import { LuX } from 'react-icons/lu'
|
||
import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget'
|
||
|
||
dayjs.extend(relativeTime)
|
||
dayjs.extend(isBetween)
|
||
dayjs.extend(localizedFormat)
|
||
|
||
const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
|
||
|
||
type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition }
|
||
|
||
const dashboardWidgets = Object.values(
|
||
import.meta.glob<DashboardWidgetModule>(['./widgets/*.tsx', './SocialWall/index.tsx'], {
|
||
eager: true,
|
||
}),
|
||
)
|
||
.map((module) => module.dashboardWidget)
|
||
.filter((widget): widget is DashboardWidgetDefinition => Boolean(widget))
|
||
.sort((left, right) => left.order - right.order)
|
||
|
||
const layoutPresets = [
|
||
{
|
||
id: 'default',
|
||
columns: [3, 6, 3],
|
||
labelKey: '::App.Platform.Intranet.Dashboard.Layout.Default',
|
||
},
|
||
{
|
||
id: 'wide-center',
|
||
columns: [2, 8, 2],
|
||
labelKey: '::App.Platform.Intranet.Dashboard.Layout.CenterFocus',
|
||
},
|
||
{
|
||
id: 'balanced',
|
||
columns: [4, 4, 4],
|
||
labelKey: '::App.Platform.Intranet.Dashboard.Layout.Balanced',
|
||
},
|
||
{
|
||
id: 'wide-left',
|
||
columns: [4, 6, 2],
|
||
labelKey: '::App.Platform.Intranet.Dashboard.Layout.WideLeft',
|
||
},
|
||
{
|
||
id: 'wide-right',
|
||
columns: [2, 6, 4],
|
||
labelKey: '::App.Platform.Intranet.Dashboard.Layout.WideRight',
|
||
},
|
||
] as const
|
||
|
||
const columnSpanClasses: Record<number, string> = {
|
||
2: 'lg:col-span-2',
|
||
3: 'lg:col-span-3',
|
||
4: 'lg:col-span-4',
|
||
6: 'lg:col-span-6',
|
||
8: 'lg:col-span-8',
|
||
}
|
||
|
||
const IntranetDashboard: React.FC = () => {
|
||
const { checkPermission } = usePermission()
|
||
const [selectedAnnouncement, setSelectedAnnouncement] = useState<AnnouncementDto | null>(null)
|
||
const [selectedEvent, setSelectedEvent] = useState<EventDto | null>(null)
|
||
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
|
||
const [showSurveyModal, setShowSurveyModal] = useState(false)
|
||
const [isDesignMode, setIsDesignMode] = useState(false)
|
||
const { dashboardLayout: selectedLayout, hiddenWidgetIds, widgetOrder } = useStoreState(
|
||
(state) => state.admin.dashboard,
|
||
)
|
||
const {
|
||
setDashboardLayout,
|
||
setHiddenWidgetIds,
|
||
setWidgetOrder,
|
||
reset: resetDashboard,
|
||
} = useStoreActions((actions) => actions.admin.dashboard)
|
||
|
||
const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>()
|
||
const { translate } = useLocalization()
|
||
const currentLocale = useLocale()
|
||
|
||
const fetchIntranetDashboard = async () => {
|
||
try {
|
||
const dashboard = await intranetService.getDashboard()
|
||
if (dashboard.data) {
|
||
setIntranetDashboard(dashboard.data)
|
||
}
|
||
} catch {
|
||
// hata apiService tarafından ele alınıyor
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
fetchIntranetDashboard()
|
||
}, [])
|
||
|
||
const [dragState, setDragState] = useState<{
|
||
draggedId: string | null
|
||
targetColumn: DashboardWidgetColumn | null
|
||
targetIndex: number | null
|
||
}>({
|
||
draggedId: null,
|
||
targetColumn: null,
|
||
targetIndex: null,
|
||
})
|
||
|
||
const handleTakeSurvey = (survey: SurveyDto) => {
|
||
setSelectedSurvey(survey)
|
||
setShowSurveyModal(true)
|
||
}
|
||
|
||
const handleSubmitSurvey = async (answers: SurveyAnswerDto[]) => {
|
||
if (!selectedSurvey) return
|
||
try {
|
||
await intranetService.createSurveyResponse(
|
||
selectedSurvey.id,
|
||
answers.map((a) => ({
|
||
questionId: a.questionId,
|
||
questionType: a.questionType,
|
||
value: a.value !== undefined && a.value !== null ? String(a.value) : '',
|
||
})),
|
||
)
|
||
await fetchIntranetDashboard()
|
||
} catch (e) {
|
||
console.error('Survey submit error', e)
|
||
}
|
||
setShowSurveyModal(false)
|
||
setSelectedSurvey(null)
|
||
}
|
||
|
||
const handleEventLikeChange = (updatedEvent: EventDto) => {
|
||
setSelectedEvent((current) => (current ? { ...current, ...updatedEvent } : updatedEvent))
|
||
setIntranetDashboard((current) =>
|
||
current
|
||
? {
|
||
...current,
|
||
events: current.events.map((event) =>
|
||
event.id === updatedEvent.id ? { ...event, ...updatedEvent } : event,
|
||
),
|
||
}
|
||
: current,
|
||
)
|
||
}
|
||
|
||
const handleAnnouncementLikeChange = (updatedAnnouncement: AnnouncementDto) => {
|
||
setSelectedAnnouncement((current) =>
|
||
current ? { ...current, ...updatedAnnouncement } : updatedAnnouncement,
|
||
)
|
||
setIntranetDashboard((current) =>
|
||
current
|
||
? {
|
||
...current,
|
||
announcements: current.announcements.map((announcement) =>
|
||
announcement.id === updatedAnnouncement.id
|
||
? { ...announcement, ...updatedAnnouncement }
|
||
: announcement,
|
||
),
|
||
}
|
||
: current,
|
||
)
|
||
}
|
||
|
||
const grantedPolicies = useStoreState((state) => state.abpConfig?.config?.auth.grantedPolicies)
|
||
|
||
const initializeDefaultOrder = () => {
|
||
const defaultOrder: Record<DashboardWidgetColumn, string[]> = {
|
||
left: [],
|
||
center: [],
|
||
right: [],
|
||
}
|
||
dashboardWidgets.forEach((widget) => {
|
||
if (checkPermission(widget.permission)) defaultOrder[widget.column].push(widget.id)
|
||
})
|
||
setWidgetOrder(defaultOrder)
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (!grantedPolicies) return
|
||
|
||
const hasSavedOrder = dashboardColumns.some((column) => widgetOrder[column].length > 0)
|
||
if (!hasSavedOrder) {
|
||
initializeDefaultOrder()
|
||
return
|
||
}
|
||
|
||
// Kayıtlı sıralamayı tekilleştir ve henüz yerleştirilmemiş widget'ları ekle
|
||
const order: Record<DashboardWidgetColumn, string[]> = {
|
||
left: [...new Set(widgetOrder.left || [])],
|
||
center: [...new Set(widgetOrder.center || [])],
|
||
right: [...new Set(widgetOrder.right || [])],
|
||
}
|
||
|
||
const allAssigned = new Set([...order.left, ...order.center, ...order.right])
|
||
dashboardWidgets.forEach((widget) => {
|
||
if (!allAssigned.has(widget.id) && checkPermission(widget.permission)) {
|
||
order[widget.column].push(widget.id)
|
||
}
|
||
})
|
||
|
||
setWidgetOrder(order)
|
||
}, [grantedPolicies])
|
||
|
||
const setWidgetVisibility = (widgetId: string, visible: boolean) => {
|
||
const next = visible
|
||
? hiddenWidgetIds.filter((id) => id !== widgetId)
|
||
: [...new Set([...hiddenWidgetIds, widgetId])]
|
||
setHiddenWidgetIds(next)
|
||
}
|
||
|
||
const activeLayout =
|
||
layoutPresets.find((layout) => layout.id === selectedLayout) || layoutPresets[0]
|
||
const hiddenWidgets = dashboardWidgets.filter(
|
||
(widget) => hiddenWidgetIds.includes(widget.id) && checkPermission(widget.permission),
|
||
)
|
||
|
||
const handleDragStart = (e: React.DragEvent, widgetId: string) => {
|
||
setDragState({ draggedId: widgetId, targetColumn: null, targetIndex: null })
|
||
e.dataTransfer.effectAllowed = 'move'
|
||
e.dataTransfer.setData('widgetId', widgetId)
|
||
}
|
||
|
||
const handleDragEnterWidget = (
|
||
e: React.DragEvent,
|
||
column: DashboardWidgetColumn,
|
||
index: number,
|
||
) => {
|
||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
||
if (e.clientY - rect.top < rect.height * 0.3) {
|
||
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: index }))
|
||
} else {
|
||
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null }))
|
||
}
|
||
}
|
||
|
||
const handleDragEnterColumn = (column: DashboardWidgetColumn) => {
|
||
setDragState((prev) => ({ ...prev, targetColumn: column, targetIndex: null }))
|
||
}
|
||
|
||
const handleDragLeaveColumn = () => {
|
||
setDragState((prev) => ({ ...prev, targetColumn: null, targetIndex: null }))
|
||
}
|
||
|
||
const handleDrop = (
|
||
e: React.DragEvent,
|
||
targetColumn: DashboardWidgetColumn,
|
||
targetIndex?: number,
|
||
) => {
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
|
||
const widgetId = e.dataTransfer.getData('widgetId')
|
||
if (!widgetId) return
|
||
|
||
const newOrder = { ...widgetOrder }
|
||
|
||
dashboardColumns.forEach((col) => {
|
||
newOrder[col] = newOrder[col].filter((id) => id !== widgetId)
|
||
})
|
||
|
||
if (targetIndex !== undefined) {
|
||
newOrder[targetColumn].splice(targetIndex, 0, widgetId)
|
||
} else {
|
||
newOrder[targetColumn].push(widgetId)
|
||
}
|
||
|
||
setWidgetOrder(newOrder)
|
||
setDragState({ draggedId: null, targetColumn: null, targetIndex: null })
|
||
}
|
||
|
||
const handleDragEnd = () => {
|
||
setDragState({ draggedId: null, targetColumn: null, targetIndex: null })
|
||
}
|
||
|
||
const handleColumnDragOver = (
|
||
e: React.DragEvent<HTMLDivElement>,
|
||
column: DashboardWidgetColumn,
|
||
) => {
|
||
if (!isDesignMode) return
|
||
e.preventDefault()
|
||
|
||
const now = Date.now()
|
||
const lastUpdate = Number(e.currentTarget.dataset.lastColumnUpdate || 0)
|
||
if (now - lastUpdate > 150) {
|
||
e.currentTarget.dataset.lastColumnUpdate = now.toString()
|
||
handleDragEnterColumn(column)
|
||
}
|
||
}
|
||
|
||
const handleColumnDrop = (e: React.DragEvent<HTMLDivElement>, column: DashboardWidgetColumn) => {
|
||
if (!isDesignMode) return
|
||
handleDrop(e, column, widgetOrder[column].length)
|
||
}
|
||
|
||
const renderWidgets = (column: DashboardWidgetColumn) => {
|
||
const columnWidgets = widgetOrder[column] || []
|
||
|
||
const uniqueWidgets = [...new Set(columnWidgets)]
|
||
|
||
return uniqueWidgets
|
||
.map((widgetId, index) => {
|
||
const metadata = dashboardWidgets.find((w) => w.id === widgetId)
|
||
if (
|
||
!metadata ||
|
||
!checkPermission(metadata.permission) ||
|
||
hiddenWidgetIds.includes(widgetId)
|
||
)
|
||
return null
|
||
|
||
const isDragging = dragState.draggedId === widgetId
|
||
const isDropTarget = dragState.targetColumn === column && dragState.targetIndex === index
|
||
|
||
return (
|
||
<div key={`${column}-${widgetId}`} className="relative group">
|
||
{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">
|
||
<div className="h-2 bg-gradient-to-r from-transparent via-blue-500 to-transparent rounded-full shadow-lg" />
|
||
<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>{translate('::App.Platform.Intranet.Dashboard.DropHere')}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div
|
||
draggable={isDesignMode}
|
||
onDragStart={(e) => {
|
||
if (isDesignMode) {
|
||
handleDragStart(e, widgetId)
|
||
}
|
||
}}
|
||
onDragOver={(e) => {
|
||
if (!isDesignMode) return
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
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={() => {
|
||
if (isDesignMode) {
|
||
setDragState((prev) => ({
|
||
...prev,
|
||
targetColumn: prev.targetColumn,
|
||
targetIndex: null,
|
||
}))
|
||
}
|
||
}}
|
||
onDrop={(e) => {
|
||
if (!isDesignMode) return
|
||
e.stopPropagation()
|
||
|
||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
||
if (e.clientY - rect.top < rect.height * 0.3) {
|
||
handleDrop(e, column, index)
|
||
} else {
|
||
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: isDesignMode ? 'none' : 'pan-y',
|
||
transition:
|
||
'border-color 0.3s ease-out, opacity 0.3s ease-out, box-shadow 0.3s ease-out',
|
||
willChange: isDragging ? 'opacity' : 'auto',
|
||
}}
|
||
>
|
||
{isDesignMode && !isDragging && (
|
||
<button
|
||
type="button"
|
||
className="absolute right-2 top-2 z-20 flex h-7 w-7 items-center justify-center rounded-full bg-white text-gray-500 shadow-sm dark:bg-gray-800 dark:text-gray-300"
|
||
title={translate('::App.Platform.Intranet.Dashboard.HideWidget')}
|
||
aria-label={`${translate('::App.Platform.Intranet.Dashboard.HideWidget')}: ${translate(metadata.labelKey)}`}
|
||
draggable={false}
|
||
onMouseDown={(e) => e.stopPropagation()}
|
||
onDragStart={(e) => e.stopPropagation()}
|
||
onClick={(e) => {
|
||
e.stopPropagation()
|
||
setWidgetVisibility(widgetId, false)
|
||
}}
|
||
>
|
||
<LuX className="h-4 w-4" aria-hidden="true" />
|
||
</button>
|
||
)}
|
||
{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>{translate('::App.Platform.Intranet.Dashboard.Moving')}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div
|
||
className={`${isDesignMode ? 'p-1.5' : ''} transition-all duration-500 ease-out`}
|
||
>
|
||
{metadata.render({
|
||
dashboard: intranetDashboard,
|
||
openAnnouncement: setSelectedAnnouncement,
|
||
openEvent: setSelectedEvent,
|
||
takeSurvey: handleTakeSurvey,
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
})
|
||
.filter(Boolean)
|
||
}
|
||
|
||
return (
|
||
<Container>
|
||
<div className="mx-auto space-y-4">
|
||
<div className="relative flex flex-wrap items-center justify-between gap-4 lg:min-h-10">
|
||
<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"
|
||
>
|
||
🎨 {translate('::App.Platform.Intranet.Dashboard.DesignMode')}
|
||
</label>
|
||
<Button
|
||
id="design-mode-toggle"
|
||
onClick={() => setIsDesignMode(!isDesignMode)}
|
||
variant="plain"
|
||
shape="none"
|
||
className={`
|
||
relative !inline-flex !h-6 !w-11 !min-w-11 !items-center !justify-start !rounded-full !border-0 !px-0 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
|
||
${isDesignMode ? '!bg-blue-600 hover:!bg-blue-600' : '!bg-gray-300 hover:!bg-gray-300 dark:!bg-gray-600 dark:hover:!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>
|
||
{isDesignMode && (
|
||
<Button
|
||
onClick={() => {
|
||
resetDashboard()
|
||
initializeDefaultOrder()
|
||
}}
|
||
variant="plain"
|
||
shape="none"
|
||
className="!h-auto !items-center gap-1 !rounded-none !px-0 !py-0 text-sm text-gray-500 transition-colors hover:!bg-transparent hover:text-gray-700 active:!bg-transparent focus:!bg-transparent dark:text-gray-400 dark:hover:text-gray-200"
|
||
title={translate('::App.Platform.Intranet.Dashboard.ResetTitle')}
|
||
>
|
||
🔄 {translate('::Reset')}
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
{isDesignMode && (
|
||
<div
|
||
className="order-last flex w-full items-center justify-center gap-1 lg:absolute lg:left-1/2 lg:top-1/2 lg:order-none lg:w-auto lg:-translate-x-1/2 lg:-translate-y-1/2"
|
||
role="group"
|
||
aria-label={translate('::App.Platform.Intranet.Dashboard.Layout')}
|
||
>
|
||
<span className="mr-1 text-sm text-gray-500 dark:text-gray-400">
|
||
{translate('::App.Platform.Intranet.Dashboard.Layout')}
|
||
</span>
|
||
{layoutPresets.map((layout) => (
|
||
<Button
|
||
key={layout.id}
|
||
type="button"
|
||
variant="plain"
|
||
shape="none"
|
||
onClick={() => setDashboardLayout(layout.id)}
|
||
className={`!h-8 !rounded-md !px-2 ${selectedLayout === layout.id ? '!bg-blue-600 text-white hover:!bg-blue-600' : '!bg-gray-100 text-gray-600 hover:!bg-gray-200 dark:!bg-gray-700 dark:text-gray-200'}`}
|
||
title={`${translate(layout.labelKey)} (${layout.columns.join(' / ')})`}
|
||
aria-label={`${translate(layout.labelKey)} (${layout.columns.join(' / ')})`}
|
||
aria-pressed={selectedLayout === layout.id}
|
||
>
|
||
<span className="flex h-4 w-12 gap-0.5" aria-hidden="true">
|
||
{layout.columns.map((width, index) => (
|
||
<span
|
||
key={index}
|
||
className="h-full rounded-sm bg-current opacity-70"
|
||
style={{ flex: width }}
|
||
/>
|
||
))}
|
||
</span>
|
||
</Button>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<p className="mt-1 text-gray-600 dark:text-gray-400">
|
||
<span className="font-medium">{translate('::AI.Welcome')},</span>{' '}
|
||
{currentLocalDate(new Date(), currentLocale || 'tr')}
|
||
</p>
|
||
</div>
|
||
|
||
{isDesignMode && hiddenWidgets.length > 0 && (
|
||
<div className="flex flex-wrap items-center gap-1">
|
||
<span className="mr-1 text-sm text-gray-500 dark:text-gray-400">
|
||
{translate('::App.Platform.Intranet.Dashboard.HiddenWidgets')}:
|
||
</span>
|
||
{hiddenWidgets.map((widget) => (
|
||
<Button
|
||
key={widget.id}
|
||
type="button"
|
||
variant="plain"
|
||
shape="none"
|
||
className="!h-8 !rounded-md !bg-emerald-50 !px-2 text-xs text-emerald-700 hover:!bg-emerald-100 dark:!bg-emerald-950 dark:text-emerald-300"
|
||
onClick={() => setWidgetVisibility(widget.id, true)}
|
||
title={translate('::App.Platform.Intranet.Dashboard.ShowWidget')}
|
||
>
|
||
+ {translate(widget.labelKey)}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-12">
|
||
{dashboardColumns.map((column, index) => {
|
||
const isEmptyDropTarget =
|
||
isDesignMode && dragState.targetColumn === column && widgetOrder[column].length === 0
|
||
|
||
return (
|
||
<div
|
||
key={column}
|
||
className={`${columnSpanClasses[activeLayout.columns[index]]} min-h-[100px] space-y-6 rounded-xl p-1 ${
|
||
isDesignMode &&
|
||
dragState.targetColumn === column &&
|
||
dragState.targetIndex === null
|
||
? 'bg-blue-50/80 ring-2 ring-blue-300 shadow-lg dark:bg-blue-900/20 dark:ring-blue-600'
|
||
: 'bg-transparent'
|
||
} transition-all duration-700 ease-out`}
|
||
onDragOver={(e) => handleColumnDragOver(e, column)}
|
||
onDragLeave={handleDragLeaveColumn}
|
||
onDrop={(e) => handleColumnDrop(e, column)}
|
||
>
|
||
{renderWidgets(column)}
|
||
{isEmptyDropTarget && (
|
||
<div className="flex h-40 items-center justify-center rounded-xl border-2 border-dashed border-blue-300 bg-blue-50/50 dark:border-blue-600 dark:bg-blue-900/10">
|
||
<span className="font-medium text-blue-600 dark:text-blue-400">
|
||
{translate('::App.Platform.Intranet.Dashboard.DropWidgetHere')}
|
||
</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
<AnimatePresence>
|
||
{showSurveyModal && selectedSurvey && (
|
||
<SurveyModal
|
||
survey={selectedSurvey}
|
||
onClose={() => setShowSurveyModal(false)}
|
||
onSubmit={handleSubmitSurvey}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{selectedEvent && (
|
||
<EventModal
|
||
event={selectedEvent}
|
||
onClose={() => setSelectedEvent(null)}
|
||
onLikeChange={handleEventLikeChange}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
|
||
<AnimatePresence>
|
||
{selectedAnnouncement && (
|
||
<AnnouncementModal
|
||
announcement={selectedAnnouncement}
|
||
onClose={() => setSelectedAnnouncement(null)}
|
||
onLikeChange={handleAnnouncementLikeChange}
|
||
/>
|
||
)}
|
||
</AnimatePresence>
|
||
</Container>
|
||
)
|
||
}
|
||
|
||
export default IntranetDashboard
|