diff --git a/src/feature-module/Application/calendar.jsx b/src/feature-module/Application/calendar.jsx
index 4904ed3..3cbafd5 100644
--- a/src/feature-module/Application/calendar.jsx
+++ b/src/feature-module/Application/calendar.jsx
@@ -1,7 +1,7 @@
/* eslint-disable no-dupe-keys */
/* eslint-disable no-const-assign */
/* eslint-disable no-unused-vars */
-import React, { useState, useEffect, useRef } from "react";
+import React, { useState, useEffect, useRef, useCallback } from "react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
@@ -9,6 +9,7 @@ import interactionPlugin from "@fullcalendar/interaction";
import { Draggable } from "@fullcalendar/interaction";
// import "../../assets/plugins/fullcalendar/fullcalendar.min.css";
import "../../style/css/fullcalendar.min.css";
+import "../../style/css/calendar-custom.css";
// import FullCalendar from '@fullcalendar/react/dist/main.esm.js';
import Select from "react-select";
@@ -65,15 +66,26 @@ const Calendar = () => {
className: "bg-warning",
},
]);
+ // Add ref to prevent multiple initialization
+ const initializedRef = React.useRef(false);
+
useEffect(() => {
+ // Prevent multiple initialization
+ if (initializedRef.current) {
+ console.log("🚫 Calendar already initialized, skipping");
+ return;
+ }
+
let elements = Array.from(
document.getElementsByClassName("react-datepicker-wrapper")
);
elements.map((element) => element.classList.add("width-100"));
- // Initialize external draggable events with simple hide/show
+ // Initialize external draggable events with enhanced duplicate prevention
const draggableEl = document.getElementById("calendar-events");
if (draggableEl) {
+ console.log("🚀 Initializing calendar draggable events");
+
new Draggable(draggableEl, {
itemSelector: ".calendar-events",
eventData: function(eventEl) {
@@ -91,10 +103,9 @@ const Calendar = () => {
// Store reference to currently dragging element
let currentDragElement = null;
- let dragHelper = null;
// Listen for drag start from external elements
- draggableEl.addEventListener('dragstart', function(e) {
+ const handleDragStart = (e) => {
const target = e.target.closest('.calendar-events');
if (target) {
currentDragElement = target;
@@ -102,27 +113,42 @@ const Calendar = () => {
setTimeout(() => {
if (currentDragElement) {
currentDragElement.classList.add('dragging-hidden');
+ console.log("🎯 Hiding dragged element:", target.innerText.trim());
}
}, 10); // Small delay to let drag start
}
- });
-
- // Simple approach - just hide the original item during drag
- // No custom helper, let FullCalendar handle the drag visual
+ };
// Listen for drag end
- document.addEventListener('dragend', function(e) {
+ const handleDragEnd = (e) => {
if (currentDragElement) {
currentDragElement.classList.remove('dragging-hidden');
+ console.log("🎯 Showing dragged element back");
currentDragElement = null;
}
- if (dragHelper && dragHelper.parentNode) {
- dragHelper.parentNode.removeChild(dragHelper);
- dragHelper = null;
- }
- });
+ };
+
+ draggableEl.addEventListener('dragstart', handleDragStart);
+ document.addEventListener('dragend', handleDragEnd);
+
+ // Mark as initialized
+ initializedRef.current = true;
+ console.log("✅ Calendar draggable events initialized successfully");
+
+ // Cleanup function
+ return () => {
+ draggableEl.removeEventListener('dragstart', handleDragStart);
+ document.removeEventListener('dragend', handleDragEnd);
+ initializedRef.current = false;
+ console.log("🧹 Calendar drag listeners cleaned up");
+ };
}
- }, []);
+ }, []); // Empty dependency array for one-time initialization
+
+ // Debug useEffect to track calendarEvents changes - DISABLED to prevent re-renders
+ // useEffect(() => {
+ // console.log("🔥 calendarEvents changed:", calendarEvents.length, calendarEvents);
+ // }, [calendarEvents]);
const handleChange = (date) => {
setDate(date);
@@ -154,48 +180,65 @@ const Calendar = () => {
setaddneweventobj(selectInfo);
};
- const handleEventReceive = (info) => {
- // Handle external drag and drop
- console.log("Event received:", info.event);
+ // Add ref to track processing state more reliably
+ const processingRef = React.useRef(false);
+ const lastDropTime = React.useRef(0);
- // Prevent FullCalendar from automatically adding the event
- // We'll handle it manually to avoid duplicates
+ const handleEventReceive = useCallback((info) => {
+ const now = Date.now();
+ const timeSinceLastDrop = now - lastDropTime.current;
+
+ // Handle external drag and drop with enhanced duplicate prevention
+ console.log("🔥 handleEventReceive called - Event:", info.event.title);
+
+ // Prevent duplicate processing within 300ms
+ if (processingRef.current || timeSinceLastDrop < 300) {
+ console.log("🚫 Duplicate drop prevented:", {
+ processing: processingRef.current,
+ timeSinceLastDrop
+ });
+ info.revert();
+ return;
+ }
+
+ processingRef.current = true;
+ lastDropTime.current = now;
+
+ // Prevent default behavior
info.revert();
- // Create event object
+ // Create event object with unique ID
+ const uniqueId = `dropped-${now}-${Math.random().toString(36).substr(2, 9)}`;
const newEvent = {
- id: `dropped-${Date.now()}`,
+ id: uniqueId,
title: info.event.title,
start: info.event.start,
- end: info.event.end || new Date(info.event.start.getTime() + 60 * 60 * 1000), // Default 1 hour duration
+ end: info.event.end || new Date(info.event.start.getTime() + 60 * 60 * 1000),
className: info.event.classNames[0] || 'bg-primary',
droppedAt: new Date().toLocaleString(),
source: 'external'
};
- // Add to calendar events state to display on calendar
+ console.log("✅ Creating new event:", uniqueId);
+
+ // Update calendar events
setCalendarEvents(prev => [...prev, newEvent]);
- // Add to dropped events list for tracking
+ // Add to dropped events list
setDroppedEvents(prev => [...prev, newEvent]);
- // Show success notification in console only
- console.log("✅ Event successfully dropped:", newEvent);
-
- // Show the original item again (in case it was hidden)
- const draggedEl = info.draggedEl;
- if (draggedEl) {
- draggedEl.classList.remove('dragging-hidden');
- }
-
- // Check if "Remove after drop" is checked
- const removeAfterDrop = document.getElementById("drop-remove").checked;
- if (removeAfterDrop) {
- // Remove the dragged element from the external list
+ // Handle "Remove after drop" option
+ const removeAfterDrop = document.getElementById("drop-remove")?.checked;
+ if (removeAfterDrop && info.draggedEl) {
info.draggedEl.remove();
console.log("🗑️ Original event removed from sidebar");
}
- };
+
+ // Reset processing flag
+ setTimeout(() => {
+ processingRef.current = false;
+ }, 300);
+ }, []); // Remove dependencies to prevent unnecessary re-creation
const handleEventDrop = (info) => {
// Handle internal event drag and drop
@@ -331,32 +374,52 @@ const Calendar = () => {
{/* Dropped Events Tracker */}
{droppedEvents.length > 0 && (
-
✅ Recently Dropped Events ({droppedEvents.length})
+
+ ✅ Recently Dropped Events
+ {droppedEvents.length}
+
- {droppedEvents.slice(-5).map((event, index) => (
-
-
-
+ {droppedEvents.slice(-8).map((event) => (
+
+
+
{event.title}
-
-
- 📅 {event.start.toLocaleDateString()} at {event.start.toLocaleTimeString()}
-
-
-
- ⏰ Dropped: {event.droppedAt}
-
+
+ 📅
+
+ {event.start.toLocaleDateString()} • {event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
+
+
+
+ ⏰
+
+ {event.droppedAt}
+
+
+
+
+
+
-
- {event.className.replace('bg-', '')}
-
))}
- {droppedEvents.length > 5 && (
-
- ... and {droppedEvents.length - 5} more events
-
+ {droppedEvents.length > 8 && (
+
+
+ ... and {droppedEvents.length - 8} more events
+
+
)}
diff --git a/src/feature-module/projects/projecttracker.jsx b/src/feature-module/projects/projecttracker.jsx
index 334ac09..a6a0628 100644
--- a/src/feature-module/projects/projecttracker.jsx
+++ b/src/feature-module/projects/projecttracker.jsx
@@ -35,12 +35,39 @@ const ProjectTracker = () => {
const [totalCount, setTotalCount] = useState(0);
const [totalPages, setTotalPages] = useState(1);
- // Load projects from API
- const loadProjects = async (page = currentPage, size = pageSize) => {
+ // Add loading ref to prevent duplicate calls with timestamp
+ const loadingRef = React.useRef(false);
+ const lastCallRef = React.useRef(0);
+ const mountedRef = React.useRef(false);
+
+ // Load projects from API with enhanced duplicate prevention
+ const loadProjects = React.useCallback(async (page = currentPage, size = pageSize) => {
+ const now = Date.now();
+ const timeSinceLastCall = now - lastCallRef.current;
+
+ // Prevent duplicate API calls within 500ms
+ if (loadingRef.current || timeSinceLastCall < 500) {
+ console.log('🚫 API call blocked - already in progress or too soon:', {
+ loading: loadingRef.current,
+ timeSinceLastCall,
+ mounted: mountedRef.current
+ });
+ return;
+ }
+
+ // Only proceed if component is mounted
+ if (!mountedRef.current) {
+ console.log('🚫 Component not mounted, skipping API call');
+ return;
+ }
+
+ lastCallRef.current = now;
+ loadingRef.current = true;
setLoading(true);
+
try {
const apiBaseUrl = process.env.REACT_APP_API_BASE_URL || '';
- console.log('Loading projects from:', `${apiBaseUrl}Projects`);
+ console.log('📡 Loading projects from:', `${apiBaseUrl}Projects?page=${page}&pageSize=${size}`);
const response = await fetch(`${apiBaseUrl}Projects?page=${page}&pageSize=${size}`, {
method: 'GET',
@@ -55,7 +82,7 @@ const ProjectTracker = () => {
}
const result = await response.json();
- console.log('API Response:', result);
+ console.log('✅ API Response:', result);
if (result.data) {
// Map API data to table format
@@ -99,15 +126,20 @@ const ProjectTracker = () => {
setTotalPages(1);
}
} catch (error) {
- console.error('Error loading projects:', error);
- // Set empty data on error
- setProjectData([]);
- setTotalCount(0);
- setTotalPages(1);
+ console.error('💥 Error loading projects:', error);
+ // Only update state if component is still mounted
+ if (mountedRef.current) {
+ setProjectData([]);
+ setTotalCount(0);
+ setTotalPages(1);
+ }
} finally {
- setLoading(false);
+ if (mountedRef.current) {
+ setLoading(false);
+ }
+ loadingRef.current = false; // Reset loading ref
}
- };
+ }, [currentPage, pageSize]); // Add dependencies for useCallback
// Helper functions for mapping
const getCategoryColor = (categoryName) => {
@@ -188,6 +220,12 @@ const ProjectTracker = () => {
// Delete project function
const handleDeleteProject = async (projectId) => {
+ // Prevent multiple delete operations
+ if (loading || loadingRef.current) {
+ console.log('🚫 Operation already in progress, ignoring delete request');
+ return;
+ }
+
Modal.confirm({
title: 'Xác nhận xóa dự án',
content: 'Bạn có chắc chắn muốn xóa dự án này không? Hành động này không thể hoàn tác.',
@@ -260,34 +298,47 @@ const ProjectTracker = () => {
});
};
- // Load data on component mount
+ // Mount/unmount management
useEffect(() => {
+ mountedRef.current = true;
+ console.log('🚀 Component mounted - loading projects');
+
+ // Load projects on mount
loadProjects();
- }, []);
+
+ // Cleanup on unmount
+ return () => {
+ console.log('🔄 Component unmounting - cleaning up');
+ mountedRef.current = false;
+ loadingRef.current = false;
+ lastCallRef.current = 0;
+ };
+ }, [loadProjects]); // Include loadProjects in dependencies
// Handle pagination change
const handlePageChange = (page) => {
- setCurrentPage(page);
- loadProjects(page, pageSize);
+ if (page !== currentPage && !loading) {
+ setCurrentPage(page);
+ loadProjects(page, pageSize);
+ }
};
// Handle page size change
const handlePageSizeChange = (newPageSize) => {
- setPageSize(newPageSize);
- setCurrentPage(1); // Reset to first page when changing page size
- loadProjects(1, newPageSize);
+ if (newPageSize !== pageSize && !loading) {
+ setPageSize(newPageSize);
+ setCurrentPage(1); // Reset to first page when changing page size
+ loadProjects(1, newPageSize);
+ }
};
- // Handle table change (for Ant Design Table)
- const handleTableChange = (paginationInfo) => {
- if (paginationInfo.current !== currentPage) {
- handlePageChange(paginationInfo.current);
- }
- if (paginationInfo.pageSize !== pageSize) {
- handlePageSizeChange(paginationInfo.pageSize);
- }
+ // Handle table change (for Ant Design Table) - DISABLED to prevent double calls
+ const handleTableChange = () => {
+ // Disabled to prevent duplicate API calls since we use CustomPagination
+ // The CustomPagination component handles all pagination logic
+ console.log('Table change event ignored to prevent duplicate API calls');
};
@@ -345,7 +396,7 @@ const ProjectTracker = () => {
dataIndex: 'manager',
key: 'manager',
render: (managers) => (
-
+
{managers.map((manager, index) => (
-
-
);
} else {
console.error("Element with id 'root' not found.");
diff --git a/src/style/css/calendar-custom.css b/src/style/css/calendar-custom.css
new file mode 100644
index 0000000..05db97d
--- /dev/null
+++ b/src/style/css/calendar-custom.css
@@ -0,0 +1,1044 @@
+/* Custom Calendar Styles - Enhanced for better UX and duplicate prevention */
+
+/* Dragging States */
+.dragging-hidden {
+ opacity: 0.3 !important;
+ pointer-events: none !important;
+ transition: opacity 0.2s ease !important;
+ transform: scale(0.95) !important;
+}
+
+.calendar-events {
+ transition: all 0.2s ease !important;
+ cursor: grab !important;
+ padding: 8px 12px !important;
+ margin: 5px 0 !important;
+ border-radius: 8px !important;
+ border: 1px solid transparent !important;
+ background: linear-gradient(45deg, #f8f9fa, #e9ecef) !important;
+ color: #495057 !important;
+ font-weight: 500 !important;
+}
+
+.calendar-events:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
+ border-color: #007bff !important;
+ background: linear-gradient(45deg, #ffffff, #f8f9fa) !important;
+}
+
+.calendar-events:active {
+ cursor: grabbing !important;
+ transform: scale(0.98) !important;
+}
+
+/* Calendar Page Wrapper */
+.calendar-page-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
+ min-height: 100vh !important;
+ padding: 20px !important;
+}
+
+.calendar-page-wrapper .content {
+ background: transparent !important;
+}
+
+/* Calendar Header */
+.calendar-page-header {
+ background: rgba(255,255,255,0.95) !important;
+ border-radius: 15px !important;
+ padding: 20px !important;
+ margin-bottom: 20px !important;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1) !important;
+ backdrop-filter: blur(10px) !important;
+ border: 1px solid rgba(255,255,255,0.2) !important;
+}
+
+.calendar-page-header h3 {
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+ font-weight: 700 !important;
+ margin: 0 !important;
+}
+
+/* Calendar Main Card */
+.calendar-main-card {
+ border-radius: 15px !important;
+ box-shadow: 0 15px 35px rgba(0,0,0,0.1) !important;
+ border: none !important;
+ overflow: visible !important;
+ background: rgba(255,255,255,0.95) !important;
+ backdrop-filter: blur(10px) !important;
+}
+
+.calendar-main-card .card-body {
+ padding: 15px !important;
+ overflow: visible !important;
+}
+
+/* FullCalendar Container */
+.fc {
+ overflow: visible !important;
+}
+
+.fc-view-harness-active {
+ overflow: visible !important;
+}
+
+/* Calendar Sidebar */
+.calendar-sidebar {
+ background: rgba(255,255,255,0.95) !important;
+ border-radius: 15px !important;
+ padding: 25px !important;
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1) !important;
+ backdrop-filter: blur(10px) !important;
+ border: 1px solid rgba(255,255,255,0.2) !important;
+ margin-bottom: 20px !important;
+}
+
+.calendar-sidebar .card-title {
+ color: #495057 !important;
+ font-weight: 600 !important;
+ margin-bottom: 20px !important;
+ font-size: 1.1rem !important;
+}
+
+/* Create Event Button */
+.calendar-create-btn {
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%) !important;
+ border: none !important;
+ border-radius: 25px !important;
+ padding: 12px 30px !important;
+ color: white !important;
+ text-decoration: none !important;
+ transition: all 0.3s ease !important;
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
+ font-weight: 500 !important;
+ display: inline-block !important;
+}
+
+.calendar-create-btn:hover {
+ transform: translateY(-3px) !important;
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6) !important;
+ color: white !important;
+ text-decoration: none !important;
+}
+
+.calendar-create-btn:active {
+ transform: translateY(-1px) !important;
+}
+
+/* Add Category Button */
+.calendar-add-category-btn {
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
+ border: none !important;
+ border-radius: 20px !important;
+ padding: 10px 20px !important;
+ color: white !important;
+ text-decoration: none !important;
+ transition: all 0.3s ease !important;
+ box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3) !important;
+ font-weight: 500 !important;
+ display: inline-block !important;
+ width: 100% !important;
+ text-align: center !important;
+}
+
+.calendar-add-category-btn:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 6px 20px rgba(40, 167, 69, 0.5) !important;
+ color: white !important;
+ text-decoration: none !important;
+}
+
+/* Dropped Events Tracker */
+.dropped-events-list {
+ max-height: 400px !important;
+ overflow-y: auto !important;
+ overflow-x: hidden !important;
+ padding-right: 5px !important;
+}
+
+.dropped-events-list::-webkit-scrollbar {
+ width: 6px !important;
+}
+
+.dropped-events-list::-webkit-scrollbar-track {
+ background: rgba(0,0,0,0.1) !important;
+ border-radius: 3px !important;
+}
+
+.dropped-events-list::-webkit-scrollbar-thumb {
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ border-radius: 3px !important;
+}
+
+.dropped-events-list::-webkit-scrollbar-thumb:hover {
+ background: linear-gradient(45deg, #764ba2, #667eea) !important;
+}
+
+.dropped-event-item {
+ background: linear-gradient(45deg, #f8f9fa, #e9ecef) !important;
+ border: 1px solid #dee2e6 !important;
+ border-radius: 12px !important;
+ transition: all 0.3s ease !important;
+ overflow: hidden !important;
+ word-wrap: break-word !important;
+ word-break: break-word !important;
+ margin-bottom: 10px !important;
+}
+
+.dropped-event-item:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15) !important;
+ border-color: #007bff !important;
+ background: linear-gradient(45deg, #ffffff, #f8f9fa) !important;
+}
+
+.dropped-event-item .d-flex {
+ flex-wrap: nowrap !important;
+ gap: 12px !important;
+ align-items: flex-start !important;
+}
+
+.dropped-event-item .badge {
+ width: 20px !important;
+ height: 20px !important;
+ border-radius: 50% !important;
+ display: inline-block !important;
+ flex-shrink: 0 !important;
+ position: relative !important;
+ z-index: 10 !important;
+ box-shadow: 0 3px 10px rgba(0,0,0,0.2) !important;
+ border: 2px solid rgba(255,255,255,0.3) !important;
+ transition: all 0.3s ease !important;
+ cursor: pointer !important;
+}
+
+.dropped-event-item strong {
+ color: #495057 !important;
+ font-weight: 600 !important;
+ font-size: 0.9rem !important;
+ line-height: 1.4 !important;
+ word-break: break-word !important;
+}
+
+.dropped-event-item small {
+ font-size: 0.75rem !important;
+ line-height: 1.4 !important;
+ word-break: break-word !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 4px !important;
+ margin-bottom: 3px !important;
+}
+
+.dropped-event-item .text-muted {
+ color: #6c757d !important;
+}
+
+.dropped-event-item .text-success {
+ color: #28a745 !important;
+ font-weight: 500 !important;
+}
+
+.dropped-event-item .event-icon {
+ font-size: 0.8rem !important;
+ margin-right: 4px !important;
+ display: inline-flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 16px !important;
+ height: 16px !important;
+ flex-shrink: 0 !important;
+}
+
+.dropped-event-item .event-time {
+ display: flex !important;
+ align-items: center !important;
+ gap: 6px !important;
+ margin: 4px 0 !important;
+}
+
+.dropped-event-item .event-dropped-time {
+ display: flex !important;
+ align-items: center !important;
+ gap: 6px !important;
+ margin: 4px 0 !important;
+}
+
+/* Recently Dropped Events Header */
+.dropped-events-header {
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
+ color: white !important;
+ padding: 12px 15px !important;
+ border-radius: 10px !important;
+ margin-bottom: 15px !important;
+ box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3) !important;
+ font-weight: 600 !important;
+ font-size: 1rem !important;
+ text-align: center !important;
+}
+
+.dropped-events-count {
+ background: rgba(255,255,255,0.2) !important;
+ padding: 2px 8px !important;
+ border-radius: 12px !important;
+ font-size: 0.8rem !important;
+ margin-left: 8px !important;
+}
+
+/* Specific badge colors with proper contrast */
+.dropped-event-item .badge.bg-primary {
+ background: linear-gradient(45deg, #007bff, #0056b3) !important;
+ color: white !important;
+ border: 1px solid #0056b3 !important;
+}
+
+.dropped-event-item .badge.bg-success {
+ background: linear-gradient(45deg, #28a745, #1e7e34) !important;
+ color: white !important;
+ border: 1px solid #1e7e34 !important;
+}
+
+.dropped-event-item .badge.bg-warning {
+ background: linear-gradient(45deg, #ffc107, #e0a800) !important;
+ color: #212529 !important;
+ border: 1px solid #e0a800 !important;
+ font-weight: 700 !important;
+}
+
+.dropped-event-item .badge.bg-danger {
+ background: linear-gradient(45deg, #dc3545, #c82333) !important;
+ color: white !important;
+ border: 1px solid #c82333 !important;
+}
+
+.dropped-event-item .badge.bg-info {
+ background: linear-gradient(45deg, #17a2b8, #138496) !important;
+ color: white !important;
+ border: 1px solid #138496 !important;
+}
+
+.dropped-event-item .badge.bg-secondary {
+ background: linear-gradient(45deg, #6c757d, #545b62) !important;
+ color: white !important;
+ border: 1px solid #545b62 !important;
+}
+
+.dropped-event-item .badge.bg-purple {
+ background: linear-gradient(45deg, #6f42c1, #59359a) !important;
+ color: white !important;
+ border: 1px solid #59359a !important;
+}
+
+/* Color dot hover effects */
+.dropped-event-item:hover .badge {
+ transform: scale(1.2) !important;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3) !important;
+ border: 3px solid rgba(255,255,255,0.6) !important;
+}
+
+.dropped-event-item .badge:hover {
+ transform: scale(1.4) !important;
+ z-index: 20 !important;
+ box-shadow: 0 6px 20px rgba(0,0,0,0.4) !important;
+ border: 3px solid rgba(255,255,255,0.8) !important;
+}
+
+/* FullCalendar Customizations */
+.fc-toolbar {
+ background: linear-gradient(135deg, rgba(255,255,255,0.95), rgba(248,249,250,0.95)) !important;
+ padding: 25px 20px !important;
+ border-radius: 15px !important;
+ margin-bottom: 15px !important;
+ box-shadow: 0 8px 25px rgba(0,0,0,0.1) !important;
+ backdrop-filter: blur(10px) !important;
+ border: 1px solid rgba(255,255,255,0.2) !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ flex-wrap: wrap !important;
+ gap: 15px !important;
+ min-height: 80px !important;
+ overflow: visible !important;
+}
+
+.fc-toolbar-chunk {
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+}
+
+/* Left chunk - navigation buttons */
+.fc-toolbar-chunk:first-child {
+ justify-content: flex-start !important;
+}
+
+/* Center chunk - title */
+.fc-toolbar-chunk:nth-child(2) {
+ justify-content: center !important;
+ flex-grow: 1 !important;
+}
+
+/* Right chunk - view buttons */
+.fc-toolbar-chunk:last-child {
+ justify-content: flex-end !important;
+}
+
+.fc-toolbar h2 {
+ color: #2c3e50 !important;
+ font-weight: 700 !important;
+ font-size: 1.8rem !important;
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+ margin: 0 !important;
+ text-shadow: none !important;
+}
+
+/* Navigation Buttons (prev, next, today) */
+.fc-button-primary {
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ border: none !important;
+ border-radius: 12px !important;
+ padding: 12px 20px !important;
+ font-weight: 600 !important;
+ font-size: 0.9rem !important;
+ transition: all 0.3s ease !important;
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
+ color: white !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.5px !important;
+ min-width: 80px !important;
+ height: 44px !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ margin: 5px 2px !important;
+ position: relative !important;
+ z-index: 1 !important;
+ overflow: visible !important;
+}
+
+.fc-button-primary:hover {
+ transform: translateY(-2px) !important;
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4) !important;
+ background: linear-gradient(45deg, #764ba2, #667eea) !important;
+ z-index: 10 !important;
+}
+
+.fc-button-primary:active {
+ transform: translateY(0px) !important;
+ box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3) !important;
+}
+
+.fc-button-primary:focus {
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3) !important;
+ outline: none !important;
+}
+
+/* Button Group Container */
+.fc-button-group {
+ display: flex !important;
+ align-items: center !important;
+ border-radius: 12px !important;
+ overflow: visible !important;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
+ border: 1px solid #dee2e6 !important;
+ margin: 5px 0 !important;
+ position: relative !important;
+ z-index: 1 !important;
+}
+
+/* View Buttons (month, week, day) */
+.fc-button-group .fc-button {
+ background: linear-gradient(45deg, #f8f9fa, #e9ecef) !important;
+ border: none !important;
+ border-right: 1px solid #dee2e6 !important;
+ color: #495057 !important;
+ font-weight: 500 !important;
+ transition: all 0.2s ease !important;
+ padding: 12px 16px !important;
+ font-size: 0.85rem !important;
+ height: 44px !important;
+ min-width: 70px !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ border-radius: 0 !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.3px !important;
+}
+
+.fc-button-group .fc-button:last-child {
+ border-right: none !important;
+}
+
+.fc-button-group .fc-button:hover {
+ background: linear-gradient(45deg, #e9ecef, #dee2e6) !important;
+ color: #212529 !important;
+ transform: translateY(-1px) !important;
+ z-index: 2 !important;
+}
+
+.fc-button-group .fc-button-active {
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ color: white !important;
+ border-color: #667eea !important;
+ box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3) !important;
+ z-index: 3 !important;
+}
+
+/* Today Button Special Styling */
+.fc-today-button {
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
+ border: none !important;
+ box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3) !important;
+ height: 44px !important;
+ min-width: 80px !important;
+ padding: 12px 20px !important;
+ border-radius: 12px !important;
+ font-weight: 600 !important;
+ font-size: 0.9rem !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.5px !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ margin: 5px 2px !important;
+ position: relative !important;
+ z-index: 1 !important;
+ overflow: visible !important;
+}
+
+.fc-today-button:hover {
+ background: linear-gradient(45deg, #20c997, #28a745) !important;
+ box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4) !important;
+ transform: translateY(-2px) !important;
+ z-index: 10 !important;
+}
+
+.fc-today-button:focus {
+ outline: none !important;
+ box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.3) !important;
+}
+
+.fc-event {
+ border-radius: 6px !important;
+ border: none !important;
+ padding: 2px 6px !important;
+ font-weight: 500 !important;
+ transition: all 0.2s ease !important;
+}
+
+.fc-event:hover {
+ transform: scale(1.02) !important;
+ box-shadow: 0 3px 10px rgba(0,0,0,0.2) !important;
+}
+
+/* Calendar Background and Grid */
+.fc-view-harness {
+ background: linear-gradient(135deg, rgba(255,255,255,0.98), rgba(248,249,250,0.98)) !important;
+ border-radius: 12px !important;
+ overflow: hidden !important;
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.05) !important;
+}
+
+.fc-scrollgrid {
+ border: none !important;
+ border-radius: 12px !important;
+ overflow: hidden !important;
+}
+
+.fc-col-header {
+ background: linear-gradient(135deg, #667eea, #764ba2) !important;
+ color: white !important;
+ font-weight: 600 !important;
+ text-transform: uppercase !important;
+ letter-spacing: 1px !important;
+ font-size: 0.85rem !important;
+}
+
+.fc-col-header-cell {
+ border: none !important;
+ padding: 12px 8px !important;
+}
+
+.fc-daygrid-day {
+ background: rgba(255,255,255,0.8) !important;
+ border: 1px solid rgba(222,226,230,0.5) !important;
+ transition: all 0.2s ease !important;
+}
+
+.fc-daygrid-day:hover {
+ background: rgba(102, 126, 234, 0.05) !important;
+ border-color: rgba(102, 126, 234, 0.2) !important;
+}
+
+.fc-daygrid-day-number {
+ color: #495057 !important;
+ font-weight: 500 !important;
+ padding: 8px !important;
+ transition: all 0.2s ease !important;
+}
+
+.fc-day-today {
+ background: linear-gradient(135deg, rgba(40, 167, 69, 0.1), rgba(32, 201, 151, 0.1)) !important;
+ border: 2px solid rgba(40, 167, 69, 0.3) !important;
+}
+
+.fc-day-today .fc-daygrid-day-number {
+ background: linear-gradient(45deg, #28a745, #20c997) !important;
+ color: white !important;
+ border-radius: 50% !important;
+ width: 32px !important;
+ height: 32px !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ font-weight: 700 !important;
+ box-shadow: 0 3px 10px rgba(40, 167, 69, 0.3) !important;
+}
+
+.fc-daygrid-day-events {
+ margin: 2px !important;
+}
+
+/* Weekend styling */
+.fc-day-sat,
+.fc-day-sun {
+ background: rgba(248, 249, 250, 0.8) !important;
+}
+
+.fc-day-sat .fc-daygrid-day-number,
+.fc-day-sun .fc-daygrid-day-number {
+ color: #6c757d !important;
+}
+
+/* Checkbox Styling */
+.checkbox {
+ margin: 15px 0 !important;
+}
+
+.checkbox input[type="checkbox"] {
+ transform: scale(1.2) !important;
+ margin-right: 8px !important;
+}
+
+.checkbox label {
+ color: #495057 !important;
+ font-weight: 500 !important;
+ cursor: pointer !important;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .calendar-page-wrapper {
+ padding: 10px !important;
+ }
+
+ .calendar-sidebar {
+ margin-bottom: 20px !important;
+ padding: 15px !important;
+ }
+
+ .calendar-create-btn {
+ width: 100% !important;
+ text-align: center !important;
+ }
+
+ .dropped-events-list {
+ max-height: 250px !important;
+ }
+
+ .dropped-event-item {
+ padding: 12px !important;
+ margin-bottom: 8px !important;
+ }
+
+ .dropped-event-item .row {
+ margin: 0 !important;
+ }
+
+ .dropped-event-item .col-8,
+ .dropped-event-item .col-4 {
+ padding: 0 !important;
+ }
+
+ .dropped-event-item .col-4 {
+ text-align: right !important;
+ display: flex !important;
+ align-items: flex-start !important;
+ justify-content: flex-end !important;
+ }
+
+ .dropped-event-item .badge {
+ width: 18px !important;
+ height: 18px !important;
+ }
+
+ .dropped-events-header {
+ font-size: 0.9rem !important;
+ padding: 10px 12px !important;
+ }
+
+ .calendar-events {
+ padding: 6px 10px !important;
+ font-size: 0.85rem !important;
+ }
+}
+
+@media (max-width: 480px) {
+ .calendar-page-header {
+ padding: 15px !important;
+ }
+
+ .calendar-page-header h3 {
+ font-size: 1.3rem !important;
+ }
+
+ .dropped-events-list {
+ max-height: 200px !important;
+ }
+
+ .dropped-event-item strong {
+ font-size: 0.85rem !important;
+ }
+
+ .dropped-event-item small {
+ font-size: 0.7rem !important;
+ }
+
+ .dropped-event-item .event-icon {
+ font-size: 0.75rem !important;
+ width: 14px !important;
+ height: 14px !important;
+ }
+
+ .dropped-event-item .event-time,
+ .dropped-event-item .event-dropped-time {
+ gap: 4px !important;
+ margin: 2px 0 !important;
+ }
+
+ /* Mobile Calendar Toolbar */
+ .fc-toolbar {
+ padding: 15px !important;
+ flex-direction: column !important;
+ gap: 15px !important;
+ align-items: center !important;
+ }
+
+ .fc-toolbar h2 {
+ font-size: 1.4rem !important;
+ margin: 0 !important;
+ text-align: center !important;
+ }
+
+ .fc-toolbar-chunk {
+ display: flex !important;
+ justify-content: center !important;
+ align-items: center !important;
+ flex-wrap: wrap !important;
+ gap: 10px !important;
+ }
+
+ .fc-button-primary,
+ .fc-today-button {
+ padding: 10px 16px !important;
+ font-size: 0.8rem !important;
+ height: 40px !important;
+ min-width: 70px !important;
+ }
+
+ .fc-button-group {
+ box-shadow: 0 2px 6px rgba(0,0,0,0.1) !important;
+ }
+
+ .fc-button-group .fc-button {
+ padding: 10px 12px !important;
+ font-size: 0.75rem !important;
+ height: 40px !important;
+ min-width: 60px !important;
+ }
+
+ .fc-daygrid-day-number {
+ font-size: 0.8rem !important;
+ padding: 4px !important;
+ }
+
+ .fc-day-today .fc-daygrid-day-number {
+ width: 24px !important;
+ height: 24px !important;
+ font-size: 0.75rem !important;
+ }
+}
+
+/* Dark Mode Support */
+[data-layout-mode="dark_mode"] .calendar-events {
+ background: linear-gradient(45deg, #2c3e50, #34495e) !important;
+ color: #ecf0f1 !important;
+ border-color: #34495e !important;
+}
+
+[data-layout-mode="dark_mode"] .calendar-events:hover {
+ background: linear-gradient(45deg, #34495e, #2c3e50) !important;
+ border-color: #3498db !important;
+}
+
+[data-layout-mode="dark_mode"] .calendar-sidebar {
+ background: rgba(44, 62, 80, 0.95) !important;
+ border-color: rgba(52, 73, 94, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .calendar-main-card {
+ background: rgba(44, 62, 80, 0.95) !important;
+}
+
+[data-layout-mode="dark_mode"] .calendar-page-header {
+ background: rgba(44, 62, 80, 0.95) !important;
+ border-color: rgba(52, 73, 94, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item {
+ background: linear-gradient(45deg, #34495e, #2c3e50) !important;
+ border-color: #34495e !important;
+ color: #ecf0f1 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item:hover {
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+ border-color: #3498db !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item strong {
+ color: #ecf0f1 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .text-muted {
+ color: #bdc3c7 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .text-success {
+ color: #2ecc71 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-events-header {
+ background: linear-gradient(45deg, #2ecc71, #27ae60) !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-events-list::-webkit-scrollbar-track {
+ background: rgba(255,255,255,0.1) !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-events-list::-webkit-scrollbar-thumb {
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+}
+
+/* Dark Mode Calendar Styles */
+[data-layout-mode="dark_mode"] .fc-toolbar {
+ background: linear-gradient(135deg, rgba(44, 62, 80, 0.95), rgba(52, 73, 94, 0.95)) !important;
+ border-color: rgba(52, 73, 94, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-toolbar h2 {
+ color: #ecf0f1 !important;
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-button-primary {
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+ box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-button-primary:hover {
+ background: linear-gradient(45deg, #2980b9, #3498db) !important;
+ box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-today-button {
+ background: linear-gradient(45deg, #2ecc71, #27ae60) !important;
+ box-shadow: 0 4px 15px rgba(46, 204, 113, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-today-button:hover {
+ background: linear-gradient(45deg, #27ae60, #2ecc71) !important;
+ box-shadow: 0 6px 20px rgba(46, 204, 113, 0.4) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-button-group .fc-button {
+ background: linear-gradient(45deg, #34495e, #2c3e50) !important;
+ border-color: #34495e !important;
+ color: #ecf0f1 !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-button-group .fc-button:hover {
+ background: linear-gradient(45deg, #2c3e50, #34495e) !important;
+ color: #ffffff !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-button-group .fc-button-active {
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+ border-color: #3498db !important;
+ box-shadow: 0 3px 10px rgba(52, 152, 219, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-view-harness {
+ background: linear-gradient(135deg, rgba(44, 62, 80, 0.98), rgba(52, 73, 94, 0.98)) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-col-header {
+ background: linear-gradient(135deg, #3498db, #2980b9) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-daygrid-day {
+ background: rgba(44, 62, 80, 0.8) !important;
+ border-color: rgba(52, 73, 94, 0.5) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-daygrid-day:hover {
+ background: rgba(52, 152, 219, 0.1) !important;
+ border-color: rgba(52, 152, 219, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-daygrid-day-number {
+ color: #bdc3c7 !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-day-today {
+ background: linear-gradient(135deg, rgba(46, 204, 113, 0.15), rgba(39, 174, 96, 0.15)) !important;
+ border-color: rgba(46, 204, 113, 0.4) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-day-today .fc-daygrid-day-number {
+ background: linear-gradient(45deg, #2ecc71, #27ae60) !important;
+ box-shadow: 0 3px 10px rgba(46, 204, 113, 0.3) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-day-sat,
+[data-layout-mode="dark_mode"] .fc-day-sun {
+ background: rgba(52, 73, 94, 0.6) !important;
+}
+
+[data-layout-mode="dark_mode"] .fc-day-sat .fc-daygrid-day-number,
+[data-layout-mode="dark_mode"] .fc-day-sun .fc-daygrid-day-number {
+ color: #7f8c8d !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .event-icon {
+ opacity: 0.9 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .event-time,
+[data-layout-mode="dark_mode"] .dropped-event-item .event-dropped-time {
+ color: #bdc3c7 !important;
+}
+
+/* Dark mode badge styles */
+[data-layout-mode="dark_mode"] .dropped-event-item .badge.bg-warning {
+ background: linear-gradient(45deg, #f39c12, #e67e22) !important;
+ color: #2c3e50 !important;
+ border: 1px solid #e67e22 !important;
+ font-weight: 700 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .badge.bg-primary {
+ background: linear-gradient(45deg, #3498db, #2980b9) !important;
+ color: white !important;
+ border: 1px solid #2980b9 !important;
+}
+
+[data-layout-mode="dark_mode"] .dropped-event-item .badge.bg-success {
+ background: linear-gradient(45deg, #2ecc71, #27ae60) !important;
+ color: white !important;
+ border: 1px solid #27ae60 !important;
+}
+
+/* Animation for successful drops */
+@keyframes dropSuccess {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.05); }
+ 100% { transform: scale(1); }
+}
+
+.drop-success {
+ animation: dropSuccess 0.3s ease !important;
+}
+
+/* Icon animations */
+@keyframes iconPulse {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+}
+
+.dropped-event-item:hover .event-icon {
+ animation: iconPulse 0.6s ease-in-out !important;
+}
+
+/* Subtle glow effect for icons */
+.dropped-event-item .event-icon {
+ transition: all 0.2s ease !important;
+ text-shadow: 0 0 4px rgba(0,0,0,0.1) !important;
+}
+
+.dropped-event-item:hover .event-icon {
+ text-shadow: 0 0 8px rgba(102, 126, 234, 0.4) !important;
+ transform: scale(1.1) !important;
+}
+
+/* Color dot pulse animation */
+@keyframes colorDotPulse {
+ 0% {
+ transform: scale(1);
+ box-shadow: 0 3px 10px rgba(0,0,0,0.2);
+ }
+ 50% {
+ transform: scale(1.1);
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
+ }
+ 100% {
+ transform: scale(1);
+ box-shadow: 0 3px 10px rgba(0,0,0,0.2);
+ }
+}
+
+.dropped-event-item .badge {
+ animation: colorDotPulse 2s ease-in-out infinite !important;
+}
+
+.dropped-event-item:hover .badge {
+ animation: none !important;
+}
+
+/* Loading states */
+.calendar-loading {
+ opacity: 0.7 !important;
+ pointer-events: none !important;
+}
+
+.calendar-loading::after {
+ content: '' !important;
+ position: absolute !important;
+ top: 50% !important;
+ left: 50% !important;
+ width: 20px !important;
+ height: 20px !important;
+ margin: -10px 0 0 -10px !important;
+ border: 2px solid #f3f3f3 !important;
+ border-top: 2px solid #667eea !important;
+ border-radius: 50% !important;
+ animation: spin 1s linear infinite !important;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}