diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..291cdb8
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,21 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html, body, #root {
+ height: 100%;
+}
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..1fd12b7
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+
+const root = ReactDOM.createRoot(
+ document.getElementById('root') as HTMLElement
+);
+root.render(
+
+
+
+);
diff --git a/src/pages/Calendar.js b/src/pages/Calendar.js
new file mode 100644
index 0000000..e45d305
--- /dev/null
+++ b/src/pages/Calendar.js
@@ -0,0 +1,367 @@
+import React, { useState, useEffect, useRef, useCallback } from "react";
+import FullCalendar from "@fullcalendar/react";
+import dayGridPlugin from "@fullcalendar/daygrid";
+import timeGridPlugin from "@fullcalendar/timegrid";
+import interactionPlugin from "@fullcalendar/interaction";
+import { Draggable } from "@fullcalendar/interaction";
+import "../styles/fullcalendar.min.css";
+import "../styles/calendar-custom.css";
+import Select from "react-select";
+
+const Calendar = () => {
+ const calendarRef = useRef(null);
+ const initializedRef = useRef(false);
+
+ const [weekendsVisible, setWeekendsVisible] = useState(true);
+ const [isnewevent, setisnewevent] = useState(false);
+ const [iseditdelete, setiseditdelete] = useState(false);
+ const [event_title, setevent_title] = useState("");
+ const [category_color, setcategory_color] = useState("");
+ const [calenderevent, setcalenderevent] = useState(null);
+ const [addneweventobj, setaddneweventobj] = useState(null);
+
+ // Combined events state for calendar display - using current dates
+ const today = new Date();
+ const [calendarEvents, setCalendarEvents] = useState([
+ {
+ id: 'default-1',
+ title: "🎯 Existing Meeting",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 10, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 11, 0),
+ className: "bg-primary",
+ },
+ {
+ id: 'default-2',
+ title: "📈 Weekly Review",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 14, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 15, 30),
+ className: "bg-success",
+ },
+ {
+ id: 'default-3',
+ title: "🚀 Project Launch",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 9, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 17, 0),
+ className: "bg-warning",
+ },
+ {
+ id: 'default-4',
+ title: "🎉 Team Building",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 13, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 18, 0),
+ className: "bg-info",
+ }
+ ]);
+
+ useEffect(() => {
+ 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
+ const draggableEl = document.getElementById("calendar-events");
+ if (draggableEl) {
+ console.log("🚀 Initializing calendar draggable events");
+
+ new Draggable(draggableEl, {
+ itemSelector: ".calendar-events",
+ eventData: function(eventEl) {
+ const title = eventEl.innerText.trim();
+ const className = eventEl.getAttribute("data-class");
+ return {
+ title: title,
+ className: className,
+ duration: "01:00"
+ };
+ },
+ longPressDelay: 0,
+ touchTimeoutDelay: 0
+ });
+
+ initializedRef.current = true;
+ console.log("✅ Calendar initialization completed");
+ }
+ }, []);
+
+ const handleDateSelect = useCallback((selectInfo) => {
+ console.log("📅 Date selected:", selectInfo);
+ setaddneweventobj(selectInfo);
+ setisnewevent(true);
+ }, []);
+
+ const handleEventClick = useCallback((clickInfo) => {
+ console.log("🖱️ Event clicked:", clickInfo.event);
+ setcalenderevent(clickInfo.event);
+ setevent_title(clickInfo.event.title);
+ setiseditdelete(true);
+ }, []);
+
+ const handleEventReceive = useCallback((info) => {
+ console.log("📥 External event dropped:", info);
+
+ const newEvent = {
+ id: `external-${Date.now()}`,
+ title: info.event.title,
+ start: info.event.start,
+ end: info.event.end,
+ className: info.event.classNames[0] || "bg-primary"
+ };
+
+ setCalendarEvents(prev => [...prev, newEvent]);
+ }, []);
+
+ const handleEventDrop = useCallback((info) => {
+ console.log("🔄 Event moved:", info);
+
+ setCalendarEvents(prev =>
+ prev.map(event =>
+ event.id === info.event.id
+ ? { ...event, start: info.event.start, end: info.event.end }
+ : event
+ )
+ );
+ }, []);
+
+ const addnewevent = () => {
+ let calendarApi = addneweventobj.view.calendar;
+
+ calendarApi.unselect();
+
+ if (event_title) {
+ const newEvent = {
+ id: `new-${Date.now()}`,
+ title: event_title,
+ className: category_color,
+ start: addneweventobj.startStr,
+ end: addneweventobj.endStr,
+ allDay: addneweventobj.allDay,
+ };
+
+ calendarApi.addEvent(newEvent);
+ setCalendarEvents(prev => [...prev, newEvent]);
+ }
+ setisnewevent(false);
+ };
+
+ const onupdateModalClose = () => {
+ setiseditdelete(false);
+ setevent_title("");
+ };
+
+ const oncreateeventModalClose = () => {
+ setevent_title("");
+ setisnewevent(false);
+ };
+
+ const removeevent = () => {
+ calenderevent.remove();
+ setCalendarEvents(prev => prev.filter(event => event.id !== calenderevent.id));
+ setiseditdelete(false);
+ };
+
+ const categoryColorOptions = [
+ { value: "bg-danger", label: "🔴 Đỏ", color: "#dc3545" },
+ { value: "bg-success", label: "🟢 Xanh lá", color: "#28a745" },
+ { value: "bg-primary", label: "🔵 Xanh dương", color: "#007bff" },
+ { value: "bg-info", label: "🟦 Xanh nhạt", color: "#17a2b8" },
+ { value: "bg-warning", label: "🟡 Vàng", color: "#ffc107" },
+ { value: "bg-purple", label: "🟣 Tím", color: "#6f42c1" },
+ ];
+
+ return (
+
+
+
+
+
+
📅 Beautiful Calendar
+
+
+
+
+
+
+
+
+
+
🎯 Drag & Drop Events
+
+
+ 👥 Họp
+
+
+ 📞 Gọi điện
+
+
+ 💼 Công việc
+
+
+ 🎯 Mục tiêu
+
+
+ ⚠️ Quan trọng
+
+
+ 🎉 Sự kiện
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Add Event Modal */}
+ {isnewevent && (
+
+
+
+
+
Thêm sự kiện mới
+
+
+
+
+
+
+ setevent_title(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* Edit/Delete Event Modal */}
+ {iseditdelete && (
+
+
+
+
+
Chỉnh sửa sự kiện
+
+
+
+
+
+
+ setevent_title(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default Calendar;
diff --git a/src/pages/Calendar.tsx b/src/pages/Calendar.tsx
new file mode 100644
index 0000000..9418c08
--- /dev/null
+++ b/src/pages/Calendar.tsx
@@ -0,0 +1,367 @@
+import React, { useState, useEffect, useRef, useCallback } from "react";
+import FullCalendar from "@fullcalendar/react";
+import dayGridPlugin from "@fullcalendar/daygrid";
+import timeGridPlugin from "@fullcalendar/timegrid";
+import interactionPlugin from "@fullcalendar/interaction";
+import { Draggable } from "@fullcalendar/interaction";
+import "../styles/fullcalendar.min.css";
+import "../styles/calendar-custom.css";
+import Select from "react-select";
+
+const Calendar = () => {
+ const calendarRef = useRef
(null);
+ const initializedRef = useRef(false);
+
+ const [weekendsVisible, setWeekendsVisible] = useState(true);
+ const [isnewevent, setisnewevent] = useState(false);
+ const [iseditdelete, setiseditdelete] = useState(false);
+ const [event_title, setevent_title] = useState("");
+ const [category_color, setcategory_color] = useState("");
+ const [calenderevent, setcalenderevent] = useState(null);
+ const [addneweventobj, setaddneweventobj] = useState(null);
+
+ // Combined events state for calendar display - using current dates
+ const today = new Date();
+ const [calendarEvents, setCalendarEvents] = useState([
+ {
+ id: 'default-1',
+ title: "🎯 Existing Meeting",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 10, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate(), 11, 0),
+ className: "bg-primary",
+ },
+ {
+ id: 'default-2',
+ title: "📈 Weekly Review",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 14, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1, 15, 30),
+ className: "bg-success",
+ },
+ {
+ id: 'default-3',
+ title: "🚀 Project Launch",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 9, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 17, 0),
+ className: "bg-warning",
+ },
+ {
+ id: 'default-4',
+ title: "🎉 Team Building",
+ start: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 13, 0),
+ end: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 18, 0),
+ className: "bg-info",
+ }
+ ]);
+
+ useEffect(() => {
+ 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
+ const draggableEl = document.getElementById("calendar-events");
+ if (draggableEl) {
+ console.log("🚀 Initializing calendar draggable events");
+
+ new Draggable(draggableEl, {
+ itemSelector: ".calendar-events",
+ eventData: function(eventEl) {
+ const title = eventEl.innerText.trim();
+ const className = eventEl.getAttribute("data-class");
+ return {
+ title: title,
+ className: className,
+ duration: "01:00"
+ };
+ },
+ longPressDelay: 0,
+ touchTimeoutDelay: 0
+ });
+
+ initializedRef.current = true;
+ console.log("✅ Calendar initialization completed");
+ }
+ }, []);
+
+ const handleDateSelect = useCallback((selectInfo: any) => {
+ console.log("📅 Date selected:", selectInfo);
+ setaddneweventobj(selectInfo);
+ setisnewevent(true);
+ }, []);
+
+ const handleEventClick = useCallback((clickInfo: any) => {
+ console.log("🖱️ Event clicked:", clickInfo.event);
+ setcalenderevent(clickInfo.event);
+ setevent_title(clickInfo.event.title);
+ setiseditdelete(true);
+ }, []);
+
+ const handleEventReceive = useCallback((info: any) => {
+ console.log("📥 External event dropped:", info);
+
+ const newEvent = {
+ id: `external-${Date.now()}`,
+ title: info.event.title,
+ start: info.event.start,
+ end: info.event.end,
+ className: info.event.classNames[0] || "bg-primary"
+ };
+
+ setCalendarEvents(prev => [...prev, newEvent]);
+ }, []);
+
+ const handleEventDrop = useCallback((info: any) => {
+ console.log("🔄 Event moved:", info);
+
+ setCalendarEvents(prev =>
+ prev.map(event =>
+ event.id === info.event.id
+ ? { ...event, start: info.event.start, end: info.event.end }
+ : event
+ )
+ );
+ }, []);
+
+ const addnewevent = () => {
+ let calendarApi = addneweventobj.view.calendar;
+
+ calendarApi.unselect();
+
+ if (event_title) {
+ const newEvent = {
+ id: `new-${Date.now()}`,
+ title: event_title,
+ className: category_color,
+ start: addneweventobj.startStr,
+ end: addneweventobj.endStr,
+ allDay: addneweventobj.allDay,
+ };
+
+ calendarApi.addEvent(newEvent);
+ setCalendarEvents(prev => [...prev, newEvent]);
+ }
+ setisnewevent(false);
+ };
+
+ const onupdateModalClose = () => {
+ setiseditdelete(false);
+ setevent_title("");
+ };
+
+ const oncreateeventModalClose = () => {
+ setevent_title("");
+ setisnewevent(false);
+ };
+
+ const removeevent = () => {
+ calenderevent.remove();
+ setCalendarEvents(prev => prev.filter(event => event.id !== calenderevent.id));
+ setiseditdelete(false);
+ };
+
+ const categoryColorOptions = [
+ { value: "bg-danger", label: "🔴 Đỏ", color: "#dc3545" },
+ { value: "bg-success", label: "🟢 Xanh lá", color: "#28a745" },
+ { value: "bg-primary", label: "🔵 Xanh dương", color: "#007bff" },
+ { value: "bg-info", label: "🟦 Xanh nhạt", color: "#17a2b8" },
+ { value: "bg-warning", label: "🟡 Vàng", color: "#ffc107" },
+ { value: "bg-purple", label: "🟣 Tím", color: "#6f42c1" },
+ ];
+
+ return (
+
+
+
+
+
+
📅 Beautiful Calendar
+
+
+
+
+
+
+
+
+
+
🎯 Drag & Drop Events
+
+
+ 👥 Họp
+
+
+ 📞 Gọi điện
+
+
+ 💼 Công việc
+
+
+ 🎯 Mục tiêu
+
+
+ ⚠️ Quan trọng
+
+
+ 🎉 Sự kiện
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Add Event Modal */}
+ {isnewevent && (
+
+
+
+
+
Thêm sự kiện mới
+
+
+
+
+
+
+ setevent_title(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* Edit/Delete Event Modal */}
+ {iseditdelete && (
+
+
+
+
+
Chỉnh sửa sự kiện
+
+
+
+
+
+
+ setevent_title(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default Calendar;
diff --git a/src/pages/ProjectTracker.js b/src/pages/ProjectTracker.js
new file mode 100644
index 0000000..e4880ab
--- /dev/null
+++ b/src/pages/ProjectTracker.js
@@ -0,0 +1,418 @@
+import React, { useState, useEffect } from "react";
+import { Eye, Edit, Trash2, Plus, Filter, Search } from "feather-icons-react";
+import Select from "react-select";
+
+const ProjectTracker = () => {
+ const [projects, setProjects] = useState([
+ {
+ id: 1,
+ name: "🚀 Website Redesign",
+ category: "Web Development",
+ startDate: "2024-01-15",
+ endDate: "2024-03-15",
+ progress: 75,
+ status: "In Progress",
+ budget: 50000000,
+ team: ["John Doe", "Jane Smith", "Mike Johnson"],
+ description: "Complete redesign of company website with modern UI/UX"
+ },
+ {
+ id: 2,
+ name: "📱 Mobile App Development",
+ category: "Mobile Development",
+ startDate: "2024-02-01",
+ endDate: "2024-06-01",
+ progress: 45,
+ status: "In Progress",
+ budget: 120000000,
+ team: ["Sarah Wilson", "David Brown", "Lisa Chen"],
+ description: "Native mobile application for iOS and Android platforms"
+ },
+ {
+ id: 3,
+ name: "🔒 Security Audit",
+ category: "Security",
+ startDate: "2024-01-01",
+ endDate: "2024-02-01",
+ progress: 100,
+ status: "Completed",
+ budget: 25000000,
+ team: ["Alex Turner", "Emma Davis"],
+ description: "Comprehensive security audit and vulnerability assessment"
+ },
+ {
+ id: 4,
+ name: "☁️ Cloud Migration",
+ category: "Infrastructure",
+ startDate: "2024-03-01",
+ endDate: "2024-05-01",
+ progress: 20,
+ status: "Planning",
+ budget: 80000000,
+ team: ["Tom Wilson", "Rachel Green", "Chris Lee"],
+ description: "Migration of legacy systems to cloud infrastructure"
+ },
+ {
+ id: 5,
+ name: "📊 Data Analytics Platform",
+ category: "Data Science",
+ startDate: "2024-02-15",
+ endDate: "2024-07-15",
+ progress: 60,
+ status: "In Progress",
+ budget: 95000000,
+ team: ["Kevin Zhang", "Maria Garcia", "James Park"],
+ description: "Advanced analytics platform for business intelligence"
+ }
+ ]);
+
+ const [filteredProjects, setFilteredProjects] = useState(projects);
+ const [searchTerm, setSearchTerm] = useState("");
+ const [statusFilter, setStatusFilter] = useState("");
+ const [categoryFilter, setCategoryFilter] = useState("");
+ const [showModal, setShowModal] = useState(false);
+ const [selectedProject, setSelectedProject] = useState(null);
+ const [isEditing, setIsEditing] = useState(false);
+
+ useEffect(() => {
+ let filtered = projects;
+
+ if (searchTerm) {
+ filtered = filtered.filter(project =>
+ project.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ project.description.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ }
+
+ if (statusFilter) {
+ filtered = filtered.filter(project => project.status === statusFilter);
+ }
+
+ if (categoryFilter) {
+ filtered = filtered.filter(project => project.category === categoryFilter);
+ }
+
+ setFilteredProjects(filtered);
+ }, [projects, searchTerm, statusFilter, categoryFilter]);
+
+ const getStatusBadge = (status) => {
+ const statusConfig = {
+ "Completed": { bg: "#e8f5e8", color: "#28a745", border: "#c3e6cb" },
+ "In Progress": { bg: "#e3f2fd", color: "#1565c0", border: "#bbdefb" },
+ "Planning": { bg: "#fff3cd", color: "#856404", border: "#ffeaa7" },
+ "On Hold": { bg: "#f8d7da", color: "#721c24", border: "#f5c6cb" }
+ };
+
+ const config = statusConfig[status] || statusConfig["Planning"];
+
+ return (
+
+ {status}
+
+ );
+ };
+
+ const getProgressBar = (progress) => {
+ let progressColor = "#28a745";
+ if (progress < 30) progressColor = "#dc3545";
+ else if (progress < 70) progressColor = "#ffc107";
+
+ return (
+
+ );
+ };
+
+ const formatCurrency = (amount) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND'
+ }).format(amount);
+ };
+
+ const statusOptions = [
+ { value: "", label: "Tất cả trạng thái" },
+ { value: "Planning", label: "📋 Planning" },
+ { value: "In Progress", label: "🔄 In Progress" },
+ { value: "Completed", label: "✅ Completed" },
+ { value: "On Hold", label: "⏸️ On Hold" }
+ ];
+
+ const categoryOptions = [
+ { value: "", label: "Tất cả danh mục" },
+ { value: "Web Development", label: "🌐 Web Development" },
+ { value: "Mobile Development", label: "📱 Mobile Development" },
+ { value: "Security", label: "🔒 Security" },
+ { value: "Infrastructure", label: "☁️ Infrastructure" },
+ { value: "Data Science", label: "📊 Data Science" }
+ ];
+
+ const handleViewProject = (project) => {
+ setSelectedProject(project);
+ setIsEditing(false);
+ setShowModal(true);
+ };
+
+ const handleEditProject = (project) => {
+ setSelectedProject(project);
+ setIsEditing(true);
+ setShowModal(true);
+ };
+
+ const handleDeleteProject = (projectId) => {
+ if (window.confirm("Bạn có chắc chắn muốn xóa dự án này?")) {
+ setProjects(projects.filter(p => p.id !== projectId));
+ }
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ setSelectedProject(null);
+ setIsEditing(false);
+ };
+
+ return (
+
+
+
+
+
+
🎯 Project Tracker
+
Quản lý và theo dõi tiến độ dự án
+
+
+
+
+
+ {/* Filters */}
+
+
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Projects Table */}
+
+
+
+
+
+
+ | Dự án |
+ Danh mục |
+ Ngày bắt đầu |
+ Deadline |
+ Tiến độ |
+ Trạng thái |
+ Ngân sách |
+ Thao tác |
+
+
+
+ {filteredProjects.map((project) => (
+
+ |
+
+ {project.name}
+
+ Team: {project.team.length} thành viên
+
+
+ |
+
+
+ {project.category}
+
+ |
+ {new Date(project.startDate).toLocaleDateString('vi-VN')} |
+ {new Date(project.endDate).toLocaleDateString('vi-VN')} |
+
+ {getProgressBar(project.progress)}
+ |
+ {getStatusBadge(project.status)} |
+
+
+ {formatCurrency(project.budget)}
+
+ |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Project Details Modal */}
+ {showModal && selectedProject && (
+
+
+
+
+
+ {isEditing ? "Chỉnh sửa dự án" : "Chi tiết dự án"}
+
+
+
+
+
+
+
Tên dự án
+
{selectedProject.name}
+
+
+
Danh mục
+
{selectedProject.category}
+
+
+
Ngày bắt đầu
+
{new Date(selectedProject.startDate).toLocaleDateString('vi-VN')}
+
+
+
Deadline
+
{new Date(selectedProject.endDate).toLocaleDateString('vi-VN')}
+
+
+
Tiến độ
+ {getProgressBar(selectedProject.progress)}
+
+
+
Trạng thái
+ {getStatusBadge(selectedProject.status)}
+
+
+
Ngân sách
+
+ {formatCurrency(selectedProject.budget)}
+
+
+
+
Thành viên team
+
{selectedProject.team.join(", ")}
+
+
+
Mô tả
+
{selectedProject.description}
+
+
+
+
+
+ {isEditing && (
+
+ )}
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default ProjectTracker;
diff --git a/src/pages/ProjectTracker.tsx b/src/pages/ProjectTracker.tsx
new file mode 100644
index 0000000..12f24be
--- /dev/null
+++ b/src/pages/ProjectTracker.tsx
@@ -0,0 +1,431 @@
+import React, { useState, useEffect } from "react";
+import { Eye, Edit, Trash2, Plus, Filter, Search } from "feather-icons-react";
+import Select from "react-select";
+
+interface Project {
+ id: number;
+ name: string;
+ category: string;
+ startDate: string;
+ endDate: string;
+ progress: number;
+ status: string;
+ budget: number;
+ team: string[];
+ description: string;
+}
+
+const ProjectTracker: React.FC = () => {
+ const [projects, setProjects] = useState([
+ {
+ id: 1,
+ name: "🚀 Website Redesign",
+ category: "Web Development",
+ startDate: "2024-01-15",
+ endDate: "2024-03-15",
+ progress: 75,
+ status: "In Progress",
+ budget: 50000000,
+ team: ["John Doe", "Jane Smith", "Mike Johnson"],
+ description: "Complete redesign of company website with modern UI/UX"
+ },
+ {
+ id: 2,
+ name: "📱 Mobile App Development",
+ category: "Mobile Development",
+ startDate: "2024-02-01",
+ endDate: "2024-06-01",
+ progress: 45,
+ status: "In Progress",
+ budget: 120000000,
+ team: ["Sarah Wilson", "David Brown", "Lisa Chen"],
+ description: "Native mobile application for iOS and Android platforms"
+ },
+ {
+ id: 3,
+ name: "🔒 Security Audit",
+ category: "Security",
+ startDate: "2024-01-01",
+ endDate: "2024-02-01",
+ progress: 100,
+ status: "Completed",
+ budget: 25000000,
+ team: ["Alex Turner", "Emma Davis"],
+ description: "Comprehensive security audit and vulnerability assessment"
+ },
+ {
+ id: 4,
+ name: "☁️ Cloud Migration",
+ category: "Infrastructure",
+ startDate: "2024-03-01",
+ endDate: "2024-05-01",
+ progress: 20,
+ status: "Planning",
+ budget: 80000000,
+ team: ["Tom Wilson", "Rachel Green", "Chris Lee"],
+ description: "Migration of legacy systems to cloud infrastructure"
+ },
+ {
+ id: 5,
+ name: "📊 Data Analytics Platform",
+ category: "Data Science",
+ startDate: "2024-02-15",
+ endDate: "2024-07-15",
+ progress: 60,
+ status: "In Progress",
+ budget: 95000000,
+ team: ["Kevin Zhang", "Maria Garcia", "James Park"],
+ description: "Advanced analytics platform for business intelligence"
+ }
+ ]);
+
+ const [filteredProjects, setFilteredProjects] = useState(projects);
+ const [searchTerm, setSearchTerm] = useState("");
+ const [statusFilter, setStatusFilter] = useState("");
+ const [categoryFilter, setCategoryFilter] = useState("");
+ const [showModal, setShowModal] = useState(false);
+ const [selectedProject, setSelectedProject] = useState(null);
+ const [isEditing, setIsEditing] = useState(false);
+
+ useEffect(() => {
+ let filtered = projects;
+
+ if (searchTerm) {
+ filtered = filtered.filter(project =>
+ project.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ project.description.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ }
+
+ if (statusFilter) {
+ filtered = filtered.filter(project => project.status === statusFilter);
+ }
+
+ if (categoryFilter) {
+ filtered = filtered.filter(project => project.category === categoryFilter);
+ }
+
+ setFilteredProjects(filtered);
+ }, [projects, searchTerm, statusFilter, categoryFilter]);
+
+ const getStatusBadge = (status: string) => {
+ const statusConfig = {
+ "Completed": { bg: "#e8f5e8", color: "#28a745", border: "#c3e6cb" },
+ "In Progress": { bg: "#e3f2fd", color: "#1565c0", border: "#bbdefb" },
+ "Planning": { bg: "#fff3cd", color: "#856404", border: "#ffeaa7" },
+ "On Hold": { bg: "#f8d7da", color: "#721c24", border: "#f5c6cb" }
+ };
+
+ const config = statusConfig[status as keyof typeof statusConfig] || statusConfig["Planning"];
+
+ return (
+
+ {status}
+
+ );
+ };
+
+ const getProgressBar = (progress: number) => {
+ let progressColor = "#28a745";
+ if (progress < 30) progressColor = "#dc3545";
+ else if (progress < 70) progressColor = "#ffc107";
+
+ return (
+
+ );
+ };
+
+ const formatCurrency = (amount: number) => {
+ return new Intl.NumberFormat('vi-VN', {
+ style: 'currency',
+ currency: 'VND'
+ }).format(amount);
+ };
+
+ const statusOptions = [
+ { value: "", label: "Tất cả trạng thái" },
+ { value: "Planning", label: "📋 Planning" },
+ { value: "In Progress", label: "🔄 In Progress" },
+ { value: "Completed", label: "✅ Completed" },
+ { value: "On Hold", label: "⏸️ On Hold" }
+ ];
+
+ const categoryOptions = [
+ { value: "", label: "Tất cả danh mục" },
+ { value: "Web Development", label: "🌐 Web Development" },
+ { value: "Mobile Development", label: "📱 Mobile Development" },
+ { value: "Security", label: "🔒 Security" },
+ { value: "Infrastructure", label: "☁️ Infrastructure" },
+ { value: "Data Science", label: "📊 Data Science" }
+ ];
+
+ const handleViewProject = (project: Project) => {
+ setSelectedProject(project);
+ setIsEditing(false);
+ setShowModal(true);
+ };
+
+ const handleEditProject = (project: Project) => {
+ setSelectedProject(project);
+ setIsEditing(true);
+ setShowModal(true);
+ };
+
+ const handleDeleteProject = (projectId: number) => {
+ if (window.confirm("Bạn có chắc chắn muốn xóa dự án này?")) {
+ setProjects(projects.filter(p => p.id !== projectId));
+ }
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ setSelectedProject(null);
+ setIsEditing(false);
+ };
+
+ return (
+
+
+
+
+
+
🎯 Project Tracker
+
Quản lý và theo dõi tiến độ dự án
+
+
+
+
+
+ {/* Filters */}
+
+
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Projects Table */}
+
+
+
+
+
+
+ | Dự án |
+ Danh mục |
+ Ngày bắt đầu |
+ Deadline |
+ Tiến độ |
+ Trạng thái |
+ Ngân sách |
+ Thao tác |
+
+
+
+ {filteredProjects.map((project) => (
+
+ |
+
+ {project.name}
+
+ Team: {project.team.length} thành viên
+
+
+ |
+
+
+ {project.category}
+
+ |
+ {new Date(project.startDate).toLocaleDateString('vi-VN')} |
+ {new Date(project.endDate).toLocaleDateString('vi-VN')} |
+
+ {getProgressBar(project.progress)}
+ |
+ {getStatusBadge(project.status)} |
+
+
+ {formatCurrency(project.budget)}
+
+ |
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ {/* Project Details Modal */}
+ {showModal && selectedProject && (
+
+
+
+
+
+ {isEditing ? "Chỉnh sửa dự án" : "Chi tiết dự án"}
+
+
+
+
+
+
+
Tên dự án
+
{selectedProject.name}
+
+
+
Danh mục
+
{selectedProject.category}
+
+
+
Ngày bắt đầu
+
{new Date(selectedProject.startDate).toLocaleDateString('vi-VN')}
+
+
+
Deadline
+
{new Date(selectedProject.endDate).toLocaleDateString('vi-VN')}
+
+
+
Tiến độ
+ {getProgressBar(selectedProject.progress)}
+
+
+
Trạng thái
+ {getStatusBadge(selectedProject.status)}
+
+
+
Ngân sách
+
+ {formatCurrency(selectedProject.budget)}
+
+
+
+
Thành viên team
+
{selectedProject.team.join(", ")}
+
+
+
Mô tả
+
{selectedProject.description}
+
+
+
+
+
+ {isEditing && (
+
+ )}
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default ProjectTracker;
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/services/weddingGuestService.js b/src/services/weddingGuestService.js
index 02dd4da..6f59072 100644
--- a/src/services/weddingGuestService.js
+++ b/src/services/weddingGuestService.js
@@ -132,14 +132,38 @@ export const weddingGuestService = {
// Update wedding guest
async updateWeddingGuest(id, guestData) {
try {
- const response = await apiClient.put(`/WeddingGuests/${id}`, guestData);
+ console.log('🔍 API Call - Update Wedding Guest:', {
+ baseURL: API_BASE_URL,
+ endpoint: `/WeddingGuests/${id}`,
+ method: 'POST',
+ data: guestData
+ });
+
+ const response = await apiClient.post(`/WeddingGuests/${id}`, guestData);
+
+ console.log('✅ API Response - Update Wedding Guest:', {
+ status: response.status,
+ data: response.data
+ });
+
return {
success: true,
data: response.data.data || response.data,
message: response.data.message || 'Wedding guest updated successfully'
};
} catch (error) {
- console.error('Error updating wedding guest:', error);
+ console.error('❌ Error updating wedding guest:', {
+ message: error.message,
+ status: error.response?.status,
+ statusText: error.response?.statusText,
+ data: error.response?.data,
+ config: {
+ url: error.config?.url,
+ method: error.config?.method,
+ baseURL: error.config?.baseURL
+ }
+ });
+
return {
success: false,
data: null,
@@ -170,7 +194,7 @@ export const weddingGuestService = {
// Update wedding guest status only
async updateWeddingGuestStatus(id, status) {
try {
- const response = await apiClient.put(`/WeddingGuests/${id}/status`, { status });
+ const response = await apiClient.post(`/WeddingGuests/${id}/status`, { status });
return {
success: true,
data: response.data.data || response.data,
diff --git a/src/styles/calendar-custom.css b/src/styles/calendar-custom.css
new file mode 100644
index 0000000..a215db0
--- /dev/null
+++ b/src/styles/calendar-custom.css
@@ -0,0 +1,312 @@
+/* Calendar Page Wrapper */
+.calendar-page-wrapper {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 20px;
+}
+
+.calendar-page-header {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ border-radius: 15px;
+ padding: 20px;
+ margin-bottom: 20px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.page-title {
+ color: #2c3e50;
+ font-weight: 700;
+ margin: 0;
+ font-size: 2rem;
+}
+
+/* Calendar Create Button */
+.calendar-create-btn {
+ background: linear-gradient(45deg, #667eea, #764ba2);
+ color: white;
+ border: none;
+ padding: 12px 24px;
+ border-radius: 25px;
+ font-weight: 600;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+}
+
+.calendar-create-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
+}
+
+/* Calendar Sidebar */
+.calendar-sidebar {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ border-radius: 15px;
+ padding: 20px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ height: fit-content;
+}
+
+.calendar-sidebar .card-title {
+ color: #2c3e50;
+ font-weight: 600;
+ margin-bottom: 20px;
+ font-size: 1.2rem;
+}
+
+/* Draggable Events */
+.calendar-events {
+ background: linear-gradient(45deg, #f8f9fa, #e9ecef);
+ border: 2px dashed #dee2e6;
+ border-radius: 10px;
+ padding: 12px 16px;
+ margin-bottom: 10px;
+ cursor: move;
+ transition: all 0.3s ease;
+ font-weight: 500;
+ color: #495057;
+}
+
+.calendar-events:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+ border-color: #007bff;
+}
+
+.calendar-events[data-class="bg-danger"] {
+ background: linear-gradient(45deg, #ffebee, #ffcdd2);
+ border-color: #f44336;
+ color: #c62828;
+}
+
+.calendar-events[data-class="bg-success"] {
+ background: linear-gradient(45deg, #e8f5e8, #c8e6c9);
+ border-color: #4caf50;
+ color: #2e7d32;
+}
+
+.calendar-events[data-class="bg-primary"] {
+ background: linear-gradient(45deg, #e3f2fd, #bbdefb);
+ border-color: #2196f3;
+ color: #1565c0;
+}
+
+.calendar-events[data-class="bg-info"] {
+ background: linear-gradient(45deg, #e0f2f1, #b2dfdb);
+ border-color: #00bcd4;
+ color: #00695c;
+}
+
+.calendar-events[data-class="bg-warning"] {
+ background: linear-gradient(45deg, #fff8e1, #ffecb3);
+ border-color: #ff9800;
+ color: #ef6c00;
+}
+
+.calendar-events[data-class="bg-purple"] {
+ background: linear-gradient(45deg, #f3e5f5, #e1bee7);
+ border-color: #9c27b0;
+ color: #7b1fa2;
+}
+
+/* Calendar Options */
+.calendar-options {
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px solid #dee2e6;
+}
+
+.calendar-options label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 500;
+ color: #495057;
+ cursor: pointer;
+}
+
+.calendar-options input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ accent-color: #667eea;
+}
+
+/* Main Calendar Card */
+.calendar-main-card {
+ background: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(10px);
+ border-radius: 15px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ overflow: hidden;
+}
+
+.calendar-main-card .card-body {
+ padding: 20px;
+}
+
+/* FullCalendar Customizations */
+.fc {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.fc-header-toolbar {
+ margin-bottom: 20px !important;
+ padding: 15px;
+ background: linear-gradient(45deg, #f8f9fa, #e9ecef);
+ border-radius: 10px;
+}
+
+.fc-button-primary {
+ background: linear-gradient(45deg, #667eea, #764ba2) !important;
+ border: none !important;
+ border-radius: 8px !important;
+ padding: 8px 16px !important;
+ font-weight: 600 !important;
+ transition: all 0.3s ease !important;
+}
+
+.fc-button-primary:hover {
+ transform: translateY(-1px) !important;
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
+}
+
+.fc-button-primary:not(:disabled):active,
+.fc-button-primary:not(:disabled).fc-button-active {
+ background: linear-gradient(45deg, #5a6fd8, #6a42a0) !important;
+}
+
+.fc-daygrid-day {
+ transition: all 0.2s ease;
+}
+
+.fc-daygrid-day:hover {
+ background-color: rgba(102, 126, 234, 0.05) !important;
+}
+
+.fc-day-today {
+ background: linear-gradient(45deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1)) !important;
+}
+
+.fc-event {
+ border-radius: 8px !important;
+ border: none !important;
+ padding: 2px 6px !important;
+ font-weight: 500 !important;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
+ transition: all 0.2s ease !important;
+}
+
+.fc-event:hover {
+ transform: translateY(-1px) !important;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2) !important;
+}
+
+.fc-event.bg-danger {
+ background: linear-gradient(45deg, #f44336, #e53935) !important;
+}
+
+.fc-event.bg-success {
+ background: linear-gradient(45deg, #4caf50, #43a047) !important;
+}
+
+.fc-event.bg-primary {
+ background: linear-gradient(45deg, #2196f3, #1e88e5) !important;
+}
+
+.fc-event.bg-info {
+ background: linear-gradient(45deg, #00bcd4, #00acc1) !important;
+}
+
+.fc-event.bg-warning {
+ background: linear-gradient(45deg, #ff9800, #fb8c00) !important;
+}
+
+.fc-event.bg-purple {
+ background: linear-gradient(45deg, #9c27b0, #8e24aa) !important;
+}
+
+/* Modal Customizations */
+.modal-content {
+ border-radius: 15px;
+ border: none;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+}
+
+.modal-header {
+ background: linear-gradient(45deg, #667eea, #764ba2);
+ color: white;
+ border-radius: 15px 15px 0 0;
+ border-bottom: none;
+}
+
+.modal-title {
+ font-weight: 600;
+}
+
+.btn-close {
+ filter: invert(1);
+}
+
+.modal-body {
+ padding: 30px;
+}
+
+.modal-footer {
+ border-top: 1px solid #dee2e6;
+ padding: 20px 30px;
+}
+
+/* Form Controls */
+.form-control {
+ border-radius: 10px;
+ border: 2px solid #e9ecef;
+ padding: 12px 16px;
+ transition: all 0.3s ease;
+}
+
+.form-control:focus {
+ border-color: #667eea;
+ box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .calendar-page-wrapper {
+ padding: 10px;
+ }
+
+ .calendar-sidebar {
+ margin-bottom: 20px;
+ }
+
+ .fc-header-toolbar {
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .fc-toolbar-chunk {
+ display: flex;
+ justify-content: center;
+ }
+}
+
+/* Animation for dragging */
+.fc-event-dragging {
+ opacity: 0.7;
+ transform: rotate(5deg);
+}
+
+/* Loading animation */
+@keyframes pulse {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ 100% { opacity: 1; }
+}
+
+.fc-loading {
+ animation: pulse 1.5s ease-in-out infinite;
+}
diff --git a/src/styles/fullcalendar.min.css b/src/styles/fullcalendar.min.css
new file mode 100644
index 0000000..535a91b
--- /dev/null
+++ b/src/styles/fullcalendar.min.css
@@ -0,0 +1,206 @@
+/* Basic FullCalendar CSS */
+.fc {
+ direction: ltr;
+ text-align: left;
+}
+
+.fc table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+.fc th {
+ text-align: center;
+}
+
+.fc th,
+.fc td {
+ vertical-align: top;
+ padding: 0;
+}
+
+.fc-header-toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.fc-toolbar-chunk {
+ display: flex;
+ align-items: center;
+}
+
+.fc-button-group {
+ position: relative;
+ display: inline-flex;
+ vertical-align: middle;
+}
+
+.fc-button {
+ position: relative;
+ display: inline-block;
+ padding: 0.4em 0.65em;
+ margin-bottom: 0;
+ font-size: 1em;
+ line-height: 1.42857143;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ cursor: pointer;
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 0.25em;
+}
+
+.fc-button:not(:disabled) {
+ cursor: pointer;
+}
+
+.fc-button:not(:disabled):active,
+.fc-button:not(:disabled).fc-button-active {
+ background-color: #007bff;
+ border-color: #007bff;
+ color: #fff;
+}
+
+.fc-daygrid {
+ position: relative;
+}
+
+.fc-daygrid-body {
+ position: relative;
+ width: 100%;
+}
+
+.fc-daygrid-day {
+ position: relative;
+}
+
+.fc-daygrid-day-number {
+ position: relative;
+ z-index: 4;
+ padding: 4px;
+}
+
+.fc-event {
+ position: relative;
+ display: block;
+ font-size: 0.85em;
+ line-height: 1.3;
+ border-radius: 3px;
+ border: 1px solid #3788d8;
+ background-color: #3788d8;
+ font-weight: normal;
+ color: #fff;
+}
+
+.fc-event-harness {
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+}
+
+.fc-event-title {
+ padding: 0 1px;
+}
+
+.fc-day-today {
+ background-color: rgba(255, 220, 40, 0.15);
+}
+
+.fc-timegrid {
+ position: relative;
+ z-index: 1;
+}
+
+.fc-timegrid-body {
+ position: relative;
+ z-index: 1;
+ min-height: 100%;
+}
+
+.fc-timegrid-slot {
+ height: 1.5em;
+ border-bottom: 1px solid #ddd;
+}
+
+.fc-timegrid-slot-label {
+ vertical-align: middle;
+ padding: 0 4px;
+}
+
+.fc-scrollgrid {
+ position: relative;
+ border: 1px solid #ddd;
+}
+
+.fc-scrollgrid table {
+ width: 100%;
+}
+
+.fc-col-header-cell {
+ background-color: #eee;
+}
+
+.fc-daygrid-day-frame {
+ position: relative;
+ min-height: 100%;
+}
+
+.fc-daygrid-day-top {
+ position: relative;
+ z-index: 2;
+}
+
+.fc-daygrid-day-events {
+ margin-top: 1px;
+}
+
+.fc-daygrid-event {
+ position: relative;
+ white-space: nowrap;
+ border-radius: 3px;
+ font-size: 0.85em;
+ margin-bottom: 1px;
+}
+
+.fc-daygrid-block-event .fc-event-time {
+ font-weight: bold;
+}
+
+.fc-daygrid-block-event .fc-event-title {
+ margin-top: 1px;
+}
+
+.fc-direction-ltr .fc-daygrid-event.fc-event-start,
+.fc-direction-rtl .fc-daygrid-event.fc-event-end {
+ margin-left: 2px;
+}
+
+.fc-direction-ltr .fc-daygrid-event.fc-event-end,
+.fc-direction-rtl .fc-daygrid-event.fc-event-start {
+ margin-right: 2px;
+}
+
+.fc-daygrid-week-number {
+ position: absolute;
+ z-index: 5;
+ top: 0;
+ padding: 2px;
+ min-width: 1.5em;
+ text-align: center;
+ background-color: rgba(208, 208, 208, 0.3);
+ color: #808080;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .fc-header-toolbar {
+ flex-direction: column;
+ }
+
+ .fc-toolbar-chunk {
+ margin: 2px 0;
+ }
+}