diff --git a/api.ts b/api.ts
index 1b076e3..203c67a 100644
--- a/api.ts
+++ b/api.ts
@@ -66,5 +66,6 @@ export const API = {
},
Chatbot:{
CHAT: `${API_URL_ROOT}/chatbot/chat`,
+ HISTORY: `${API_URL_ROOT}/chatbot/history`,
}
}
diff --git a/package-lock.json b/package-lock.json
index 300c84b..854c7a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "fe_admin_history_web",
+ "name": "ultra_history_map",
"version": "2.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "fe_admin_history_web",
+ "name": "ultra_history_map",
"version": "2.2.3",
"dependencies": {
"@fullcalendar/core": "^6.1.19",
diff --git a/public/images/brand/brand-01.svg b/public/images/brand/brand-01.svg
deleted file mode 100644
index 7321fbf..0000000
--- a/public/images/brand/brand-01.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/public/images/brand/brand-02.svg b/public/images/brand/brand-02.svg
deleted file mode 100644
index 14da422..0000000
--- a/public/images/brand/brand-02.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/brand/brand-03.svg b/public/images/brand/brand-03.svg
deleted file mode 100644
index 8d29afa..0000000
--- a/public/images/brand/brand-03.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/public/images/brand/brand-04.svg b/public/images/brand/brand-04.svg
deleted file mode 100644
index 837a4d4..0000000
--- a/public/images/brand/brand-04.svg
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/brand/brand-05.svg b/public/images/brand/brand-05.svg
deleted file mode 100644
index 7044f46..0000000
--- a/public/images/brand/brand-05.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/public/images/brand/brand-06.svg b/public/images/brand/brand-06.svg
deleted file mode 100644
index 78c5d01..0000000
--- a/public/images/brand/brand-06.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/public/images/brand/brand-07.svg b/public/images/brand/brand-07.svg
deleted file mode 100644
index 5abb368..0000000
--- a/public/images/brand/brand-07.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/public/images/brand/brand-08.svg b/public/images/brand/brand-08.svg
deleted file mode 100644
index 71bc1e2..0000000
--- a/public/images/brand/brand-08.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/public/images/brand/brand-09.svg b/public/images/brand/brand-09.svg
deleted file mode 100644
index 1330ba2..0000000
--- a/public/images/brand/brand-09.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/public/images/brand/brand-10.svg b/public/images/brand/brand-10.svg
deleted file mode 100644
index 60308dd..0000000
--- a/public/images/brand/brand-10.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/public/images/brand/brand-11.svg b/public/images/brand/brand-11.svg
deleted file mode 100644
index b316bb4..0000000
--- a/public/images/brand/brand-11.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/public/images/brand/brand-12.svg b/public/images/brand/brand-12.svg
deleted file mode 100644
index 8396a56..0000000
--- a/public/images/brand/brand-12.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/public/images/brand/brand-13.svg b/public/images/brand/brand-13.svg
deleted file mode 100644
index dd53f79..0000000
--- a/public/images/brand/brand-13.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/public/images/brand/brand-14.svg b/public/images/brand/brand-14.svg
deleted file mode 100644
index 381d72d..0000000
--- a/public/images/brand/brand-14.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/public/images/brand/brand-15.svg b/public/images/brand/brand-15.svg
deleted file mode 100644
index dfde3dd..0000000
--- a/public/images/brand/brand-15.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/public/images/cards/card-01.jpg b/public/images/cards/card-01.jpg
deleted file mode 100644
index 6ca5220..0000000
Binary files a/public/images/cards/card-01.jpg and /dev/null differ
diff --git a/public/images/cards/card-01.png b/public/images/cards/card-01.png
deleted file mode 100644
index 50136d2..0000000
Binary files a/public/images/cards/card-01.png and /dev/null differ
diff --git a/public/images/cards/card-02.jpg b/public/images/cards/card-02.jpg
deleted file mode 100644
index 4529105..0000000
Binary files a/public/images/cards/card-02.jpg and /dev/null differ
diff --git a/public/images/cards/card-02.png b/public/images/cards/card-02.png
deleted file mode 100644
index 29e3c40..0000000
Binary files a/public/images/cards/card-02.png and /dev/null differ
diff --git a/public/images/cards/card-03.jpg b/public/images/cards/card-03.jpg
deleted file mode 100644
index dea4663..0000000
Binary files a/public/images/cards/card-03.jpg and /dev/null differ
diff --git a/public/images/cards/card-03.png b/public/images/cards/card-03.png
deleted file mode 100644
index b8c8ed0..0000000
Binary files a/public/images/cards/card-03.png and /dev/null differ
diff --git a/public/images/carousel/carousel-01.png b/public/images/carousel/carousel-01.png
deleted file mode 100644
index 0c738c8..0000000
Binary files a/public/images/carousel/carousel-01.png and /dev/null differ
diff --git a/public/images/carousel/carousel-02.png b/public/images/carousel/carousel-02.png
deleted file mode 100644
index 963ca5f..0000000
Binary files a/public/images/carousel/carousel-02.png and /dev/null differ
diff --git a/public/images/carousel/carousel-03.png b/public/images/carousel/carousel-03.png
deleted file mode 100644
index d744261..0000000
Binary files a/public/images/carousel/carousel-03.png and /dev/null differ
diff --git a/public/images/carousel/carousel-04.png b/public/images/carousel/carousel-04.png
deleted file mode 100644
index 58d393d..0000000
Binary files a/public/images/carousel/carousel-04.png and /dev/null differ
diff --git a/public/images/country/country-01.svg b/public/images/country/country-01.svg
deleted file mode 100644
index 4c14b12..0000000
--- a/public/images/country/country-01.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-02.svg b/public/images/country/country-02.svg
deleted file mode 100644
index 52f57c7..0000000
--- a/public/images/country/country-02.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-03.svg b/public/images/country/country-03.svg
deleted file mode 100644
index e435fab..0000000
--- a/public/images/country/country-03.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-04.svg b/public/images/country/country-04.svg
deleted file mode 100644
index 93b49b6..0000000
--- a/public/images/country/country-04.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-05.svg b/public/images/country/country-05.svg
deleted file mode 100644
index 5aa26b5..0000000
--- a/public/images/country/country-05.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-06.svg b/public/images/country/country-06.svg
deleted file mode 100644
index 730e2e6..0000000
--- a/public/images/country/country-06.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-07.svg b/public/images/country/country-07.svg
deleted file mode 100644
index ce770d4..0000000
--- a/public/images/country/country-07.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/country/country-08.svg b/public/images/country/country-08.svg
deleted file mode 100644
index c652b95..0000000
--- a/public/images/country/country-08.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/images/product/product-01.jpg b/public/images/product/product-01.jpg
deleted file mode 100644
index 7ffb1e6..0000000
Binary files a/public/images/product/product-01.jpg and /dev/null differ
diff --git a/public/images/product/product-02.jpg b/public/images/product/product-02.jpg
deleted file mode 100644
index db30a9a..0000000
Binary files a/public/images/product/product-02.jpg and /dev/null differ
diff --git a/public/images/product/product-03.jpg b/public/images/product/product-03.jpg
deleted file mode 100644
index 95fd8d4..0000000
Binary files a/public/images/product/product-03.jpg and /dev/null differ
diff --git a/public/images/product/product-04.jpg b/public/images/product/product-04.jpg
deleted file mode 100644
index 131a9f5..0000000
Binary files a/public/images/product/product-04.jpg and /dev/null differ
diff --git a/public/images/product/product-05.jpg b/public/images/product/product-05.jpg
deleted file mode 100644
index 1ad17a9..0000000
Binary files a/public/images/product/product-05.jpg and /dev/null differ
diff --git a/public/images/video-thumb/thumb-16.png b/public/images/video-thumb/thumb-16.png
deleted file mode 100644
index 1f12017..0000000
Binary files a/public/images/video-thumb/thumb-16.png and /dev/null differ
diff --git a/public/images/video-thumb/youtube-icon-84.svg b/public/images/video-thumb/youtube-icon-84.svg
deleted file mode 100644
index 5a9478a..0000000
--- a/public/images/video-thumb/youtube-icon-84.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/src/app/(admin)/(ui-elements)/alerts/page.tsx b/src/app/(admin)/(ui-elements)/alerts/page.tsx
deleted file mode 100644
index d26561e..0000000
--- a/src/app/(admin)/(ui-elements)/alerts/page.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import Alert from "@/components/ui/alert/Alert";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Alerts | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Alerts page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
- // other metadata
-};
-
-export default function Alerts() {
- return (
-
-
-
-
-
-
-
-
-
-
- {" "}
-
-
-
- {" "}
-
-
-
-
-
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/avatars/page.tsx b/src/app/(admin)/(ui-elements)/avatars/page.tsx
deleted file mode 100644
index b1b1749..0000000
--- a/src/app/(admin)/(ui-elements)/avatars/page.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import Avatar from "@/components/ui/avatar/Avatar";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Avatars | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Avatars page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
-};
-
-export default function AvatarPage() {
- return (
-
-
-
-
- {/* Default Avatar (No Status) */}
-
-
-
-
-
-
-
- {" "}
-
-
-
-
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/badge/page.tsx b/src/app/(admin)/(ui-elements)/badge/page.tsx
deleted file mode 100644
index 7dde8dc..0000000
--- a/src/app/(admin)/(ui-elements)/badge/page.tsx
+++ /dev/null
@@ -1,221 +0,0 @@
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import Badge from "@/components/ui/badge/Badge";
-import { PlusIcon } from "@/icons";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Badge | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Badge page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
- // other metadata
-};
-
-export default function BadgePage() {
- return (
-
-
-
-
-
-
- With Light Background
-
-
-
-
- {/* Light Variant */}
-
- Primary
-
-
- Success
- {" "}
-
- Error
- {" "}
-
- Warning
- {" "}
-
- Info
-
-
- Light
-
-
- Dark
-
-
-
-
-
-
-
-
- With Solid Background
-
-
-
-
- {/* Light Variant */}
-
- Primary
-
-
- Success
- {" "}
-
- Error
- {" "}
-
- Warning
- {" "}
-
- Info
-
-
- Light
-
-
- Dark
-
-
-
-
-
-
-
-
- Light Background with Left Icon
-
-
-
-
- }>
- Primary
-
- }>
- Success
- {" "}
- }>
- Error
- {" "}
- }>
- Warning
- {" "}
- }>
- Info
-
- }>
- Light
-
- }>
- Dark
-
-
-
-
-
-
-
-
- Solid Background with Left Icon
-
-
-
-
- }>
- Primary
-
- }>
- Success
- {" "}
- }>
- Error
- {" "}
- }>
- Warning
- {" "}
- }>
- Info
-
- }>
- Light
-
- }>
- Dark
-
-
-
-
-
-
-
-
- Light Background with Right Icon
-
-
-
-
- }>
- Primary
-
- }>
- Success
- {" "}
- }>
- Error
- {" "}
- }>
- Warning
- {" "}
- }>
- Info
-
- }>
- Light
-
- }>
- Dark
-
-
-
-
-
-
-
-
- Solid Background with Right Icon
-
-
-
-
- }>
- Primary
-
- }>
- Success
- {" "}
- }>
- Error
- {" "}
- }>
- Warning
- {" "}
- }>
- Info
-
- }>
- Light
-
- }>
- Dark
-
-
-
-
-
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/buttons/page.tsx b/src/app/(admin)/(ui-elements)/buttons/page.tsx
deleted file mode 100644
index 77feb50..0000000
--- a/src/app/(admin)/(ui-elements)/buttons/page.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import Button from "@/components/ui/button/Button";
-import { BoxIcon } from "@/icons";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Buttons | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Buttons page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
-};
-
-export default function Buttons() {
- return (
-
-
-
- {/* Primary Button */}
-
-
-
- Button Text
-
-
- Button Text
-
-
-
- {/* Primary Button with Start Icon */}
-
-
- }>
- Button Text
-
- }>
- Button Text
-
-
- {" "}
- {/* Primary Button with Start Icon */}
-
-
- }>
- Button Text
-
- }>
- Button Text
-
-
-
- {/* Outline Button */}
-
-
- {/* Outline Button */}
-
- Button Text
-
-
- Button Text
-
-
-
- {/* Outline Button with Start Icon */}
-
-
- }>
- Button Text
-
- }>
- Button Text
-
-
- {" "}
- {/* Outline Button with Start Icon */}
-
-
- }>
- Button Text
-
- }>
- Button Text
-
-
-
-
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/images/page.tsx b/src/app/(admin)/(ui-elements)/images/page.tsx
deleted file mode 100644
index bcdecd1..0000000
--- a/src/app/(admin)/(ui-elements)/images/page.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import ResponsiveImage from "@/components/ui/images/ResponsiveImage";
-import ThreeColumnImageGrid from "@/components/ui/images/ThreeColumnImageGrid";
-import TwoColumnImageGrid from "@/components/ui/images/TwoColumnImageGrid";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Images | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Images page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
- // other metadata
-};
-
-export default function Images() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/modals/page.tsx b/src/app/(admin)/(ui-elements)/modals/page.tsx
deleted file mode 100644
index 4afa0d0..0000000
--- a/src/app/(admin)/(ui-elements)/modals/page.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import DefaultModal from "@/components/example/ModalExample/DefaultModal";
-import FormInModal from "@/components/example/ModalExample/FormInModal";
-import FullScreenModal from "@/components/example/ModalExample/FullScreenModal";
-import ModalBasedAlerts from "@/components/example/ModalExample/ModalBasedAlerts";
-import VerticallyCenteredModal from "@/components/example/ModalExample/VerticallyCenteredModal";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Modals | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Modals page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
- // other metadata
-};
-
-export default function Modals() {
- return (
-
- );
-}
diff --git a/src/app/(admin)/(ui-elements)/videos/page.tsx b/src/app/(admin)/(ui-elements)/videos/page.tsx
deleted file mode 100644
index 08ddb46..0000000
--- a/src/app/(admin)/(ui-elements)/videos/page.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import VideosExample from "@/components/ui/video/VideosExample";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Videos | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Videos page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
-};
-
-export default function VideoPage() {
- return (
-
- );
-}
diff --git a/src/app/user/(chart)/bar-chart/page.tsx b/src/app/user/(chart)/bar-chart/page.tsx
deleted file mode 100644
index 9ae0984..0000000
--- a/src/app/user/(chart)/bar-chart/page.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import BarChartOne from "@/components/charts/bar/BarChartOne";
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Bar Chart | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Bar Chart page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
-};
-
-export default function page() {
- return (
-
- );
-}
diff --git a/src/app/user/(chart)/line-chart/page.tsx b/src/app/user/(chart)/line-chart/page.tsx
deleted file mode 100644
index 6ab7664..0000000
--- a/src/app/user/(chart)/line-chart/page.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import LineChartOne from "@/components/charts/line/LineChartOne";
-import ComponentCard from "@/components/common/ComponentCard";
-import PageBreadcrumb from "@/components/common/PageBreadCrumb";
-import { Metadata } from "next";
-import React from "react";
-
-export const metadata: Metadata = {
- title: "Next.js Line Chart | TailAdmin - Next.js Dashboard Template",
- description:
- "This is Next.js Line Chart page for TailAdmin - Next.js Tailwind CSS Admin Dashboard Template",
-};
-export default function LineChart() {
- return (
-
- );
-}
diff --git a/src/app/user/account/page.tsx b/src/app/user/account/page.tsx
deleted file mode 100644
index 8384d3e..0000000
--- a/src/app/user/account/page.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-"use client";
-
-import AccountDetails from "@/components/user-profile/AccountDetails";
-import UserInfoCard from "@/components/user-profile/UserInfoCard";
-import UserMetaCard from "@/components/user-profile/UserMetaCard";
-import { UserMetaCardProps } from "@/interface/user";
-import { apiGetCurrentUser } from "@/service/auth";
-import { setUserData } from "@/store/features/userSlice";
-import { useEffect, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import { RootState } from "@/store/store";
-import StickyHeader from "@/components/ui/StickyHeader";
-import { SafeHTMLRenderer } from "@/components/ui/parse/SafeHTMLRenderer";
-import { apiGetCurrentUserApplications } from "@/service/userService";
-import Loading from "@/app/loading";
-
-export default function Profile() {
- const currentUser = useSelector((state: RootState) => state.user.data);
- const dispatch = useDispatch();
-
- const [application, setApplication] = useState(null);
- const [appLoading, setAppLoading] = useState(false);
-
- const isHistorian = !!currentUser?.roles?.some(
- (role: any) => role.name === "HISTORIAN"
- );
-
- // Background refresh of user data to ensure eventual consistency
- useEffect(() => {
- const fetchUser = async () => {
- try {
- const userData = await apiGetCurrentUser();
- dispatch(setUserData(userData.data));
- } catch (err) {
- console.error("Lỗi:", err);
- }
- };
- fetchUser();
- }, [dispatch]);
-
- // Fetch applications in parallel immediately if user is a historian
- useEffect(() => {
- if (isHistorian) {
- const fetchApp = async () => {
- try {
- setAppLoading(true);
- const res = await apiGetCurrentUserApplications();
- if (res?.data) {
- const approvedApp =
- res.data.find((app: any) => app.status === "APPROVED") ||
- res.data[0];
- setApplication(approvedApp);
- }
- } catch (err) {
- console.error("Lỗi khi tải hồ sơ nhà sử học:", err);
- } finally {
- setAppLoading(false);
- }
- };
- fetchApp();
- }
- }, [isHistorian]);
-
- if (!currentUser) {
- return ;
- }
-
- const userMetaProps: UserMetaCardProps = {
- data: currentUser
- ? {
- id: currentUser.id,
- email: currentUser.email,
- profile: currentUser.profile,
- roles: currentUser.roles?.map((role) => ({
- id: Number(role.id) || undefined,
- name: role.name,
- })),
- }
- : undefined,
- status: true,
- };
-
- // Nếu người dùng có role là HISTORIAN
- if (isHistorian) {
- return (
-
-
-
-
-
-
- {appLoading ? (
-
- ) : application ? (
-
-
-
- ) : (
-
- Không tìm thấy thông tin hồ sơ nhà sử học.
-
- )}
-
-
-
- );
- }
-
- return (
-
-
-
- Thông tin tài khoản
-
-
-
-
- );
-}
diff --git a/src/app/user/page.tsx b/src/app/user/page.tsx
index ff75fa0..6e99209 100644
--- a/src/app/user/page.tsx
+++ b/src/app/user/page.tsx
@@ -1,41 +1,126 @@
-import type { Metadata } from "next";
-import { EcommerceMetrics } from "@/components/ecommerce/EcommerceMetrics";
-import React from "react";
-import MonthlyTarget from "@/components/ecommerce/MonthlyTarget";
-import MonthlySalesChart from "@/components/ecommerce/MonthlySalesChart";
-import StatisticsChart from "@/components/ecommerce/StatisticsChart";
-import RecentOrders from "@/components/ecommerce/RecentOrders";
-import DemographicCard from "@/components/ecommerce/DemographicCard";
+"use client";
-export const metadata: Metadata = {
- title:
- "Home Page",
- description: "This is Dashboard Home for History Web",
-};
+import AccountDetails from "@/components/user-profile/AccountDetails";
+import UserInfoCard from "@/components/user-profile/UserInfoCard";
+import UserMetaCard from "@/components/user-profile/UserMetaCard";
+import { UserMetaCardProps } from "@/interface/user";
+import { apiGetCurrentUser } from "@/service/auth";
+import { setUserData } from "@/store/features/userSlice";
+import { useEffect, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "@/store/store";
+import StickyHeader from "@/components/ui/StickyHeader";
+import { SafeHTMLRenderer } from "@/components/ui/parse/SafeHTMLRenderer";
+import { apiGetCurrentUserApplications } from "@/service/userService";
+import Loading from "@/app/loading";
+
+export default function Profile() {
+ const currentUser = useSelector((state: RootState) => state.user.data);
+ const dispatch = useDispatch();
+
+ const [application, setApplication] = useState(null);
+ const [appLoading, setAppLoading] = useState(false);
+
+ const isHistorian = !!currentUser?.roles?.some(
+ (role: any) => role.name === "HISTORIAN"
+ );
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const userData = await apiGetCurrentUser();
+ dispatch(setUserData(userData.data));
+ } catch (err) {
+ console.error("Lỗi:", err);
+ }
+ };
+ fetchUser();
+ }, [dispatch]);
+
+ useEffect(() => {
+ if (isHistorian) {
+ const fetchApp = async () => {
+ try {
+ setAppLoading(true);
+ const res = await apiGetCurrentUserApplications();
+ if (res?.data) {
+ const approvedApp =
+ res.data.find((app: any) => app.status === "APPROVED") ||
+ res.data[0];
+ setApplication(approvedApp);
+ }
+ } catch (err) {
+ console.error("Lỗi khi tải hồ sơ nhà sử học:", err);
+ } finally {
+ setAppLoading(false);
+ }
+ };
+ fetchApp();
+ }
+ }, [isHistorian]);
+
+ if (!currentUser) {
+ return ;
+ }
+
+ const userMetaProps: UserMetaCardProps = {
+ data: currentUser
+ ? {
+ id: currentUser.id,
+ email: currentUser.email,
+ profile: currentUser.profile,
+ roles: currentUser.roles?.map((role) => ({
+ id: Number(role.id) || undefined,
+ name: role.name,
+ })),
+ }
+ : undefined,
+ status: true,
+ };
+
+ // Nếu người dùng có role là HISTORIAN
+ if (isHistorian) {
+ return (
+
+
+
+
+
+
+ {appLoading ? (
+
+ ) : application ? (
+
+
+
+ ) : (
+
+ Không tìm thấy thông tin hồ sơ nhà sử học.
+
+ )}
+
+
+
+ );
+ }
-export default function Ecommerce() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Thông tin tài khoản
+
+
);
diff --git a/src/components/calendar/Calendar.tsx b/src/components/calendar/Calendar.tsx
deleted file mode 100644
index b3bb58c..0000000
--- a/src/components/calendar/Calendar.tsx
+++ /dev/null
@@ -1,293 +0,0 @@
-"use client";
-import React, { useState, useRef, useEffect } from "react";
-import FullCalendar from "@fullcalendar/react";
-import dayGridPlugin from "@fullcalendar/daygrid";
-import timeGridPlugin from "@fullcalendar/timegrid";
-import interactionPlugin from "@fullcalendar/interaction";
-import {
- EventInput,
- DateSelectArg,
- EventClickArg,
- EventContentArg,
-} from "@fullcalendar/core";
-import { useModal } from "@/hooks/useModal";
-import { Modal } from "@/components/ui/modal";
-import { newId } from "@/uhm/lib/utils/id";
-
-interface CalendarEvent extends EventInput {
- extendedProps: {
- calendar: string;
- };
-}
-
-const Calendar: React.FC = () => {
- const [selectedEvent, setSelectedEvent] = useState
(
- null
- );
- const [eventTitle, setEventTitle] = useState("");
- const [eventStartDate, setEventStartDate] = useState("");
- const [eventEndDate, setEventEndDate] = useState("");
- const [eventLevel, setEventLevel] = useState("");
- const [events, setEvents] = useState([]);
- const calendarRef = useRef(null);
- const { isOpen, openModal, closeModal } = useModal();
-
- const calendarsEvents = {
- Danger: "danger",
- Success: "success",
- Primary: "primary",
- Warning: "warning",
- };
-
- useEffect(() => {
- // Initialize with some events
- setEvents([
- {
- id: "1",
- title: "Event Conf.",
- start: new Date().toISOString().split("T")[0],
- extendedProps: { calendar: "Danger" },
- },
- {
- id: "2",
- title: "Meeting",
- start: new Date(Date.now() + 86400000).toISOString().split("T")[0],
- extendedProps: { calendar: "Success" },
- },
- {
- id: "3",
- title: "Workshop",
- start: new Date(Date.now() + 172800000).toISOString().split("T")[0],
- end: new Date(Date.now() + 259200000).toISOString().split("T")[0],
- extendedProps: { calendar: "Primary" },
- },
- ]);
- }, []);
-
- const handleDateSelect = (selectInfo: DateSelectArg) => {
- resetModalFields();
- setEventStartDate(selectInfo.startStr);
- setEventEndDate(selectInfo.endStr || selectInfo.startStr);
- openModal();
- };
-
- const handleEventClick = (clickInfo: EventClickArg) => {
- const event = clickInfo.event;
- setSelectedEvent({
- id: event.id,
- title: event.title,
- start: event.startStr,
- end: event.endStr,
- extendedProps: {
- calendar: event.extendedProps.calendar,
- },
- } as CalendarEvent);
-
- setEventTitle(event.title);
- setEventStartDate(event.start?.toISOString().split("T")[0] || "");
- setEventEndDate(event.end?.toISOString().split("T")[0] || "");
- setEventLevel(event.extendedProps.calendar);
- openModal();
- };
-
- const handleAddOrUpdateEvent = () => {
- if (selectedEvent) {
- // Update existing event
- setEvents((prevEvents) =>
- prevEvents.map((event) =>
- event.id === selectedEvent.id
- ? {
- ...event,
- title: eventTitle,
- start: eventStartDate,
- end: eventEndDate,
- extendedProps: { calendar: eventLevel },
- }
- : event
- )
- );
- } else {
- // Add new event
- const newEvent: CalendarEvent = {
- id: newId(),
- title: eventTitle,
- start: eventStartDate,
- end: eventEndDate,
- allDay: true,
- extendedProps: { calendar: eventLevel },
- };
- setEvents((prevEvents) => [...prevEvents, newEvent]);
- }
- closeModal();
- resetModalFields();
- };
-
- const resetModalFields = () => {
- setEventTitle("");
- setEventStartDate("");
- setEventEndDate("");
- setEventLevel("");
- setSelectedEvent(null);
- };
-
- return (
-
-
-
-
-
-
-
-
- {selectedEvent ? "Edit Event" : "Add Event"}
-
-
- Plan your next big moment: schedule or edit an event to stay on
- track
-
-
-
-
-
-
- Event Title
-
- setEventTitle(e.target.value)}
- className="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
- />
-
-
-
-
- Event Color
-
-
- {Object.entries(calendarsEvents).map(([key, value]) => (
-
-
-
-
- setEventLevel(key)}
- />
-
-
-
-
- {key}
-
-
-
- ))}
-
-
-
-
-
- Enter Start Date
-
-
- setEventStartDate(e.target.value)}
- className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
- />
-
-
-
-
-
- Enter End Date
-
-
- setEventEndDate(e.target.value)}
- className="dark:bg-dark-900 h-11 w-full appearance-none rounded-lg border border-gray-300 bg-transparent bg-none px-4 py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
- />
-
-
-
-
-
- Close
-
-
- {selectedEvent ? "Update Changes" : "Add Event"}
-
-
-
-
-
- );
-};
-
-const renderEventContent = (eventInfo: EventContentArg) => {
- const colorClass = `fc-bg-${eventInfo.event.extendedProps.calendar.toLowerCase()}`;
- return (
-
-
-
{eventInfo.timeText}
-
{eventInfo.event.title}
-
- );
-};
-
-export default Calendar;
diff --git a/src/components/charts/bar/BarChartOne.tsx b/src/components/charts/bar/BarChartOne.tsx
deleted file mode 100644
index e373a7a..0000000
--- a/src/components/charts/bar/BarChartOne.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-"use client";
-import React from "react";
-
-import { ApexOptions } from "apexcharts";
-
-import dynamic from "next/dynamic";
-// Dynamically import the ReactApexChart component
-const ReactApexChart = dynamic(() => import("react-apexcharts"), {
- ssr: false,
-});
-
-export default function BarChartOne() {
- const options: ApexOptions = {
- colors: ["#465fff"],
- chart: {
- fontFamily: "Outfit, sans-serif",
- type: "bar",
- height: 180,
- toolbar: {
- show: false,
- },
- },
- plotOptions: {
- bar: {
- horizontal: false,
- columnWidth: "39%",
- borderRadius: 5,
- borderRadiusApplication: "end",
- },
- },
- dataLabels: {
- enabled: false,
- },
- stroke: {
- show: true,
- width: 4,
- colors: ["transparent"],
- },
- xaxis: {
- categories: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ],
- axisBorder: {
- show: false,
- },
- axisTicks: {
- show: false,
- },
- },
- legend: {
- show: true,
- position: "top",
- horizontalAlign: "left",
- fontFamily: "Outfit",
- },
- yaxis: {
- title: {
- text: undefined,
- },
- },
- grid: {
- yaxis: {
- lines: {
- show: true,
- },
- },
- },
- fill: {
- opacity: 1,
- },
-
- tooltip: {
- x: {
- show: false,
- },
- y: {
- formatter: (val: number) => `${val}`,
- },
- },
- };
- const series = [
- {
- name: "Sales",
- data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
- },
- ];
- return (
-
- );
-}
diff --git a/src/components/charts/line/LineChartOne.tsx b/src/components/charts/line/LineChartOne.tsx
deleted file mode 100644
index 94d39b3..0000000
--- a/src/components/charts/line/LineChartOne.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-"use client";
-import React from "react";
-
-import { ApexOptions } from "apexcharts";
-
-import dynamic from "next/dynamic";
-// Dynamically import the ReactApexChart component
-const ReactApexChart = dynamic(() => import("react-apexcharts"), {
- ssr: false,
-});
-
-export default function LineChartOne() {
- const options: ApexOptions = {
- legend: {
- show: false, // Hide legend
- position: "top",
- horizontalAlign: "left",
- },
- colors: ["#465FFF", "#9CB9FF"], // Define line colors
- chart: {
- fontFamily: "Outfit, sans-serif",
- height: 310,
- type: "line", // Set the chart type to 'line'
- toolbar: {
- show: false, // Hide chart toolbar
- },
- },
- stroke: {
- curve: "straight", // Define the line style (straight, smooth, or step)
- width: [2, 2], // Line width for each dataset
- },
-
- fill: {
- type: "gradient",
- gradient: {
- opacityFrom: 0.55,
- opacityTo: 0,
- },
- },
- markers: {
- size: 0, // Size of the marker points
- strokeColors: "#fff", // Marker border color
- strokeWidth: 2,
- hover: {
- size: 6, // Marker size on hover
- },
- },
- grid: {
- xaxis: {
- lines: {
- show: false, // Hide grid lines on x-axis
- },
- },
- yaxis: {
- lines: {
- show: true, // Show grid lines on y-axis
- },
- },
- },
- dataLabels: {
- enabled: false, // Disable data labels
- },
- tooltip: {
- enabled: true, // Enable tooltip
- x: {
- format: "dd MMM yyyy", // Format for x-axis tooltip
- },
- },
- xaxis: {
- type: "category", // Category-based x-axis
- categories: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ],
- axisBorder: {
- show: false, // Hide x-axis border
- },
- axisTicks: {
- show: false, // Hide x-axis ticks
- },
- tooltip: {
- enabled: false, // Disable tooltip for x-axis points
- },
- },
- yaxis: {
- labels: {
- style: {
- fontSize: "12px", // Adjust font size for y-axis labels
- colors: ["#6B7280"], // Color of the labels
- },
- },
- title: {
- text: "", // Remove y-axis title
- style: {
- fontSize: "0px",
- },
- },
- },
- };
-
- const series = [
- {
- name: "Sales",
- data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
- },
- {
- name: "Revenue",
- data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
- },
- ];
- return (
-
- );
-}
diff --git a/src/components/common/ChartTab.tsx b/src/components/common/ChartTab.tsx
deleted file mode 100644
index d2f5820..0000000
--- a/src/components/common/ChartTab.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React, { useState } from "react";
-
-const ChartTab: React.FC = () => {
- const [selected, setSelected] = useState<
- "optionOne" | "optionTwo" | "optionThree"
- >("optionOne");
-
- const getButtonClass = (option: "optionOne" | "optionTwo" | "optionThree") =>
- selected === option
- ? "shadow-theme-xs text-gray-900 dark:text-white bg-white dark:bg-gray-800"
- : "text-gray-500 dark:text-gray-400";
-
- return (
-
- setSelected("optionOne")}
- className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
- "optionOne"
- )}`}
- >
- Monthly
-
-
- setSelected("optionTwo")}
- className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
- "optionTwo"
- )}`}
- >
- Quarterly
-
-
- setSelected("optionThree")}
- className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
- "optionThree"
- )}`}
- >
- Annually
-
-
- );
-};
-
-export default ChartTab;
diff --git a/src/components/ecommerce/CountryMap.tsx b/src/components/ecommerce/CountryMap.tsx
deleted file mode 100644
index 589df37..0000000
--- a/src/components/ecommerce/CountryMap.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from "react";
-// import { VectorMap } from "@react-jvectormap/core";
-import { worldMill } from "@react-jvectormap/world";
-import dynamic from "next/dynamic";
-
-const VectorMap = dynamic(
- () => import("@react-jvectormap/core").then((mod) => mod.VectorMap),
- { ssr: false }
-);
-
-// Define the component props
-interface CountryMapProps {
- mapColor?: string;
-}
-
-type MarkerStyle = {
- initial: {
- fill: string;
- r: number; // Radius for markers
- };
-};
-
-type Marker = {
- latLng: [number, number];
- name: string;
- style?: {
- fill: string;
- borderWidth: number;
- borderColor: string;
- stroke?: string;
- strokeOpacity?: number;
- };
-};
-
-const CountryMap: React.FC = ({ mapColor }) => {
- return (
-
- );
-};
-
-export default CountryMap;
diff --git a/src/components/ecommerce/DemographicCard.tsx b/src/components/ecommerce/DemographicCard.tsx
deleted file mode 100644
index f2b552b..0000000
--- a/src/components/ecommerce/DemographicCard.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-"use client";
-import Image from "next/image";
-
-import CountryMap from "./CountryMap";
-import { useState } from "react";
-import { MoreDotIcon } from "@/icons";
-import { Dropdown } from "../ui/dropdown/Dropdown";
-import { DropdownItem } from "../ui/dropdown/DropdownItem";
-
-export default function DemographicCard() {
- const [isOpen, setIsOpen] = useState(false);
-
- function toggleDropdown() {
- setIsOpen(!isOpen);
- }
-
- function closeDropdown() {
- setIsOpen(false);
- }
-
- return (
-
-
-
-
- Customers Demographic
-
-
- Number of customer based on country
-
-
-
-
-
-
-
-
-
- View More
-
-
- Delete
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- USA
-
-
- 2,379 Customers
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- France
-
-
- 589 Customers
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ecommerce/EcommerceMetrics.tsx b/src/components/ecommerce/EcommerceMetrics.tsx
deleted file mode 100644
index 58ab6c1..0000000
--- a/src/components/ecommerce/EcommerceMetrics.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client";
-import React from "react";
-import Badge from "../ui/badge/Badge";
-import { ArrowDownIcon, ArrowUpIcon, BoxIconLine, GroupIcon } from "@/icons";
-
-export const EcommerceMetrics = () => {
- return (
-
- {/* */}
-
-
-
-
-
-
-
-
- Customers
-
-
- 3,782
-
-
-
-
- 11.01%
-
-
-
- {/* */}
-
- {/* */}
-
-
-
-
-
-
-
- Orders
-
-
- 5,359
-
-
-
-
-
- 9.05%
-
-
-
- {/* */}
-
- );
-};
diff --git a/src/components/ecommerce/MonthlySalesChart.tsx b/src/components/ecommerce/MonthlySalesChart.tsx
deleted file mode 100644
index ed387ce..0000000
--- a/src/components/ecommerce/MonthlySalesChart.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-"use client";
-import { ApexOptions } from "apexcharts";
-import dynamic from "next/dynamic";
-import { MoreDotIcon } from "@/icons";
-import { DropdownItem } from "../ui/dropdown/DropdownItem";
-import { useState } from "react";
-import { Dropdown } from "../ui/dropdown/Dropdown";
-
-// Dynamically import the ReactApexChart component
-const ReactApexChart = dynamic(() => import("react-apexcharts"), {
- ssr: false,
-});
-
-export default function MonthlySalesChart() {
- const options: ApexOptions = {
- colors: ["#465fff"],
- chart: {
- fontFamily: "Outfit, sans-serif",
- type: "bar",
- height: 180,
- toolbar: {
- show: false,
- },
- },
- plotOptions: {
- bar: {
- horizontal: false,
- columnWidth: "39%",
- borderRadius: 5,
- borderRadiusApplication: "end",
- },
- },
- dataLabels: {
- enabled: false,
- },
- stroke: {
- show: true,
- width: 4,
- colors: ["transparent"],
- },
- xaxis: {
- categories: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ],
- axisBorder: {
- show: false,
- },
- axisTicks: {
- show: false,
- },
- },
- legend: {
- show: true,
- position: "top",
- horizontalAlign: "left",
- fontFamily: "Outfit",
- },
- yaxis: {
- title: {
- text: undefined,
- },
- },
- grid: {
- yaxis: {
- lines: {
- show: true,
- },
- },
- },
- fill: {
- opacity: 1,
- },
-
- tooltip: {
- x: {
- show: false,
- },
- y: {
- formatter: (val: number) => `${val}`,
- },
- },
- };
- const series = [
- {
- name: "Sales",
- data: [168, 385, 201, 298, 187, 195, 291, 110, 215, 390, 280, 112],
- },
- ];
- const [isOpen, setIsOpen] = useState(false);
-
- function toggleDropdown() {
- setIsOpen(!isOpen);
- }
-
- function closeDropdown() {
- setIsOpen(false);
- }
-
- return (
-
-
-
- Monthly Sales
-
-
-
-
-
-
-
-
- View More
-
-
- Delete
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ecommerce/MonthlyTarget.tsx b/src/components/ecommerce/MonthlyTarget.tsx
deleted file mode 100644
index d4f66db..0000000
--- a/src/components/ecommerce/MonthlyTarget.tsx
+++ /dev/null
@@ -1,209 +0,0 @@
-"use client";
-// import Chart from "react-apexcharts";
-import { ApexOptions } from "apexcharts";
-
-import dynamic from "next/dynamic";
-import { Dropdown } from "../ui/dropdown/Dropdown";
-import { MoreDotIcon } from "@/icons";
-import { useState } from "react";
-import { DropdownItem } from "../ui/dropdown/DropdownItem";
-// Dynamically import the ReactApexChart component
-const ReactApexChart = dynamic(() => import("react-apexcharts"), {
- ssr: false,
-});
-
-export default function MonthlyTarget() {
- const series = [75.55];
- const options: ApexOptions = {
- colors: ["#465FFF"],
- chart: {
- fontFamily: "Outfit, sans-serif",
- type: "radialBar",
- height: 330,
- sparkline: {
- enabled: true,
- },
- },
- plotOptions: {
- radialBar: {
- startAngle: -85,
- endAngle: 85,
- hollow: {
- size: "80%",
- },
- track: {
- background: "#E4E7EC",
- strokeWidth: "100%",
- margin: 5, // margin is in pixels
- },
- dataLabels: {
- name: {
- show: false,
- },
- value: {
- fontSize: "36px",
- fontWeight: "600",
- offsetY: -40,
- color: "#1D2939",
- formatter: function (val) {
- return val + "%";
- },
- },
- },
- },
- },
- fill: {
- type: "solid",
- colors: ["#465FFF"],
- },
- stroke: {
- lineCap: "round",
- },
- labels: ["Progress"],
- };
-
- const [isOpen, setIsOpen] = useState(false);
-
- function toggleDropdown() {
- setIsOpen(!isOpen);
- }
-
- function closeDropdown() {
- setIsOpen(false);
- }
-
- return (
-
-
-
-
-
- Monthly Target
-
-
- Target you’ve set for each month
-
-
-
-
-
-
-
-
- View More
-
-
- Delete
-
-
-
-
-
-
- You earn $3287 today, it's higher than last month. Keep up your
- good work!
-
-
-
-
-
-
- Target
-
-
- $20K
-
-
-
-
-
-
-
-
-
-
- Revenue
-
-
- $20K
-
-
-
-
-
-
-
-
-
-
- Today
-
-
- $20K
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ecommerce/RecentOrders.tsx b/src/components/ecommerce/RecentOrders.tsx
deleted file mode 100644
index d06a073..0000000
--- a/src/components/ecommerce/RecentOrders.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import {
- Table,
- TableBody,
- TableCell,
- TableHeader,
- TableRow,
-} from "../ui/table";
-import Badge from "../ui/badge/Badge";
-import Image from "next/image";
-
-// Define the TypeScript interface for the table rows
-interface Product {
- id: number; // Unique identifier for each product
- name: string; // Product name
- variants: string; // Number of variants (e.g., "1 Variant", "2 Variants")
- category: string; // Category of the product
- price: string; // Price of the product (as a string with currency symbol)
- // status: string; // Status of the product
- image: string; // URL or path to the product image
- status: "Delivered" | "Pending" | "Canceled"; // Status of the product
-}
-
-// Define the table data using the interface
-const tableData: Product[] = [
- {
- id: 1,
- name: "MacBook Pro 13”",
- variants: "2 Variants",
- category: "Laptop",
- price: "$2399.00",
- status: "Delivered",
- image: "/images/product/product-01.jpg", // Replace with actual image URL
- },
- {
- id: 2,
- name: "Apple Watch Ultra",
- variants: "1 Variant",
- category: "Watch",
- price: "$879.00",
- status: "Pending",
- image: "/images/product/product-02.jpg", // Replace with actual image URL
- },
- {
- id: 3,
- name: "iPhone 15 Pro Max",
- variants: "2 Variants",
- category: "SmartPhone",
- price: "$1869.00",
- status: "Delivered",
- image: "/images/product/product-03.jpg", // Replace with actual image URL
- },
- {
- id: 4,
- name: "iPad Pro 3rd Gen",
- variants: "2 Variants",
- category: "Electronics",
- price: "$1699.00",
- status: "Canceled",
- image: "/images/product/product-04.jpg", // Replace with actual image URL
- },
- {
- id: 5,
- name: "AirPods Pro 2nd Gen",
- variants: "1 Variant",
- category: "Accessories",
- price: "$240.00",
- status: "Delivered",
- image: "/images/product/product-05.jpg", // Replace with actual image URL
- },
-];
-
-export default function RecentOrders() {
- return (
-
-
-
-
- Recent Orders
-
-
-
-
-
-
-
-
-
-
-
- Filter
-
-
- See all
-
-
-
-
-
- {/* Table Header */}
-
-
-
- Products
-
-
- Category
-
-
- Price
-
-
- Status
-
-
-
-
- {/* Table Body */}
-
-
- {tableData.map((product) => (
-
-
-
-
-
-
-
-
- {product.name}
-
-
- {product.variants}
-
-
-
-
-
- {product.price}
-
-
- {product.category}
-
-
-
- {product.status}
-
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/src/components/ecommerce/StatisticsChart.tsx b/src/components/ecommerce/StatisticsChart.tsx
deleted file mode 100644
index ee91bf1..0000000
--- a/src/components/ecommerce/StatisticsChart.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-"use client";
-import { useEffect, useRef } from "react";
-import dynamic from "next/dynamic";
-import { ApexOptions } from "apexcharts";
-import flatpickr from "flatpickr";
-import "flatpickr/dist/flatpickr.css";
-import ChartTab from "../common/ChartTab";
-import { CalenderIcon } from "../../icons";
-
-const Chart = dynamic(() => import("react-apexcharts"), { ssr: false });
-
-export default function StatisticsChart() {
- const datePickerRef = useRef(null);
-
- useEffect(() => {
- if (!datePickerRef.current) return;
-
- const today = new Date();
- const sevenDaysAgo = new Date();
- sevenDaysAgo.setDate(today.getDate() - 6);
-
- const fp = flatpickr(datePickerRef.current, {
- mode: "range",
- static: true,
- monthSelectorType: "static",
- dateFormat: "M d",
- defaultDate: [sevenDaysAgo, today],
- clickOpens: true,
- prevArrow:
- ' ',
- nextArrow:
- ' ',
- });
-
- return () => {
- if (!Array.isArray(fp)) {
- fp.destroy();
- }
- };
- }, []);
-
- const options: ApexOptions = {
- legend: {
- show: false, // Hide legend
- position: "top",
- horizontalAlign: "left",
- },
- colors: ["#465FFF", "#9CB9FF"], // Define line colors
- chart: {
- fontFamily: "Outfit, sans-serif",
- height: 310,
- type: "line", // Set the chart type to 'line'
- toolbar: {
- show: false, // Hide chart toolbar
- },
- },
- stroke: {
- curve: "straight", // Define the line style (straight, smooth, or step)
- width: [2, 2], // Line width for each dataset
- },
-
- fill: {
- type: "gradient",
- gradient: {
- opacityFrom: 0.55,
- opacityTo: 0,
- },
- },
- markers: {
- size: 0, // Size of the marker points
- strokeColors: "#fff", // Marker border color
- strokeWidth: 2,
- hover: {
- size: 6, // Marker size on hover
- },
- },
- grid: {
- xaxis: {
- lines: {
- show: false, // Hide grid lines on x-axis
- },
- },
- yaxis: {
- lines: {
- show: true, // Show grid lines on y-axis
- },
- },
- },
- dataLabels: {
- enabled: false, // Disable data labels
- },
- tooltip: {
- enabled: true, // Enable tooltip
- x: {
- format: "dd MMM yyyy", // Format for x-axis tooltip
- },
- },
- xaxis: {
- type: "category", // Category-based x-axis
- categories: [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ],
- axisBorder: {
- show: false, // Hide x-axis border
- },
- axisTicks: {
- show: false, // Hide x-axis ticks
- },
- tooltip: {
- enabled: false, // Disable tooltip for x-axis points
- },
- },
- yaxis: {
- labels: {
- style: {
- fontSize: "12px", // Adjust font size for y-axis labels
- colors: ["#6B7280"], // Color of the labels
- },
- },
- title: {
- text: "", // Remove y-axis title
- style: {
- fontSize: "0px",
- },
- },
- },
- };
-
- const series = [
- {
- name: "Sales",
- data: [180, 190, 170, 160, 175, 165, 170, 205, 230, 210, 240, 235],
- },
- {
- name: "Revenue",
- data: [40, 30, 50, 40, 55, 40, 70, 100, 110, 120, 150, 140],
- },
- ];
- return (
-
-
-
-
- Statistics
-
-
- Target you've set for each month
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/example/ModalExample/DefaultModal.tsx b/src/components/example/ModalExample/DefaultModal.tsx
deleted file mode 100644
index c017f01..0000000
--- a/src/components/example/ModalExample/DefaultModal.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-
-import { Modal } from "../../ui/modal";
-import Button from "../../ui/button/Button";
-import { useModal } from "@/hooks/useModal";
-
-export default function DefaultModal() {
- const { isOpen, openModal, closeModal } = useModal();
- const handleSave = () => {
- // Handle save logic here
- console.log("Saving changes...");
- closeModal();
- };
- return (
-
-
-
- Open Modal
-
-
-
- Modal Heading
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra. Sed a ligula
- ac odio condimentum aliquet a nec nulla. Aliquam bibendum ex sit
- amet ipsum rutrum feugiat ultrices enim quam.
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra. Sed a ligula
- ac odio.
-
-
-
- Close
-
-
- Save Changes
-
-
-
-
-
- );
-}
diff --git a/src/components/example/ModalExample/FormInModal.tsx b/src/components/example/ModalExample/FormInModal.tsx
deleted file mode 100644
index 9b6c4e0..0000000
--- a/src/components/example/ModalExample/FormInModal.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Button from "../../ui/button/Button";
-import { Modal } from "../../ui/modal";
-import Label from "../../form/Label";
-import Input from "../../form/input/InputField";
-import { useModal } from "@/hooks/useModal";
-
-export default function FormInModal() {
- const { isOpen, openModal, closeModal } = useModal();
- const handleSave = () => {
- // Handle save logic here
- console.log("Saving changes...");
- closeModal();
- };
- return (
-
-
- Open Modal
-
-
-
-
-
- );
-}
diff --git a/src/components/example/ModalExample/FullScreenModal.tsx b/src/components/example/ModalExample/FullScreenModal.tsx
deleted file mode 100644
index b51f69b..0000000
--- a/src/components/example/ModalExample/FullScreenModal.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-"use client";
-import { useModal } from "@/hooks/useModal";
-import ComponentCard from "../../common/ComponentCard";
-
-import Button from "../../ui/button/Button";
-import { Modal } from "../../ui/modal";
-
-export default function FullScreenModal() {
- const {
- isOpen: isFullscreenModalOpen,
- openModal: openFullscreenModal,
- closeModal: closeFullscreenModal,
- } = useModal();
- const handleSave = () => {
- // Handle save logic here
- console.log("Saving changes...");
- closeFullscreenModal();
- };
- return (
-
-
- Open Modal
-
-
-
-
-
- Modal Heading
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra. Sed a
- ligula ac odio condimentum aliquet a nec nulla. Aliquam bibendum
- ex sit amet ipsum rutrum feugiat ultrices enim quam.
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra. Sed a
- ligula ac odio condimentum aliquet a nec nulla. Aliquam bibendum
- ex sit amet ipsum rutrum feugiat ultrices enim quam odio
- condimentum aliquet a nec nulla pellentesque euismod est quis
- mauris lacinia pharetra.
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra.
-
-
-
-
- Close
-
-
- Save Changes
-
-
-
-
-
- );
-}
diff --git a/src/components/example/ModalExample/ModalBasedAlerts.tsx b/src/components/example/ModalExample/ModalBasedAlerts.tsx
deleted file mode 100644
index 7a1c560..0000000
--- a/src/components/example/ModalExample/ModalBasedAlerts.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-
-import { Modal } from "../../ui/modal";
-import { useModal } from "@/hooks/useModal";
-
-export default function ModalBasedAlerts() {
- const successModal = useModal();
- const infoModal = useModal();
- const warningModal = useModal();
- const errorModal = useModal();
- return (
-
-
-
- Success Alert
-
-
- Info Alert
-
-
- Warning Alert
-
-
- Danger Alert
-
-
- {/* Success Modal */}
-
-
-
-
- Well Done!
-
-
- Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
- felis risus nisi non. Quisque eu ut tempor curabitur.
-
-
-
-
- Okay, Got It
-
-
-
-
- {/* Info Modal */}
-
-
-
-
-
- Information Alert!
-
-
- Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
- felis risus nisi non. Quisque eu ut tempor curabitur.
-
-
-
-
- Okay, Got It
-
-
-
-
- {/* Warning Modal */}
-
-
-
-
-
- Warning Alert!
-
-
- Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
- felis risus nisi non. Quisque eu ut tempor curabitur.
-
-
-
-
- Okay, Got It
-
-
-
-
- {/* Error Modal */}
-
-
-
-
-
- Danger Alert!
-
-
- Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
- felis risus nisi non. Quisque eu ut tempor curabitur.
-
-
-
-
- Okay, Got It
-
-
-
-
-
- );
-}
diff --git a/src/components/example/ModalExample/VerticallyCenteredModal.tsx b/src/components/example/ModalExample/VerticallyCenteredModal.tsx
deleted file mode 100644
index 2ec90a7..0000000
--- a/src/components/example/ModalExample/VerticallyCenteredModal.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Button from "../../ui/button/Button";
-import { Modal } from "../../ui/modal";
-import { useModal } from "@/hooks/useModal";
-
-export default function VerticallyCenteredModal() {
- const { isOpen, openModal, closeModal } = useModal();
- const handleSave = () => {
- // Handle save logic here
- console.log("Saving changes...");
- closeModal();
- };
- return (
-
-
- Open Modal
-
-
-
-
- All Done! Success Confirmed
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit.
- Pellentesque euismod est quis mauris lacinia pharetra.
-
-
-
-
- Close
-
-
- Save Changes
-
-
-
-
-
- );
-}
diff --git a/src/components/form/Form.tsx b/src/components/form/Form.tsx
deleted file mode 100644
index 0530a6a..0000000
--- a/src/components/form/Form.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { FC, ReactNode, FormEvent } from "react";
-
-interface FormProps {
- onSubmit: (event: FormEvent) => void;
- children: ReactNode;
- className?: string;
-}
-
-const Form: FC = ({ onSubmit, children, className }) => {
- return (
-
- );
-};
-
-export default Form;
diff --git a/src/components/form/MultiSelect.tsx b/src/components/form/MultiSelect.tsx
deleted file mode 100644
index 49651b5..0000000
--- a/src/components/form/MultiSelect.tsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import React, { useState } from "react";
-
-interface Option {
- value: string;
- text: string;
- selected: boolean;
-}
-
-interface MultiSelectProps {
- label: string;
- options: Option[];
- defaultSelected?: string[];
- onChange?: (selected: string[]) => void;
- disabled?: boolean;
-}
-
-const MultiSelect: React.FC = ({
- label,
- options,
- defaultSelected = [],
- onChange,
- disabled = false,
-}) => {
- const [selectedOptions, setSelectedOptions] =
- useState(defaultSelected);
- const [isOpen, setIsOpen] = useState(false);
-
- const toggleDropdown = () => {
- if (disabled) return;
- setIsOpen((prev) => !prev);
- };
-
- const handleSelect = (optionValue: string) => {
- const newSelectedOptions = selectedOptions.includes(optionValue)
- ? selectedOptions.filter((value) => value !== optionValue)
- : [...selectedOptions, optionValue];
-
- setSelectedOptions(newSelectedOptions);
- if (onChange) onChange(newSelectedOptions);
- };
-
- const removeOption = (index: number, value: string) => {
- const newSelectedOptions = selectedOptions.filter((opt) => opt !== value);
- setSelectedOptions(newSelectedOptions);
- if (onChange) onChange(newSelectedOptions);
- };
-
- const selectedValuesText = selectedOptions.map(
- (value) => options.find((option) => option.value === value)?.text || ""
- );
-
- return (
-
-
- {label}
-
-
-
-
-
-
-
- {selectedValuesText.length > 0 ? (
- selectedValuesText.map((text, index) => (
-
-
{text}
-
-
- removeOption(index, selectedOptions[index])
- }
- className="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
- >
-
-
-
-
-
-
- ))
- ) : (
-
- )}
-
-
-
-
-
- {isOpen && (
-
e.stopPropagation()}
- >
-
- {options.map((option, index) => (
-
-
handleSelect(option.value)}
- >
-
-
-
- ))}
-
-
- )}
-
-
-
- );
-};
-
-export default MultiSelect;
diff --git a/src/components/form/Select.tsx b/src/components/form/Select.tsx
deleted file mode 100644
index b32c839..0000000
--- a/src/components/form/Select.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useState } from "react";
-
-interface Option {
- value: string;
- label: string;
-}
-
-interface SelectProps {
- options: Option[];
- placeholder?: string;
- onChange: (value: string) => void;
- className?: string;
- defaultValue?: string;
-}
-
-const Select: React.FC = ({
- options,
- placeholder = "Select an option",
- onChange,
- className = "",
- defaultValue = "",
-}) => {
- // Manage the selected value
- const [selectedValue, setSelectedValue] = useState(defaultValue);
-
- const handleChange = (e: React.ChangeEvent) => {
- const value = e.target.value;
- setSelectedValue(value);
- onChange(value); // Trigger parent handler
- };
-
- return (
-
- {/* Placeholder option */}
-
- {placeholder}
-
- {/* Map over options */}
- {options.map((option) => (
-
- {option.label}
-
- ))}
-
- );
-};
-
-export default Select;
diff --git a/src/components/form/date-picker.tsx b/src/components/form/date-picker.tsx
deleted file mode 100644
index 9cb9f17..0000000
--- a/src/components/form/date-picker.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useEffect } from 'react';
-import flatpickr from 'flatpickr';
-import 'flatpickr/dist/flatpickr.css';
-import Label from './Label';
-import { CalenderIcon } from '../../icons';
-import Hook = flatpickr.Options.Hook;
-import DateOption = flatpickr.Options.DateOption;
-
-type PropsType = {
- id: string;
- mode?: "single" | "multiple" | "range" | "time";
- onChange?: Hook | Hook[];
- defaultDate?: DateOption;
- label?: string;
- placeholder?: string;
-};
-
-export default function DatePicker({
- id,
- mode,
- onChange,
- label,
- defaultDate,
- placeholder,
-}: PropsType) {
- useEffect(() => {
- const flatPickr = flatpickr(`#${id}`, {
- mode: mode || "single",
- static: true,
- monthSelectorType: "static",
- dateFormat: "Y-m-d",
- defaultDate,
- onChange,
- });
-
- return () => {
- if (!Array.isArray(flatPickr)) {
- flatPickr.destroy();
- }
- };
- }, [mode, onChange, id, defaultDate]);
-
- return (
-
- {label &&
{label} }
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/CheckboxComponents.tsx b/src/components/form/form-elements/CheckboxComponents.tsx
deleted file mode 100644
index 6bf83ce..0000000
--- a/src/components/form/form-elements/CheckboxComponents.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Checkbox from "../input/Checkbox";
-
-export default function CheckboxComponents() {
- const [isChecked, setIsChecked] = useState(false);
- const [isCheckedTwo, setIsCheckedTwo] = useState(true);
- const [isCheckedDisabled, setIsCheckedDisabled] = useState(false);
- return (
-
-
-
-
-
- Default
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/DefaultInputs.tsx b/src/components/form/form-elements/DefaultInputs.tsx
deleted file mode 100644
index 36cc77e..0000000
--- a/src/components/form/form-elements/DefaultInputs.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-"use client";
-import React, { useState } from 'react';
-import ComponentCard from '../../common/ComponentCard';
-import Label from '../Label';
-import Input from '../input/InputField';
-import Select from '../Select';
-import { ChevronDownIcon, EyeCloseIcon, EyeIcon, TimeIcon } from '../../../icons';
-import DatePicker from '@/components/form/date-picker';
-
-export default function DefaultInputs() {
- const [showPassword, setShowPassword] = useState(false);
- const options = [
- { value: "marketing", label: "Marketing" },
- { value: "template", label: "Template" },
- { value: "development", label: "Development" },
- ];
- const handleSelectChange = (value: string) => {
- console.log("Selected value:", value);
- };
- return (
-
-
-
- Input
-
-
-
- Input with Placeholder
-
-
-
-
Select Input
-
-
-
-
-
-
-
-
-
Password Input
-
-
- setShowPassword(!showPassword)}
- className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
- >
- {showPassword ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {
- // Handle your logic
- console.log({ dates, currentDateString });
- }}
- />
-
-
-
-
Time Picker Input
-
- console.log(e.target.value)}
- />
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/DropZone.tsx b/src/components/form/form-elements/DropZone.tsx
deleted file mode 100644
index 2fd8bb2..0000000
--- a/src/components/form/form-elements/DropZone.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import { useDropzone } from "react-dropzone";
-
-const DropzoneComponent: React.FC = () => {
- const onDrop = (acceptedFiles: File[]) => {
- console.log("Files dropped:", acceptedFiles);
- // Handle file uploads here
- };
-
- const { getRootProps, getInputProps, isDragActive } = useDropzone({
- onDrop,
- accept: {
- "image/png": [],
- "image/jpeg": [],
- "image/webp": [],
- "image/svg+xml": [],
- },
- });
- return (
-
-
-
- );
-};
-
-export default DropzoneComponent;
diff --git a/src/components/form/form-elements/FileInputExample.tsx b/src/components/form/form-elements/FileInputExample.tsx
deleted file mode 100644
index 2c96dc7..0000000
--- a/src/components/form/form-elements/FileInputExample.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import FileInput from "../input/FileInput";
-import Label from "../Label";
-
-export default function FileInputExample() {
- const handleFileChange = (event: React.ChangeEvent) => {
- const file = event.target.files?.[0];
- if (file) {
- console.log("Selected file:", file.name);
- }
- };
-
- return (
-
-
- Upload file
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/InputGroup.tsx b/src/components/form/form-elements/InputGroup.tsx
deleted file mode 100644
index cefd061..0000000
--- a/src/components/form/form-elements/InputGroup.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Label from "../Label";
-import Input from "../input/InputField";
-import { EnvelopeIcon } from "../../../icons";
-import PhoneInput from "../group-input/PhoneInput";
-
-export default function InputGroup() {
- const countries = [
- { code: "US", label: "+1" },
- { code: "GB", label: "+44" },
- { code: "CA", label: "+1" },
- { code: "AU", label: "+61" },
- ];
- const handlePhoneNumberChange = (phoneNumber: string) => {
- console.log("Updated phone number:", phoneNumber);
- };
- return (
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/InputStates.tsx b/src/components/form/form-elements/InputStates.tsx
deleted file mode 100644
index 3ef08b5..0000000
--- a/src/components/form/form-elements/InputStates.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Input from "../input/InputField";
-import Label from "../Label";
-
-export default function InputStates() {
- const [email, setEmail] = useState("");
- const [error, setError] = useState(false);
-
- // Simulate a validation check
- const validateEmail = (value: string) => {
- const isValidEmail =
- /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value);
- setError(!isValidEmail);
- return isValidEmail;
- };
-
- const handleEmailChange = (e: React.ChangeEvent) => {
- const value = e.target.value;
- setEmail(value);
- validateEmail(value);
- };
- return (
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/RadioButtons.tsx b/src/components/form/form-elements/RadioButtons.tsx
deleted file mode 100644
index d7ec327..0000000
--- a/src/components/form/form-elements/RadioButtons.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Radio from "../input/Radio";
-
-export default function RadioButtons() {
- const [selectedValue, setSelectedValue] = useState("option2");
-
- const handleRadioChange = (value: string) => {
- setSelectedValue(value);
- };
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/SelectInputs.tsx b/src/components/form/form-elements/SelectInputs.tsx
deleted file mode 100644
index e19a863..0000000
--- a/src/components/form/form-elements/SelectInputs.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Label from "../Label";
-import Select from "../Select";
-import MultiSelect from "../MultiSelect";
-import { ChevronDownIcon } from "@/icons";
-
-export default function SelectInputs() {
- const options = [
- { value: "marketing", label: "Marketing" },
- { value: "template", label: "Template" },
- { value: "development", label: "Development" },
- ];
-
- const [selectedValues, setSelectedValues] = useState([]);
-
- const handleSelectChange = (value: string) => {
- console.log("Selected value:", value);
- };
-
- const multiOptions = [
- { value: "1", text: "Option 1", selected: false },
- { value: "2", text: "Option 2", selected: false },
- { value: "3", text: "Option 3", selected: false },
- { value: "4", text: "Option 4", selected: false },
- { value: "5", text: "Option 5", selected: false },
- ];
-
- return (
-
-
-
-
Select Input
-
-
-
-
-
-
-
-
-
setSelectedValues(values)}
- />
-
- Selected Values: {selectedValues.join(", ")}
-
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/TextAreaInput.tsx b/src/components/form/form-elements/TextAreaInput.tsx
deleted file mode 100644
index 692e4bf..0000000
--- a/src/components/form/form-elements/TextAreaInput.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-"use client";
-import React, { useState } from "react";
-import ComponentCard from "../../common/ComponentCard";
-import TextArea from "../input/TextArea";
-import Label from "../Label";
-
-export default function TextAreaInput() {
- const [message, setMessage] = useState("");
- const [messageTwo, setMessageTwo] = useState("");
- return (
-
-
- {/* Default TextArea */}
-
- Description
-
-
- {/* Disabled TextArea */}
-
- Description
-
-
-
- {/* Error TextArea */}
-
- Description
-
-
-
- );
-}
diff --git a/src/components/form/form-elements/ToggleSwitch.tsx b/src/components/form/form-elements/ToggleSwitch.tsx
deleted file mode 100644
index 8f7b28a..0000000
--- a/src/components/form/form-elements/ToggleSwitch.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-"use client";
-import React from "react";
-import ComponentCard from "../../common/ComponentCard";
-import Switch from "../switch/Switch";
-
-export default function ToggleSwitch() {
- const handleSwitchChange = (checked: boolean) => {
- console.log("Switch is now:", checked ? "ON" : "OFF");
- };
- return (
-
-
-
-
-
-
{" "}
-
-
-
-
-
-
- );
-}
diff --git a/src/components/form/group-input/PhoneInput.tsx b/src/components/form/group-input/PhoneInput.tsx
deleted file mode 100644
index 1a481ce..0000000
--- a/src/components/form/group-input/PhoneInput.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-"use client";
-import React, { useState } from "react";
-
-interface CountryCode {
- code: string;
- label: string;
-}
-
-interface PhoneInputProps {
- countries: CountryCode[];
- placeholder?: string;
- onChange?: (phoneNumber: string) => void;
- selectPosition?: "start" | "end"; // New prop for dropdown position
-}
-
-const PhoneInput: React.FC = ({
- countries,
- placeholder = "+1 (555) 000-0000",
- onChange,
- selectPosition = "start", // Default position is 'start'
-}) => {
- const [selectedCountry, setSelectedCountry] = useState("US");
- const [phoneNumber, setPhoneNumber] = useState("+1");
-
- const countryCodes: Record = countries.reduce(
- (acc, { code, label }) => ({ ...acc, [code]: label }),
- {}
- );
-
- const handleCountryChange = (e: React.ChangeEvent) => {
- const newCountry = e.target.value;
- setSelectedCountry(newCountry);
- setPhoneNumber(countryCodes[newCountry]);
- if (onChange) {
- onChange(countryCodes[newCountry]);
- }
- };
-
- const handlePhoneNumberChange = (e: React.ChangeEvent) => {
- const newPhoneNumber = e.target.value;
- setPhoneNumber(newPhoneNumber);
- if (onChange) {
- onChange(newPhoneNumber);
- }
- };
-
- return (
-
- {/* Dropdown position: Start */}
- {selectPosition === "start" && (
-
-
- {countries.map((country) => (
-
- {country.code}
-
- ))}
-
-
-
- )}
-
- {/* Input field */}
-
-
- {/* Dropdown position: End */}
- {selectPosition === "end" && (
-
-
- {countries.map((country) => (
-
- {country.code}
-
- ))}
-
-
-
- )}
-
- );
-};
-
-export default PhoneInput;
diff --git a/src/components/header/UserDropdown.tsx b/src/components/header/UserDropdown.tsx
index 8421af8..7de31aa 100644
--- a/src/components/header/UserDropdown.tsx
+++ b/src/components/header/UserDropdown.tsx
@@ -119,7 +119,7 @@ export default function UserDropdown() {
void;
- onViewDetail: (app: Application) => void;
- sortBy?: AppSortColumn;
- sortOrder?: "asc" | "desc";
-}
-
-export default function ApplicationTable({
- data,
- onSort,
- onViewDetail,
- sortBy,
- sortOrder,
-}: ApplicationTableProps) {
- const formatDate = (dateString: string | null | undefined) => {
- if (!dateString) return "-";
- const date = new Date(dateString);
- return date.toLocaleDateString("vi-VN", {
- day: "2-digit",
- month: "2-digit",
- year: "numeric",
- hour: "2-digit",
- minute: "2-digit",
- });
- };
-
- const SortIcon = ({ column }: { column: AppSortColumn }) => {
- const isActive = sortBy === column;
- return (
-
- );
- };
-
- const getStatusBadge = (status: string | number) => {
- const s = status?.toString();
- switch (s) {
- case "1":
- case "PENDING":
- return (
-
- Đang chờ
-
- );
- case "2":
- case "APPROVED":
- return (
-
- Đã duyệt
-
- );
- case "3":
- case "REJECTED":
- return (
-
- Từ chối
-
- );
- default:
- return (
-
- {status || "N/A"}
-
- );
- }
- };
-
- const renderVerifyTypes = (
- verifyType: string | string[] | number | number[],
- ) => {
- const typeMap: Record = {
- "1": "Thẻ nhận dạng nhà nghiên cứu",
- ID_CARD: "Thẻ nhận dạng nhà nghiên cứu",
- "2": "Bằng cấp",
- EDUCATION: "Bằng cấp",
- "3": "Chuyên gia",
- EXPERT: "Chuyên gia",
- "4": "Khác",
- OTHER: "Khác",
- };
-
- const typesArray = Array.isArray(verifyType) ? verifyType : [verifyType];
-
- return (
-
- {typesArray.map((type, index) => {
- const t = type?.toString();
- if (!t) return null;
- return (
-
- {typeMap[t] || t}
-
- );
- })}
-
- );
- };
-
- return (
-
-
-
-
-
-
-
- Người gửi (ID)
-
-
- Loại xác minh
-
-
- Đính kèm
-
-
- onSort("status")}
- >
- Trạng thái
-
-
-
- onSort("created_at")}
- >
- Ngày nộp
-
-
-
- onSort("reviewed_at")}
- >
- Cập nhật
-
-
-
- Cập nhật bởi
-
-
- Ghi chú
-
-
- Thao tác
-
-
-
-
-
- {data.length > 0 ? (
- data.map((app) => (
-
-
- {app.user.display_name}
-
-
- {renderVerifyTypes(app.verify_type)}
-
-
-
- {app.media?.length || 0}
-
-
-
- {getStatusBadge(app.status)}
-
-
- {formatDate(app.created_at)}
-
-
- {formatDate(app.reviewed_at)}
-
-
- {app.reviewer?.display_name || "-"}
-
-
- {app.review_note || "-"}
-
- {app.review_note && (
-
- )}
-
-
- onViewDetail(app)}
- className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
- >
- Chi tiết
-
-
-
- ))
- ) : (
-
-
- Không tìm thấy dữ liệu hồ sơ
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/src/components/tables/BasicTableOne.tsx b/src/components/tables/BasicTableOne.tsx
deleted file mode 100644
index ec7b9e0..0000000
--- a/src/components/tables/BasicTableOne.tsx
+++ /dev/null
@@ -1,293 +0,0 @@
-"use client";
-
-import {
- Table,
- TableBody,
- TableCell,
- TableHeader,
- TableRow,
-} from "../ui/table";
-import Badge from "../ui/badge/Badge";
-import Image from "next/image";
-import { fullDataUser } from "@/interface/admin";
-
-type SortColumn = "created_at" | "updated_at" | "display_name" | "email";
-
-interface Role {
- id: string;
- name: string;
-}
-
-interface BasicTableOneProps {
- data: fullDataUser[];
- onSort: (column: SortColumn) => void;
- onViewDetail: (user: fullDataUser) => void;
- sortBy?: SortColumn;
- sortOrder?: "asc" | "desc";
- onFilterRole?: (role: string) => void;
- selectedRole?: string;
- roles?: Role[];
-}
-
-export default function BasicTableOne({
- data,
- onSort,
- onViewDetail,
- sortBy,
- sortOrder,
- onFilterRole,
- selectedRole,
- roles = [],
-}: BasicTableOneProps) {
- const formatDate = (dateString: string) => {
- if (!dateString) return "-";
- const date = new Date(dateString);
- return date.toLocaleDateString("en-GB", {
- day: "2-digit",
- month: "short",
- year: "numeric",
- });
- };
-
- const SortIcon = ({ column }: { column: SortColumn }) => {
- const isActive = sortBy === column;
- return (
-
- );
- };
-
- return (
-
-
-
-
-
-
-
- onSort("display_name")}
- >
- Người dùng
-
-
-
-
- onSort("email")}
- >
- Email
-
-
-
-
-
-
onFilterRole?.(e.target.value)}
- className="bg-transparent border-none outline-none cursor-pointer appearance-none text-gray-500 dark:text-gray-400 pr-5 hover:text-brand-500 transition-colors font-medium"
- >
- Vai trò (Tất cả)
- {roles.map((role) => (
-
- {role.name}
-
- ))}
-
-
-
-
-
-
-
-
- Trạng thái
-
-
-
- onSort("created_at")}
- >
- Ngày tham gia
-
-
-
-
- onSort("updated_at")}
- >
- Cập nhật
-
-
-
-
- Thao tác
-
-
-
-
-
- {data.length > 0 ? (
- data.map((user) => (
-
-
-
-
- {user.profile?.avatar_url ? (
-
- ) : (
-
- {user.profile?.display_name?.charAt(0) || "U"}
-
- )}
-
-
-
- {user.profile?.display_name || "N/A"}
-
-
- ID: {user.id.slice(0, 8)}...
-
-
-
-
-
-
- {user.email}
-
-
-
-
- {user.roles?.map((role) => (
-
- {role.name}
-
- )) || (
-
- No Role
-
- )}
-
-
-
-
-
- {user.is_deleted ? "Bị khóa" : "Hoạt động"}
-
-
-
-
- {formatDate(user.created_at)}
-
-
- {formatDate(user.updated_at)}
-
-
-
- onViewDetail(user)}
- className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
- >
- Chi tiết
-
-
-
- ))
- ) : (
-
-
-
-
- Không tìm thấy dữ liệu người dùng
-
-
-
-
- )}
-
-
-
-
-
- );
-}
diff --git a/src/components/tables/ChangeRoleModal.tsx b/src/components/tables/ChangeRoleModal.tsx
deleted file mode 100644
index 9b37043..0000000
--- a/src/components/tables/ChangeRoleModal.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-"use client";
-import { useEffect, useState } from "react";
-import { Modal } from "../ui/modal";
-import Button from "../ui/button/Button";
-import { fullDataUser } from "@/interface/admin";
-import { apiGetAllRole, apiChangeRole } from "@/service/adminService";
-import { toast } from "sonner";
-
-interface Role {
- id: string;
- name: string;
-}
-
-interface ChangeRoleModalProps {
- isOpen: boolean;
- onClose: () => void;
- user: fullDataUser | null;
- onSuccess: () => void;
-}
-
-const DEFAULT_ROLE_NAME = "USER";
-
-export default function ChangeRoleModal({ isOpen, onClose, user, onSuccess }: ChangeRoleModalProps) {
- const [roles, setRoles] = useState([]);
- const [selectedRoleIds, setSelectedRoleIds] = useState([]);
- const [loading, setLoading] = useState(false);
- const [fetchingRoles, setFetchingRoles] = useState(true);
-
- useEffect(() => {
- if (isOpen && user) {
- setFetchingRoles(true);
- apiGetAllRole()
- .then((res) => {
- if (res?.status) setRoles(res.data);
- })
- .catch((err) => {
- console.error("Lỗi fetch roles:", err);
- toast.error("Không thể lấy danh sách vai trò");
- })
- .finally(() => setFetchingRoles(false));
-
- const currentUserRoles = user.roles?.map((r) => r.id) || [];
- setSelectedRoleIds(currentUserRoles);
- }
- }, [isOpen, user]);
-
- const handleToggleRole = (roleId: string, isDefault: boolean) => {
- if (isDefault) return;
-
- setSelectedRoleIds((prev) =>
- prev.includes(roleId)
- ? prev.filter((id) => id !== roleId)
- : [...prev, roleId]
- );
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- if (!user) return;
-
- try {
- setLoading(true);
-
- const payload = {
- role_ids: selectedRoleIds,
- user_id: user.id,
- };
-
- await apiChangeRole(user.id, payload);
- toast.success("Cập nhật vai trò thành công!");
- onSuccess();
- onClose();
- } catch (error) {
- console.error(error);
- toast.error("Lỗi khi cập nhật vai trò!");
- } finally {
- setLoading(false);
- }
- };
-
- if (!user) return null;
-
- return (
-
-
-
-
-
- Vai trò người dùng
-
-
- {user.profile?.display_name || user.email}
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/tables/UserDetailModal.tsx b/src/components/tables/UserDetailModal.tsx
deleted file mode 100644
index 380f695..0000000
--- a/src/components/tables/UserDetailModal.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-"use client";
-import { Modal } from "../ui/modal";
-import UserMetaCard from "@/components/user-profile/UserMetaCard";
-import UserInfoCard from "@/components/user-profile/UserInfoCard";
-import { UserMetaCardProps } from "@/interface/user";
-
-import { fullDataUser } from "@/interface/admin";
-import { useEffect, useState } from "react";
-import { MediaDto } from "@/interface/media";
-import { apiGetUserMedia } from "@/service/adminService";
-import MediaCard from "@/components/user-profile/Media";
-
-interface UserDetailModalProps {
- isOpen: boolean;
- onClose: () => void;
- user: fullDataUser | null;
- onChangeRole: (user: fullDataUser) => void;
- onDelete: (user: fullDataUser) => void;
- onRestore: (user: fullDataUser) => void;
-}
-
-export default function UserDetailModal({
- isOpen,
- onClose,
- user,
- onChangeRole,
- onDelete,
- onRestore,
-}: UserDetailModalProps) {
- const [mediaData, setMediaData] = useState(null);
- const [loading, setLoading] = useState(true);
-
- const formattedData: UserMetaCardProps = {
- data: user
- ? {
- id: user.id,
- email: user.email,
- profile: user.profile,
- roles: user.roles as any, // UserRole and Role are slightly different, need a better fix or small cast
- }
- : undefined,
- };
-
-
- useEffect(() => {
- if (user?.id && isOpen) {
- const fetchUserMedia = async () => {
- setLoading(true);
- try {
- const mediaResponse = await apiGetUserMedia(user.id);
- setMediaData(mediaResponse);
- } catch (err) {
- console.error("Lỗi fetch media:", err);
- setMediaData(null);
- } finally {
- setLoading(false);
- }
- };
- fetchUserMedia();
- }
- }, [user?.id, isOpen]);
-
- if (!user) return null;
-
- return (
-
-
-
-
- Chi tiết người dùng
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {loading ? (
-
-
-
Đang tải tài liệu...
-
- ) : (
- <>
- {(mediaData?.data?.length ?? 0) > 0 ? (
-
- ) : (
-
- Người dùng này chưa có dữ liệu media.
-
- )}
- >
- )}
-
-
-
-
-
- Thao tác quản trị viên
-
-
- onChangeRole(user)}
- className="px-4 py-2 text-sm font-medium text-purple-600 bg-purple-50 rounded-lg hover:bg-purple-100 dark:bg-purple-500/10 dark:text-purple-400 dark:hover:bg-purple-500/20 transition-colors"
- >
- Đổi vai trò
-
-
- {user.is_deleted ? (
- onRestore(user)}
- className="px-4 py-2 text-sm font-medium text-green-600 bg-green-50 rounded-lg hover:bg-green-100 dark:bg-green-500/10 dark:text-green-400 dark:hover:bg-green-500/20 transition-colors"
- >
- Khôi phục
-
- ) : (
- onDelete(user)}
- className="px-4 py-2 text-sm font-medium text-red-600 bg-red-50 rounded-lg hover:bg-red-100 dark:bg-red-500/10 dark:text-red-400 dark:hover:bg-red-500/20 transition-colors"
- >
- Khóa / Xóa
-
- )}
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/src/components/ui/alert/Alert.tsx b/src/components/ui/alert/Alert.tsx
deleted file mode 100644
index 352e14b..0000000
--- a/src/components/ui/alert/Alert.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import Link from "next/link";
-import React from "react";
-
-interface AlertProps {
- variant: "success" | "error" | "warning" | "info"; // Alert type
- title: string; // Title of the alert
- message: string; // Message of the alert
- showLink?: boolean; // Whether to show the "Learn More" link
- linkHref?: string; // Link URL
- linkText?: string; // Link text
-}
-
-const Alert: React.FC = ({
- variant,
- title,
- message,
- showLink = false,
- linkHref = "#",
- linkText = "Learn more",
-}) => {
- // Tailwind classes for each variant
- const variantClasses = {
- success: {
- container:
- "border-success-500 bg-success-50 dark:border-success-500/30 dark:bg-success-500/15",
- icon: "text-success-500",
- },
- error: {
- container:
- "border-error-500 bg-error-50 dark:border-error-500/30 dark:bg-error-500/15",
- icon: "text-error-500",
- },
- warning: {
- container:
- "border-warning-500 bg-warning-50 dark:border-warning-500/30 dark:bg-warning-500/15",
- icon: "text-warning-500",
- },
- info: {
- container:
- "border-blue-light-500 bg-blue-light-50 dark:border-blue-light-500/30 dark:bg-blue-light-500/15",
- icon: "text-blue-light-500",
- },
- };
-
- // Icon for each variant
- const icons = {
- success: (
-
-
-
- ),
- error: (
-
-
-
- ),
- warning: (
-
-
-
- ),
- info: (
-
-
-
- ),
- };
-
- return (
-
-
-
- {icons[variant]}
-
-
-
-
- {title}
-
-
-
{message}
-
- {showLink && (
-
- {linkText}
-
- )}
-
-
-
- );
-};
-
-export default Alert;
diff --git a/src/components/ui/avatar/AvatarText.tsx b/src/components/ui/avatar/AvatarText.tsx
deleted file mode 100644
index 5a3e101..0000000
--- a/src/components/ui/avatar/AvatarText.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import React from "react";
-
-interface AvatarTextProps {
- name: string;
- className?: string;
-}
-
-const AvatarText: React.FC = ({ name, className = "" }) => {
- // Generate initials from name
- const initials = name
- .split(" ")
- .map((word) => word[0])
- .join("")
- .toUpperCase()
- .slice(0, 2);
-
- // Generate a consistent pastel color based on the name
- const getColorClass = (name: string) => {
- const colors = [
- "bg-brand-100 text-brand-600",
- "bg-pink-100 text-pink-600",
- "bg-cyan-100 text-cyan-600",
- "bg-orange-100 text-orange-600",
- "bg-green-100 text-green-600",
- "bg-purple-100 text-purple-600",
- "bg-yellow-100 text-yellow-600",
- "bg-error-100 text-error-600",
- ];
-
- const index = name
- .split("")
- .reduce((acc, char) => acc + char.charCodeAt(0), 0);
- return colors[index % colors.length];
- };
-
- return (
-
- {initials}
-
- );
-};
-
-export default AvatarText;
diff --git a/src/components/ui/chat/ChatbotWidget.tsx b/src/components/ui/chat/ChatbotWidget.tsx
index 6f57656..797fdd6 100644
--- a/src/components/ui/chat/ChatbotWidget.tsx
+++ b/src/components/ui/chat/ChatbotWidget.tsx
@@ -2,16 +2,20 @@
import React, { useState, useRef, useEffect } from "react";
import { ChatbotPayload } from "@/interface/chatbot";
-import { apiChatbot } from "@/service/chatbotService";
+import { apiChatbot, apiChatbotHistory } from "@/service/chatbotService";
import { AxiosError } from "axios";
-
type Message = {
id: string;
sender: "user" | "bot";
text: string;
};
+const cleanAnswer = (text: string) => {
+ if (!text) return "";
+ return text.replace(/<\/?answer>/gi, "").trim();
+};
+
export default function ChatbotWidget({
projectId = "",
hideFloatingButton = false,
@@ -20,21 +24,22 @@ export default function ChatbotWidget({
hideFloatingButton?: boolean;
}) {
const [isOpen, setIsOpen] = useState(false);
- const [messages, setMessages] = useState([
- {
- 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 [messages, setMessages] = useState([]);
+ const [preCursor, setPreCursor] = useState(null);
+ const [isFetchingHistory, setIsFetchingHistory] = useState(false);
+ const [hasLoadedHistory, setHasLoadedHistory] = useState(false);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
+
const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+ const prevMessagesLength = useRef(0);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
+ // Toggle chatbot visibility via window event
useEffect(() => {
const handleToggle = () => setIsOpen((prev) => !prev);
window.addEventListener("toggle-chatbot", handleToggle);
@@ -43,19 +48,83 @@ export default function ChatbotWidget({
};
}, []);
+ // Fetch history from API
+ const loadHistory = async (cursor?: string) => {
+ setIsFetchingHistory(true);
+ try {
+ const res = await apiChatbotHistory({ cursor, limit: 10 });
+ if (res && res.status && res.data) {
+ const historyItems = res.data.items || [];
+ const newMessages: Message[] = [];
+
+ // Parse questions and answers from history items
+ historyItems.forEach((item: any) => {
+ newMessages.push({
+ id: `${item.id}_q`,
+ sender: "user",
+ text: item.question,
+ });
+ newMessages.push({
+ id: `${item.id}_a`,
+ sender: "bot",
+ text: cleanAnswer(item.answer),
+ });
+ });
+
+ if (cursor) {
+ // Prepended older messages should go to the top of the conversation list
+ setMessages((prev) => [...newMessages, ...prev]);
+ } else {
+ setMessages(newMessages);
+ setHasLoadedHistory(true);
+ }
+
+ setPreCursor(res.data.pre_cursor || null);
+ }
+ } catch (error) {
+ console.error("Failed to load chat history:", error);
+ } finally {
+ setIsFetchingHistory(false);
+ }
+ };
+
+ // Load history when chatbot is first opened
+ useEffect(() => {
+ if (isOpen && !hasLoadedHistory) {
+ loadHistory();
+ }
+ }, [isOpen, hasLoadedHistory]);
+
+ // Scroll to bottom when first opened
useEffect(() => {
if (isOpen) {
scrollToBottom();
}
- }, [messages, isOpen]);
+ }, [isOpen]);
+
+ // Scroll to bottom only when new live messages are added at the end (not when older history is prepended)
+ useEffect(() => {
+ if (messages.length > prevMessagesLength.current) {
+ scrollToBottom();
+ }
+ prevMessagesLength.current = messages.length;
+ }, [messages]);
+
+ // Auto focus input when opened or loading ends
+ useEffect(() => {
+ if (isOpen && !isLoading) {
+ inputRef.current?.focus();
+ }
+ }, [isOpen, isLoading]);
const handleSend = async () => {
- if (!input.trim()) return;
+ if (!input.trim() || isLoading) return;
+ const userText = input.trim();
const userMessage: Message = {
id: Date.now().toString(),
sender: "user",
- text: input.trim(),
+ text: userText,
};
setMessages((prev) => [...prev, userMessage]);
@@ -65,7 +134,7 @@ export default function ChatbotWidget({
try {
const payload: ChatbotPayload = {
project_id: projectId,
- question: userMessage.text,
+ question: userText,
};
const res = await apiChatbot(payload);
@@ -74,7 +143,7 @@ export default function ChatbotWidget({
id: (Date.now() + 1).toString(),
sender: "bot",
text: res?.status
- ? res?.data
+ ? cleanAnswer(res?.data)
: "Xin lỗi, tôi không thể trả lời lúc này.",
};
@@ -90,7 +159,6 @@ export default function ChatbotWidget({
};
setMessages((prev) => [...prev, errorMessage]);
} finally {
-
setIsLoading(false);
}
};
@@ -101,6 +169,14 @@ export default function ChatbotWidget({
}
};
+ const initMessage: Message = {
+ 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 allMessages = [initMessage, ...messages];
+
return (
{!isOpen && !hideFloatingButton && (
@@ -146,11 +222,11 @@ export default function ChatbotWidget({
d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5"
/>
- Trợ lý lịch sử.
+ Trợ lý lịch sử
setIsOpen(false)}
- className="text-white hover:text-gray-200 transition-colors"
+ className="p-1.5 hover:bg-brand-600 rounded-lg transition-colors text-white"
>
- {messages.map((msg) => (
+ {/* Load older messages button */}
+ {preCursor && (
+
+ loadHistory(preCursor)}
+ disabled={isFetchingHistory}
+ className="text-xs text-brand-500 hover:text-brand-600 font-medium py-1 px-3 bg-brand-50 dark:bg-brand-950/20 rounded-full transition-colors disabled:opacity-50"
+ >
+ {isFetchingHistory ? "Đang tải..." : "Xem tin nhắn cũ hơn"}
+
+
+ )}
+
+ {allMessages.map((msg) => (
{msg.text}
))}
+
{isLoading && (
@@ -196,6 +286,13 @@ export default function ChatbotWidget({
>
)}
+
+ {isFetchingHistory && !preCursor && (
+
+ )}
+
@@ -203,6 +300,7 @@ export default function ChatbotWidget({
- );
-}
diff --git a/src/components/ui/images/ThreeColumnImageGrid.tsx b/src/components/ui/images/ThreeColumnImageGrid.tsx
deleted file mode 100644
index 780c0b5..0000000
--- a/src/components/ui/images/ThreeColumnImageGrid.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import Image from "next/image";
-import React from "react";
-
-export default function ThreeColumnImageGrid() {
- return (
-
- );
-}
diff --git a/src/components/ui/images/TwoColumnImageGrid.tsx b/src/components/ui/images/TwoColumnImageGrid.tsx
deleted file mode 100644
index ce740b5..0000000
--- a/src/components/ui/images/TwoColumnImageGrid.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import Image from "next/image";
-import React from "react";
-
-export default function TwoColumnImageGrid() {
- return (
-
- );
-}
diff --git a/src/components/ui/video/VideosExample.tsx b/src/components/ui/video/VideosExample.tsx
deleted file mode 100644
index 967d7cc..0000000
--- a/src/components/ui/video/VideosExample.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from "react";
-import YouTubeEmbed from "./YouTubeEmbed";
-import ComponentCard from "@/components/common/ComponentCard";
-
-export default function VideosExample() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ui/video/YouTubeEmbed.tsx b/src/components/ui/video/YouTubeEmbed.tsx
deleted file mode 100644
index 7dddb9c..0000000
--- a/src/components/ui/video/YouTubeEmbed.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from "react";
-
-type AspectRatio = "16:9" | "4:3" | "21:9" | "1:1";
-
-interface YouTubeEmbedProps {
- videoId: string;
- aspectRatio?: AspectRatio;
- title?: string;
- className?: string;
-}
-
-const YouTubeEmbed: React.FC
= ({
- videoId,
- aspectRatio = "16:9",
- title = "YouTube video",
- className = "",
-}) => {
- const aspectRatioClass = {
- "16:9": "aspect-video",
- "4:3": "aspect-4/3",
- "21:9": "aspect-21/9",
- "1:1": "aspect-square",
- }[aspectRatio];
-
- return (
-
- VIDEO
-
- );
-};
-
-export default YouTubeEmbed;
diff --git a/src/components/videos/FourIsToThree.tsx b/src/components/videos/FourIsToThree.tsx
deleted file mode 100644
index 0088974..0000000
--- a/src/components/videos/FourIsToThree.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-
-export default function FourIsToThree() {
- return (
-
- VIDEO
-
- );
-}
diff --git a/src/components/videos/OneIsToOne.tsx b/src/components/videos/OneIsToOne.tsx
deleted file mode 100644
index 68fcc46..0000000
--- a/src/components/videos/OneIsToOne.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-
-export default function OneIsToOne() {
- return (
-
- VIDEO
-
- );
-}
diff --git a/src/components/videos/SixteenIsToNine.tsx b/src/components/videos/SixteenIsToNine.tsx
deleted file mode 100644
index 0685597..0000000
--- a/src/components/videos/SixteenIsToNine.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-
-export default function SixteenIsToNine() {
- return (
-
- VIDEO
-
- );
-}
diff --git a/src/components/videos/TwentyOneIsToNine.tsx b/src/components/videos/TwentyOneIsToNine.tsx
deleted file mode 100644
index 2133a9e..0000000
--- a/src/components/videos/TwentyOneIsToNine.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-
-export default function TwentyOneIsToNine() {
- return (
-
- VIDEO
-
- );
-}
diff --git a/src/layout/AppSidebar.tsx b/src/layout/AppSidebar.tsx
index ab61fdf..3e1a2fe 100644
--- a/src/layout/AppSidebar.tsx
+++ b/src/layout/AppSidebar.tsx
@@ -328,12 +328,12 @@ const AppSidebar: React.FC = () => {
-
+
diff --git a/src/service/chatbotService.ts b/src/service/chatbotService.ts
index 885d0f6..91fddb0 100644
--- a/src/service/chatbotService.ts
+++ b/src/service/chatbotService.ts
@@ -3,6 +3,11 @@ 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);
+ const response = await api.post(API.Chatbot.CHAT, payload);
+ return await response?.data;
+};
+
+export const apiChatbotHistory = async (params?: { cursor?: string; limit?: number }): Promise => {
+ const response = await api.get(API.Chatbot.HISTORY, { params });
return await response?.data;
};
\ No newline at end of file