diff --git a/api.ts b/api.ts index ee28e72..1b076e3 100644 --- a/api.ts +++ b/api.ts @@ -64,4 +64,7 @@ export const API = { UPDATE_STATUS: (id: number | string) => `${API_URL_ROOT}/submissions/${id}/status`, DELETE: (id: number | string) => `${API_URL_ROOT}/submissions/${id}`, }, + Chatbot:{ + CHAT: `${API_URL_ROOT}/chatbot/chat`, + } } diff --git a/public/images/map.jpeg b/public/images/map.jpeg new file mode 100644 index 0000000..d28f25a Binary files /dev/null and b/public/images/map.jpeg differ diff --git a/public/images/teamdev/ddk2.jpeg b/public/images/teamdev/ddk2.jpeg new file mode 100644 index 0000000..6141e75 Binary files /dev/null and b/public/images/teamdev/ddk2.jpeg differ diff --git a/public/images/teamdev/ncda.jpeg b/public/images/teamdev/ncda.jpeg new file mode 100644 index 0000000..327ee97 Binary files /dev/null and b/public/images/teamdev/ncda.jpeg differ diff --git a/public/images/teamdev/tad.jpeg b/public/images/teamdev/tad.jpeg new file mode 100644 index 0000000..ee2940d Binary files /dev/null and b/public/images/teamdev/tad.jpeg differ diff --git a/src/app/user/about-us/page.tsx b/src/app/user/about-us/page.tsx new file mode 100644 index 0000000..287e74e --- /dev/null +++ b/src/app/user/about-us/page.tsx @@ -0,0 +1,300 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; + +export default function LandingPage() { + const features = [ + { + title: "Bản đồ dòng thời gian", + desc: "Giao diện bản đồ tự động thay đổi biên giới, địa danh và sự kiện tương ứng với mốc thời gian được lựa chọn.", + icon: "🗺️", + }, + { + title: "Tương tác thực tế", + desc: "Hiển thị chi tiết bối cảnh, nhân vật và số liệu khi người dùng thao tác vào các điểm neo sự kiện trên bản đồ.", + icon: "📍", + }, + { + title: "Trợ lý ảo & Công cụ học", + desc: "Tích hợp AI giải đáp thắc mắc lịch sử, kết hợp hệ thống giao bài tập và làm Quiz trực tuyến cho học đường.", + icon: "🤖", + }, + ]; + + const team = [ + { + name: "Trần Anh Đức", + role: "Project Manager", + desc: "Đẹp trai cao m8", + avatar: "/images/teamdev/tad.jpeg", + }, + { + name: "Đỗ Duy Khánh", + role: "Backend Developer", + desc: "Kì nhân dị sỹ", + avatar: "/images/teamdev/ddk2.jpeg", + }, + { + name: "Ngô Cung Đức Anh", + role: "Frontend Developer", + desc: "Cũng đẹp trai nhưng cao m7 thôi", + avatar: "/images/teamdev/ncda.jpeg", + }, + ]; + + return ( + // Sử dụng tông màu Vàng cổ (Parchment) và Xanh rêu (Dark Slate Green) +
+ {/* --- BACKGROUND IMAGE --- */} +
+ World Map Background +
+ {/* Lớp overlay mờ để làm dịu background */} +
+ + {/* --- HEADER NAVBAR --- */} +
+
+ GeoHistory +
+ +
+ +
+ {/* --- PHẦN 1: GIỚI THIỆU TỔNG QUAN --- */} +
+

+ Bách khoa toàn thư
+ + Bản đồ số Lịch sử + +

+
+

+ Hệ thống thông tin địa lý (GIS) tiên phong trong việc trực quan + hóa dữ liệu lịch sử. Nền tảng của chúng tôi cho phép hiển thị động + các thông tin như biên giới quốc gia, diễn biến trận chiến và sự + kiện theo đúng tiến trình thời gian. +

+

+ Đây là không gian tập trung tri thức được tinh lọc, nơi các chuyên + gia, nhà sử học và giáo viên đóng góp dữ liệu tọa độ, vector, được + hệ thống kiểm duyệt chặt chẽ trước khi xuất bản. +

+
+
+ + Khám phá sứ mệnh + +
+
+ + {/* --- PHẦN 2: SỨ MỆNH & CHỨC NĂNG --- */} +
+
+ {/* Sứ mệnh */} +
+
+ Sứ mệnh của chúng tôi +
+

+ Định hình lại cách học Lịch sử +

+
+
+

+ + 1 + + Giải quyết rào cản giáo dục +

+

+ Khắc phục sự nhàm chán và khó tiếp cận của phương pháp học + lịch sử truyền thống bằng cách biến dữ liệu chữ viết thành + hình ảnh không gian, thời gian trực quan. +

+
+
+
+

+ + 2 + + Tập trung hóa tri thức +

+

+ Xây dựng một kho dữ liệu lịch sử thống nhất, chuẩn xác, phục + vụ đa dạng đối tượng từ chính phủ, chuyên gia nghiên cứu đến + học sinh, sinh viên và cộng đồng. +

+
+
+
+ + {/* Chức năng */} +
+
+ Tính năng cốt lõi +
+

+ Công nghệ hội tụ +

+
+ {features.map((feat, idx) => ( +
+
+ {feat.icon} +
+
+

+ {feat.title} +

+

+ {feat.desc} +

+
+
+ ))} +
+
+
+
+ + {/* --- PHẦN 3: ĐỘI NGŨ PHÁT TRIỂN --- */} +
+
+
+ Về chúng tôi +
+

+ Đội ngũ phát triển +

+

+ Những con người đam mê lịch sử và công nghệ chung tay xây dựng cỗ + máy thời gian kỹ thuật số. +

+
+ +
+ {team.map((member, i) => ( +
+
+ {member.name} +
+

+ {member.name} +

+

+ {member.role} +

+

{member.desc}

+
+ ))} +
+
+
+ + {/* --- PHẦN 4: GÓP Ý & LIÊN HỆ --- */} +
+
+ {/* Box Text */} +
+

+ Góp ý cho chúng tôi! +

+

+ Đăng ký nhận tin tức mới nhất hoặc để lại ý kiến đóng góp giúp hệ + thống hoàn thiện hơn. +

+
+ + {/* Box Form Nhập Liệu (Dòng trên - Dòng dưới) */} +
+ + +
+ +
+
+ + {/* Box Socials */} +
+

Follow us

+
+ + + + + +
+
+
+
+ + {/* FOOTER */} + +
+ ); +} diff --git a/src/app/user/layout.tsx b/src/app/user/layout.tsx index 239c85c..cbca4bb 100644 --- a/src/app/user/layout.tsx +++ b/src/app/user/layout.tsx @@ -8,6 +8,7 @@ import { apiGetCurrentUser } from "@/service/auth"; import { setUserData } from "@/store/features/userSlice"; import React, { useEffect } from "react"; import { useDispatch } from "react-redux"; +import ChatbotWidget from "@/components/ui/chat/ChatbotWidget"; export default function AdminLayout({ children, @@ -51,6 +52,7 @@ export default function AdminLayout({ {/* Page Content */}
{children}
+ ); } diff --git a/src/app/user/projects/page.tsx b/src/app/user/projects/page.tsx index ad0704e..619dd50 100644 --- a/src/app/user/projects/page.tsx +++ b/src/app/user/projects/page.tsx @@ -342,7 +342,7 @@ export default function ProjectsPage() { variant="outline" disabled={isExportingProjectId === String(project.id)} onClick={() => handleExportHeadSnapshot(project)} - title="Export head commit snapshot_json" + // title="Export head commit snapshot_json" > ExportJSON @@ -484,7 +484,7 @@ export default function ProjectsPage() { disabled={isSubmitting} className="bg-gray-900 hover:bg-gray-800 text-white" onClick={handleCreateProjectWithJson} - title="Tạo dự án và tạo commit đầu tiên từ JSON snapshot" + // title="Tạo dự án và tạo commit đầu tiên từ JSON snapshot" > Tạo với JSON @@ -494,4 +494,4 @@ export default function ProjectsPage() { ); -} +} \ No newline at end of file diff --git a/src/app/user/quick-qa/page.tsx b/src/app/user/quick-qa/page.tsx new file mode 100644 index 0000000..c4a4c0d --- /dev/null +++ b/src/app/user/quick-qa/page.tsx @@ -0,0 +1,82 @@ +'use client'; + +import { useState } from 'react'; + +const faqData = [ + { + id: 1, + question: "1. Phần mềm này tương thích với những hệ điều hành nào?", + answer: "Hệ thống tương thích hoàn toàn với Windows 10/11, macOS 12 trở lên. Đối với môi trường máy chủ, chúng tôi hỗ trợ các bản phân phối Linux phổ biến như Ubuntu và Debian." + }, + { + id: 2, + question: "2. Sự khác biệt giữa phiên bản Miễn phí và Trả phí là gì?", + answer: "Phiên bản trả phí cung cấp băng thông không giới hạn, hỗ trợ kỹ thuật ưu tiên 24/7, và quyền truy cập sớm vào các tính năng nâng cao. Bản miễn phí sẽ giới hạn một số tính năng xuất dữ liệu." + }, + { + id: 3, + question: "3. Hệ thống hỗ trợ những phương thức kết nối nào?", + answer: "Chúng tôi hỗ trợ kết nối qua REST API, WebSocket cho dữ liệu thời gian thực và cung cấp sẵn SDK cho các ngôn ngữ phổ biến như TypeScript, Go." + }, + { + id: 4, + question: "4. Làm thế nào để tôi có thể tích hợp vào dự án Next.js hiện tại?", + answer: "Bạn chỉ cần cài đặt package qua npm/yarn, thêm API Key vào file .env và gọi component Provider ở file layout.tsx gốc. Tài liệu chi tiết có sẵn trong mục Developer Docs." + }, + { + id: 5, + question: "5. Dữ liệu của tôi được bảo mật như thế nào?", + answer: "Toàn bộ dữ liệu được mã hóa đầu cuối (End-to-End Encryption). Chúng tôi tuân thủ nghiêm ngặt các tiêu chuẩn bảo mật quốc tế và thường xuyên rà soát hệ thống để phòng chống các lỗ hổng bảo mật." + } +]; + +export default function Page() { + // Lưu index của câu hỏi đang được mở. Mặc định mở câu đầu tiên (index 0). + const [openIndex, setOpenIndex] = useState(0); + + const toggleFAQ = (index: number) => { + // Nếu click lại vào câu đang mở thì đóng nó, ngược lại thì mở câu mới + setOpenIndex(openIndex === index ? null : index); + }; + + return ( +
+
+

FAQs

+ +
+ {faqData.map((faq, index) => { + const isOpen = openIndex === index; + + return ( +
+ + + {/* Phần nội dung có hiệu ứng trượt */} +
+

+ {faq.answer} +

+
+
+ ); + })} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/header/UserDropdown.tsx b/src/components/header/UserDropdown.tsx index 3d1dc72..8421af8 100644 --- a/src/components/header/UserDropdown.tsx +++ b/src/components/header/UserDropdown.tsx @@ -8,7 +8,7 @@ import { fullDataUser } from "@/interface/admin"; import { UserMetaCardProps } from "@/interface/user"; import { apiGetCurrentUser, apiLogout } from "@/service/auth"; import { useRouter } from "next/navigation"; -import { ListIcon } from "@/icons"; +import { ListIcon, ShootingStarIcon } from "@/icons"; export default function UserDropdown() { const router = useRouter(); @@ -152,6 +152,19 @@ export default function UserDropdown() { Nhà Sử Học + +
  • + + + + + Về chúng tôi +
  • {/*
  • ([ + { + id: "init", + sender: "bot", + text: "Xin chào! Tôi là trợ lý lịch sử thân thiện. Tôi có thể giúp gì cho bạn?", + }, + ]); + const [input, setInput] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + if (isOpen) { + scrollToBottom(); + } + }, [messages, isOpen]); + + const handleSend = async () => { + if (!input.trim()) return; + + const userMessage: Message = { + id: Date.now().toString(), + sender: "user", + text: input.trim(), + }; + + setMessages((prev) => [...prev, userMessage]); + setInput(""); + setIsLoading(true); + + try { + const payload: ChatbotPayload = { + project_id: projectId, + question: userMessage.text, + }; + + const res = await apiChatbot(payload); + + const botMessage: Message = { + id: (Date.now() + 1).toString(), + sender: "bot", + text: res?.status + ? res?.data + : "Xin lỗi, tôi không thể trả lời lúc này.", + }; + + setMessages((prev) => [...prev, botMessage]); + } catch (error: any) { + const errorMessage: Message = { + id: (Date.now() + 1).toString(), + sender: "bot", + text: + error?.response?.data?.message || + "Có lỗi xảy ra khi kết nối. Vui lòng thử lại sau.", + }; + setMessages((prev) => [...prev, errorMessage]); + } finally { + setIsLoading(false); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + handleSend(); + } + }; + + return ( +
    + {!isOpen && ( + + )} + + {/* Khung Chat */} + {isOpen && ( +
    + {/* Header */} +
    +
    + + + + Trợ lý lịch sử. +
    + +
    + + {/* Nội dung Chat */} +
    + {messages.map((msg) => ( +
    + {msg.text} +
    + ))} + {isLoading && ( +
    +
    +
    +
    +
    + )} +
    +
    + + {/* Khu vực Nhập Input */} +
    +
    + setInput(e.target.value)} + onKeyDown={handleKeyDown} + disabled={isLoading} + className="flex-1 bg-gray-100 dark:bg-gray-800 border-transparent focus:border-brand-500 focus:bg-white dark:focus:bg-gray-900 focus:ring-1 focus:ring-brand-500/20 rounded-full px-4 py-2.5 text-sm outline-none transition-all" + /> + +
    +
    +
    + )} +
    + ); +} diff --git a/src/interface/chatbot.ts b/src/interface/chatbot.ts new file mode 100644 index 0000000..9622e70 --- /dev/null +++ b/src/interface/chatbot.ts @@ -0,0 +1,9 @@ +export interface ChatbotPayload { + project_id?: string; + question: string; +} + +export interface ChatbotResponse { + status: boolean; + data: string; +} \ No newline at end of file diff --git a/src/layout/AppSidebar.tsx b/src/layout/AppSidebar.tsx index 85bdd98..17a0d6c 100644 --- a/src/layout/AppSidebar.tsx +++ b/src/layout/AppSidebar.tsx @@ -15,6 +15,7 @@ import { PageIcon, PieChartIcon, PlugInIcon, + ShootingStarIcon, TableIcon, UserCircleIcon, } from "../icons/index"; @@ -53,36 +54,47 @@ const ALL_NAV_ITEMS: NavItem[] = [ name: "Tài Khoản", path: "/user/account", }, + ]; const OTHERS_ITEMS: NavItem[] = [ + // { + // icon: , + // name: "Charts", + // subItems: [ + // { name: "Line Chart", path: "/line-chart", pro: false }, + // { name: "Bar Chart", path: "/bar-chart", pro: false }, + // ], + // }, + // { + // icon: , + // name: "UI Elements", + // subItems: [ + // { name: "Alerts", path: "/alerts", pro: false }, + // { name: "Avatar", path: "/avatars", pro: false }, + // { name: "Badge", path: "/badge", pro: false }, + // { name: "Buttons", path: "/buttons", pro: false }, + // { name: "Images", path: "/images", pro: false }, + // { name: "Videos", path: "/videos", pro: false }, + // ], + // }, + // { + // icon: , + // name: "Authentication", + // subItems: [ + // { name: "Sign In", path: "/signin", pro: false }, + // { name: "Sign Up", path: "/signup", pro: false }, + // ], + // }, { - icon: , - name: "Charts", - subItems: [ - { name: "Line Chart", path: "/line-chart", pro: false }, - { name: "Bar Chart", path: "/bar-chart", pro: false }, - ], + icon: , + name: "Về Chúng Tôi", + path: "/user/about-us", }, { - icon: , - name: "UI Elements", - subItems: [ - { name: "Alerts", path: "/alerts", pro: false }, - { name: "Avatar", path: "/avatars", pro: false }, - { name: "Badge", path: "/badge", pro: false }, - { name: "Buttons", path: "/buttons", pro: false }, - { name: "Images", path: "/images", pro: false }, - { name: "Videos", path: "/videos", pro: false }, - ], - }, - { - icon: , - name: "Authentication", - subItems: [ - { name: "Sign In", path: "/signin", pro: false }, - { name: "Sign Up", path: "/signup", pro: false }, - ], + icon: , + name: "Hỗ trợ", + path: "/user/quick-qa", }, ]; @@ -280,12 +292,12 @@ const AppSidebar: React.FC = () => { {renderMenuItems(ALL_NAV_ITEMS, "main")}
    - {/*
    +

    {isExpanded || isHovered || isMobileOpen ? "Others" : }

    {renderMenuItems(OTHERS_ITEMS, "others")} -
    */} +
    diff --git a/src/service/chatbotService.ts b/src/service/chatbotService.ts new file mode 100644 index 0000000..885d0f6 --- /dev/null +++ b/src/service/chatbotService.ts @@ -0,0 +1,8 @@ +import api from "@/config/config"; +import { API } from "../../api"; +import { ChatbotPayload, ChatbotResponse } from "@/interface/chatbot"; + +export const apiChatbot = async (payload: ChatbotPayload): Promise => { + const response = await api.post(API.Chatbot.CHAT,payload); + return await response?.data; +}; \ No newline at end of file