feat: implement user profile page, chatbot integration, and navigation components
Build and Release / release (push) Successful in 55s
Build and Release / release (push) Successful in 55s
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Alerts" />
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<ComponentCard title="Success Alert">
|
||||
<Alert
|
||||
variant="success"
|
||||
title="Success Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={true}
|
||||
linkHref="/"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
<Alert
|
||||
variant="success"
|
||||
title="Success Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={false}
|
||||
/>
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Warning Alert">
|
||||
<Alert
|
||||
variant="warning"
|
||||
title="Warning Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={true}
|
||||
linkHref="/"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
<Alert
|
||||
variant="warning"
|
||||
title="Warning Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={false}
|
||||
/>
|
||||
</ComponentCard>{" "}
|
||||
<ComponentCard title="Error Alert">
|
||||
<Alert
|
||||
variant="error"
|
||||
title="Error Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={true}
|
||||
linkHref="/"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
<Alert
|
||||
variant="error"
|
||||
title="Error Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={false}
|
||||
/>
|
||||
</ComponentCard>{" "}
|
||||
<ComponentCard title="Info Alert">
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Info Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={true}
|
||||
linkHref="/"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
<Alert
|
||||
variant="info"
|
||||
title="Info Message"
|
||||
message="Be cautious when performing this action."
|
||||
showLink={false}
|
||||
/>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Avatar" />
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<ComponentCard title="Default Avatar">
|
||||
{/* Default Avatar (No Status) */}
|
||||
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||
<Avatar src="/images/user/user-01.jpg" size="xsmall" />
|
||||
<Avatar src="/images/user/user-01.jpg" size="small" />
|
||||
<Avatar src="/images/user/user-01.jpg" size="medium" />
|
||||
<Avatar src="/images/user/user-01.jpg" size="large" />
|
||||
<Avatar src="/images/user/user-01.jpg" size="xlarge" />
|
||||
<Avatar src="/images/user/user-01.jpg" size="xxlarge" />
|
||||
</div>
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Avatar with online indicator">
|
||||
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xsmall"
|
||||
status="online"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="small"
|
||||
status="online"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="medium"
|
||||
status="online"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="large"
|
||||
status="online"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xlarge"
|
||||
status="online"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xxlarge"
|
||||
status="online"
|
||||
/>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Avatar with Offline indicator">
|
||||
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xsmall"
|
||||
status="offline"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="small"
|
||||
status="offline"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="medium"
|
||||
status="offline"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="large"
|
||||
status="offline"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xlarge"
|
||||
status="offline"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xxlarge"
|
||||
status="offline"
|
||||
/>
|
||||
</div>
|
||||
</ComponentCard>{" "}
|
||||
<ComponentCard title="Avatar with busy indicator">
|
||||
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xsmall"
|
||||
status="busy"
|
||||
/>
|
||||
<Avatar src="/images/user/user-01.jpg" size="small" status="busy" />
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="medium"
|
||||
status="busy"
|
||||
/>
|
||||
<Avatar src="/images/user/user-01.jpg" size="large" status="busy" />
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xlarge"
|
||||
status="busy"
|
||||
/>
|
||||
<Avatar
|
||||
src="/images/user/user-01.jpg"
|
||||
size="xxlarge"
|
||||
status="busy"
|
||||
/>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Badges" />
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
With Light Background
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
{/* Light Variant */}
|
||||
<Badge variant="light" color="primary">
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="light" color="success">
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="error">
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="warning">
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="info">
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="light" color="light">
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="light" color="dark">
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
With Solid Background
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
{/* Light Variant */}
|
||||
<Badge variant="solid" color="primary">
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="solid" color="success">
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="error">
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="warning">
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="info">
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="solid" color="light">
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="solid" color="dark">
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Light Background with Left Icon
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
<Badge variant="light" color="primary" startIcon={<PlusIcon />}>
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="light" color="success" startIcon={<PlusIcon />}>
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="error" startIcon={<PlusIcon />}>
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="warning" startIcon={<PlusIcon />}>
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="info" startIcon={<PlusIcon />}>
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="light" color="light" startIcon={<PlusIcon />}>
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="light" color="dark" startIcon={<PlusIcon />}>
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Solid Background with Left Icon
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
<Badge variant="solid" color="primary" startIcon={<PlusIcon />}>
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="solid" color="success" startIcon={<PlusIcon />}>
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="error" startIcon={<PlusIcon />}>
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="warning" startIcon={<PlusIcon />}>
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="info" startIcon={<PlusIcon />}>
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="solid" color="light" startIcon={<PlusIcon />}>
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="solid" color="dark" startIcon={<PlusIcon />}>
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Light Background with Right Icon
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
<Badge variant="light" color="primary" endIcon={<PlusIcon />}>
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="light" color="success" endIcon={<PlusIcon />}>
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="error" endIcon={<PlusIcon />}>
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="warning" endIcon={<PlusIcon />}>
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="light" color="info" endIcon={<PlusIcon />}>
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="light" color="light" endIcon={<PlusIcon />}>
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="light" color="dark" endIcon={<PlusIcon />}>
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-6 py-5">
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
Solid Background with Right Icon
|
||||
</h3>
|
||||
</div>
|
||||
<div className="p-6 border-t border-gray-100 dark:border-gray-800 xl:p-10">
|
||||
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||
<Badge variant="solid" color="primary" endIcon={<PlusIcon />}>
|
||||
Primary
|
||||
</Badge>
|
||||
<Badge variant="solid" color="success" endIcon={<PlusIcon />}>
|
||||
Success
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="error" endIcon={<PlusIcon />}>
|
||||
Error
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="warning" endIcon={<PlusIcon />}>
|
||||
Warning
|
||||
</Badge>{" "}
|
||||
<Badge variant="solid" color="info" endIcon={<PlusIcon />}>
|
||||
Info
|
||||
</Badge>
|
||||
<Badge variant="solid" color="light" endIcon={<PlusIcon />}>
|
||||
Light
|
||||
</Badge>
|
||||
<Badge variant="solid" color="dark" endIcon={<PlusIcon />}>
|
||||
Dark
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Buttons" />
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
{/* Primary Button */}
|
||||
<ComponentCard title="Primary Button">
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="primary">
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="primary">
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
{/* Primary Button with Start Icon */}
|
||||
<ComponentCard title="Primary Button with Left Icon">
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="primary" startIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="primary" startIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>{" "}
|
||||
{/* Primary Button with Start Icon */}
|
||||
<ComponentCard title="Primary Button with Right Icon">
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="primary" endIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="primary" endIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
{/* Outline Button */}
|
||||
<ComponentCard title="Secondary Button">
|
||||
<div className="flex items-center gap-5">
|
||||
{/* Outline Button */}
|
||||
<Button size="sm" variant="outline">
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="outline">
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
{/* Outline Button with Start Icon */}
|
||||
<ComponentCard title="Outline Button with Left Icon">
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="outline" startIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="outline" startIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>{" "}
|
||||
{/* Outline Button with Start Icon */}
|
||||
<ComponentCard title="Outline Button with Right Icon">
|
||||
<div className="flex items-center gap-5">
|
||||
<Button size="sm" variant="outline" endIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
<Button size="md" variant="outline" endIcon={<BoxIcon />}>
|
||||
Button Text
|
||||
</Button>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Images" />
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<ComponentCard title="Responsive image">
|
||||
<ResponsiveImage />
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Image in 2 Grid">
|
||||
<TwoColumnImageGrid />
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Image in 3 Grid">
|
||||
<ThreeColumnImageGrid />
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Modals" />
|
||||
<div className="grid grid-cols-1 gap-5 xl:grid-cols-2 xl:gap-6">
|
||||
<DefaultModal />
|
||||
<VerticallyCenteredModal />
|
||||
<FormInModal />
|
||||
<FullScreenModal />
|
||||
<ModalBasedAlerts />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Videos" />
|
||||
|
||||
<VideosExample />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Bar Chart" />
|
||||
<div className="space-y-6">
|
||||
<ComponentCard title="Bar Chart 1">
|
||||
<BarChartOne />
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<PageBreadcrumb pageTitle="Line Chart" />
|
||||
<div className="space-y-6">
|
||||
<ComponentCard title="Line Chart 1">
|
||||
<LineChartOne />
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<any>(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 <Loading />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<StickyHeader header={`Thông tin tài khoản`} />
|
||||
<div className="md:px-12 flex flex-col md:flex-row mx-auto gap-6 w-full max-w-7xl items-start">
|
||||
<div className="w-full md:max-w-72 xl:max-w-82 pr-0 md:pr-4 border-b md:border-b-0 md:border-r border-gray-300 pb-6 md:pb-0 shrink-0 space-y-6">
|
||||
<UserMetaCard data={userMetaProps} />
|
||||
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
|
||||
<AccountDetails data={userMetaProps} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0 w-full">
|
||||
{appLoading ? (
|
||||
<div className="flex items-center justify-center p-20 w-full bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border border-zinc-200 dark:border-zinc-800">
|
||||
<div className="w-8 h-8 border-4 border-zinc-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : application ? (
|
||||
<div className="">
|
||||
<SafeHTMLRenderer html={application.content} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-10 text-center text-zinc-500 font-medium bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border-2 border-zinc-50 dark:border-zinc-800">
|
||||
Không tìm thấy thông tin hồ sơ nhà sử học.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] lg:p-6 mt-[100px]">
|
||||
<h3 className="mb-5 text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-7">
|
||||
Thông tin tài khoản
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
<UserMetaCard data={userMetaProps} />
|
||||
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
|
||||
<AccountDetails data={userMetaProps} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+120
-35
@@ -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<any>(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 <Loading />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<StickyHeader header={`Thông tin tài khoản`} />
|
||||
<div className="md:px-12 flex flex-col md:flex-row mx-auto gap-6 w-full max-w-7xl items-start">
|
||||
<div className="w-full md:max-w-72 xl:max-w-82 pr-0 md:pr-4 border-b md:border-b-0 md:border-r border-gray-300 pb-6 md:pb-0 shrink-0 space-y-6">
|
||||
<UserMetaCard data={userMetaProps} />
|
||||
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
|
||||
<AccountDetails data={userMetaProps} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0 w-full">
|
||||
{appLoading ? (
|
||||
<div className="flex items-center justify-center p-20 w-full bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border border-zinc-200 dark:border-zinc-800">
|
||||
<div className="w-8 h-8 border-4 border-zinc-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : application ? (
|
||||
<div className="">
|
||||
<SafeHTMLRenderer html={application.content} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-10 text-center text-zinc-500 font-medium bg-zinc-50/50 dark:bg-zinc-950/30 rounded-2xl border-2 border-zinc-50 dark:border-zinc-800">
|
||||
Không tìm thấy thông tin hồ sơ nhà sử học.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Ecommerce() {
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-4 md:gap-6 2xl:gap-8">
|
||||
<div className="col-span-12 space-y-6 xl:col-span-7 2xl:col-span-8">
|
||||
<EcommerceMetrics />
|
||||
|
||||
<MonthlySalesChart />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 xl:col-span-5 2xl:col-span-4">
|
||||
<MonthlyTarget />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12">
|
||||
<StatisticsChart />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 xl:col-span-5 2xl:col-span-4">
|
||||
<DemographicCard />
|
||||
</div>
|
||||
|
||||
<div className="col-span-12 xl:col-span-7 2xl:col-span-8">
|
||||
<RecentOrders />
|
||||
<div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] lg:p-6 mt-[100px]">
|
||||
<h3 className="mb-5 text-lg font-semibold text-gray-800 dark:text-white/90 lg:mb-7">
|
||||
Thông tin tài khoản
|
||||
</h3>
|
||||
<div className="space-y-6">
|
||||
<UserMetaCard data={userMetaProps} />
|
||||
<UserInfoCard data={{ ...userMetaProps, openEdit: true }} />
|
||||
<AccountDetails data={userMetaProps} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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<CalendarEvent | null>(
|
||||
null
|
||||
);
|
||||
const [eventTitle, setEventTitle] = useState("");
|
||||
const [eventStartDate, setEventStartDate] = useState("");
|
||||
const [eventEndDate, setEventEndDate] = useState("");
|
||||
const [eventLevel, setEventLevel] = useState("");
|
||||
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
||||
const calendarRef = useRef<FullCalendar>(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 (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="custom-calendar">
|
||||
<FullCalendar
|
||||
ref={calendarRef}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
initialView="dayGridMonth"
|
||||
headerToolbar={{
|
||||
left: "prev,next addEventButton",
|
||||
center: "title",
|
||||
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||
}}
|
||||
events={events}
|
||||
selectable={true}
|
||||
select={handleDateSelect}
|
||||
eventClick={handleEventClick}
|
||||
eventContent={renderEventContent}
|
||||
customButtons={{
|
||||
addEventButton: {
|
||||
text: "Add Event +",
|
||||
click: openModal,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
className="max-w-[700px] p-6 lg:p-10"
|
||||
>
|
||||
<div className="flex flex-col px-2 overflow-y-auto custom-scrollbar">
|
||||
<div>
|
||||
<h5 className="mb-2 font-semibold text-gray-800 modal-title text-theme-xl dark:text-white/90 lg:text-2xl">
|
||||
{selectedEvent ? "Edit Event" : "Add Event"}
|
||||
</h5>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Plan your next big moment: schedule or edit an event to stay on
|
||||
track
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<div>
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Event Title
|
||||
</label>
|
||||
<input
|
||||
id="event-title"
|
||||
type="text"
|
||||
value={eventTitle}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<label className="block mb-4 text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Event Color
|
||||
</label>
|
||||
<div className="flex flex-wrap items-center gap-4 sm:gap-5">
|
||||
{Object.entries(calendarsEvents).map(([key, value]) => (
|
||||
<div key={key} className="n-chk">
|
||||
<div
|
||||
className={`form-check form-check-${value} form-check-inline`}
|
||||
>
|
||||
<label
|
||||
className="flex items-center text-sm text-gray-700 form-check-label dark:text-gray-400"
|
||||
htmlFor={`modal${key}`}
|
||||
>
|
||||
<span className="relative">
|
||||
<input
|
||||
className="sr-only form-check-input"
|
||||
type="radio"
|
||||
name="event-level"
|
||||
value={key}
|
||||
id={`modal${key}`}
|
||||
checked={eventLevel === key}
|
||||
onChange={() => setEventLevel(key)}
|
||||
/>
|
||||
<span className="flex items-center justify-center w-5 h-5 mr-2 border border-gray-300 rounded-full box dark:border-gray-700">
|
||||
<span
|
||||
className={`h-2 w-2 rounded-full bg-white ${
|
||||
eventLevel === key ? "block" : "hidden"
|
||||
}`}
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
{key}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Enter Start Date
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="event-start-date"
|
||||
type="date"
|
||||
value={eventStartDate}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Enter End Date
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="event-end-date"
|
||||
type="date"
|
||||
value={eventEndDate}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-6 modal-footer sm:justify-end">
|
||||
<button
|
||||
onClick={closeModal}
|
||||
type="button"
|
||||
className="flex w-full justify-center rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] sm:w-auto"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddOrUpdateEvent}
|
||||
type="button"
|
||||
className="btn btn-success btn-update-event flex w-full justify-center rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white hover:bg-brand-600 sm:w-auto"
|
||||
>
|
||||
{selectedEvent ? "Update Changes" : "Add Event"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEventContent = (eventInfo: EventContentArg) => {
|
||||
const colorClass = `fc-bg-${eventInfo.event.extendedProps.calendar.toLowerCase()}`;
|
||||
return (
|
||||
<div
|
||||
className={`event-fc-color flex fc-event-main ${colorClass} p-1 rounded-sm`}
|
||||
>
|
||||
<div className="fc-daygrid-event-dot"></div>
|
||||
<div className="fc-event-time">{eventInfo.timeText}</div>
|
||||
<div className="fc-event-title">{eventInfo.event.title}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calendar;
|
||||
@@ -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 (
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div id="chartOne" className="min-w-[1000px]">
|
||||
<ReactApexChart
|
||||
options={options}
|
||||
series={series}
|
||||
type="bar"
|
||||
height={180}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div id="chartEight" className="min-w-[1000px]">
|
||||
<ReactApexChart
|
||||
options={options}
|
||||
series={series}
|
||||
type="area"
|
||||
height={310}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="flex items-center gap-0.5 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
|
||||
<button
|
||||
onClick={() => 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
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => 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
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => 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
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartTab;
|
||||
@@ -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<CountryMapProps> = ({ mapColor }) => {
|
||||
return (
|
||||
<VectorMap
|
||||
map={worldMill}
|
||||
backgroundColor="transparent"
|
||||
markerStyle={
|
||||
{
|
||||
initial: {
|
||||
fill: "#465FFF",
|
||||
r: 4, // Custom radius for markers
|
||||
}, // Type assertion to bypass strict CSS property checks
|
||||
} as MarkerStyle
|
||||
}
|
||||
markersSelectable={true}
|
||||
markers={
|
||||
[
|
||||
{
|
||||
latLng: [37.2580397, -104.657039],
|
||||
name: "United States",
|
||||
style: {
|
||||
fill: "#465FFF",
|
||||
borderWidth: 1,
|
||||
borderColor: "white",
|
||||
stroke: "#383f47",
|
||||
},
|
||||
},
|
||||
{
|
||||
latLng: [20.7504374, 73.7276105],
|
||||
name: "India",
|
||||
style: { fill: "#465FFF", borderWidth: 1, borderColor: "white" },
|
||||
},
|
||||
{
|
||||
latLng: [53.613, -11.6368],
|
||||
name: "United Kingdom",
|
||||
style: { fill: "#465FFF", borderWidth: 1, borderColor: "white" },
|
||||
},
|
||||
{
|
||||
latLng: [-25.0304388, 115.2092761],
|
||||
name: "Sweden",
|
||||
style: {
|
||||
fill: "#465FFF",
|
||||
borderWidth: 1,
|
||||
borderColor: "white",
|
||||
strokeOpacity: 0,
|
||||
},
|
||||
},
|
||||
] as Marker[]
|
||||
}
|
||||
zoomOnScroll={false}
|
||||
zoomMax={12}
|
||||
zoomMin={1}
|
||||
zoomAnimate={true}
|
||||
zoomStep={1.5}
|
||||
regionStyle={{
|
||||
initial: {
|
||||
fill: mapColor || "#D0D5DD",
|
||||
fillOpacity: 1,
|
||||
fontFamily: "Outfit",
|
||||
stroke: "none",
|
||||
strokeWidth: 0,
|
||||
strokeOpacity: 0,
|
||||
},
|
||||
hover: {
|
||||
fillOpacity: 0.7,
|
||||
cursor: "pointer",
|
||||
fill: "#465fff",
|
||||
stroke: "none",
|
||||
},
|
||||
selected: {
|
||||
fill: "#465FFF",
|
||||
},
|
||||
selectedHover: {},
|
||||
}}
|
||||
regionLabelStyle={{
|
||||
initial: {
|
||||
fill: "#35373e",
|
||||
fontWeight: 500,
|
||||
fontSize: "13px",
|
||||
stroke: "none",
|
||||
},
|
||||
hover: {},
|
||||
selected: {},
|
||||
selectedHover: {},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CountryMap;
|
||||
@@ -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 (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] sm:p-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Customers Demographic
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Number of customer based on country
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative inline-block">
|
||||
<button onClick={toggleDropdown} className="dropdown-toggle">
|
||||
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
|
||||
</button>
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-6 my-6 overflow-hidden border border-gary-200 rounded-2xl bg-gray-50 dark:border-gray-800 dark:bg-gray-900 sm:px-6">
|
||||
<div
|
||||
id="mapOne"
|
||||
className="mapOne map-btn -mx-4 -my-6 h-[212px] w-[252px] 2xsm:w-[307px] xsm:w-[358px] sm:-mx-6 md:w-[668px] lg:w-[634px] xl:w-[393px] 2xl:w-[554px]"
|
||||
>
|
||||
<CountryMap />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="items-center w-full rounded-full max-w-8">
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
src="/images/country/country-01.svg"
|
||||
alt="usa"
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
|
||||
USA
|
||||
</p>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
2,379 Customers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-[140px] items-center gap-3">
|
||||
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
|
||||
<div className="absolute left-0 top-0 flex h-full w-[79%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
|
||||
</div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
79%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="items-center w-full rounded-full max-w-8">
|
||||
<Image
|
||||
width={48}
|
||||
height={48}
|
||||
className="w-full"
|
||||
src="/images/country/country-02.svg"
|
||||
alt="france"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-gray-800 text-theme-sm dark:text-white/90">
|
||||
France
|
||||
</p>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
589 Customers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-[140px] items-center gap-3">
|
||||
<div className="relative block h-2 w-full max-w-[100px] rounded-sm bg-gray-200 dark:bg-gray-800">
|
||||
<div className="absolute left-0 top-0 flex h-full w-[23%] items-center justify-center rounded-sm bg-brand-500 text-xs font-medium text-white"></div>
|
||||
</div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
23%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-6">
|
||||
{/* <!-- Metric Item Start --> */}
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
|
||||
<GroupIcon className="text-gray-800 size-6 dark:text-white/90" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-end justify-between mt-5">
|
||||
<div>
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Customers
|
||||
</span>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
3,782
|
||||
</h4>
|
||||
</div>
|
||||
<Badge color="success">
|
||||
<ArrowUpIcon />
|
||||
11.01%
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- Metric Item End --> */}
|
||||
|
||||
{/* <!-- Metric Item Start --> */}
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div className="flex items-center justify-center w-12 h-12 bg-gray-100 rounded-xl dark:bg-gray-800">
|
||||
<BoxIconLine className="text-gray-800 dark:text-white/90" />
|
||||
</div>
|
||||
<div className="flex items-end justify-between mt-5">
|
||||
<div>
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Orders
|
||||
</span>
|
||||
<h4 className="mt-2 font-bold text-gray-800 text-title-sm dark:text-white/90">
|
||||
5,359
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<Badge color="error">
|
||||
<ArrowDownIcon className="text-error-500" />
|
||||
9.05%
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* <!-- Metric Item End --> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Monthly Sales
|
||||
</h3>
|
||||
|
||||
<div className="relative inline-block">
|
||||
<button onClick={toggleDropdown} className="dropdown-toggle">
|
||||
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
|
||||
</button>
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div className="-ml-5 min-w-[650px] xl:min-w-full pl-2">
|
||||
<ReactApexChart
|
||||
options={options}
|
||||
series={series}
|
||||
type="bar"
|
||||
height={180}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="rounded-2xl border border-gray-200 bg-gray-100 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div className="px-5 pt-5 bg-white shadow-default rounded-2xl pb-11 dark:bg-gray-900 sm:px-6 sm:pt-6">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Monthly Target
|
||||
</h3>
|
||||
<p className="mt-1 font-normal text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Target you’ve set for each month
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative inline-block">
|
||||
<button onClick={toggleDropdown} className="dropdown-toggle">
|
||||
<MoreDotIcon className="text-gray-400 hover:text-gray-700 dark:hover:text-gray-300" />
|
||||
</button>
|
||||
<Dropdown
|
||||
isOpen={isOpen}
|
||||
onClose={closeDropdown}
|
||||
className="w-40 p-2"
|
||||
>
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
View More
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
tag="a"
|
||||
onItemClick={closeDropdown}
|
||||
className="flex w-full font-normal text-left text-gray-500 rounded-lg hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative ">
|
||||
<div className="max-h-[330px]">
|
||||
<ReactApexChart
|
||||
options={options}
|
||||
series={series}
|
||||
type="radialBar"
|
||||
height={330}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="absolute left-1/2 top-full -translate-x-1/2 -translate-y-[95%] rounded-full bg-success-50 px-3 py-1 text-xs font-medium text-success-600 dark:bg-success-500/15 dark:text-success-500">
|
||||
+10%
|
||||
</span>
|
||||
</div>
|
||||
<p className="mx-auto mt-10 w-full max-w-[380px] text-center text-sm text-gray-500 sm:text-base">
|
||||
You earn $3287 today, it's higher than last month. Keep up your
|
||||
good work!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-5 px-6 py-3.5 sm:gap-8 sm:py-5">
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Target
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.26816 13.6632C7.4056 13.8192 7.60686 13.9176 7.8311 13.9176C7.83148 13.9176 7.83187 13.9176 7.83226 13.9176C8.02445 13.9178 8.21671 13.8447 8.36339 13.6981L12.3635 9.70076C12.6565 9.40797 12.6567 8.9331 12.3639 8.6401C12.0711 8.34711 11.5962 8.34694 11.3032 8.63973L8.5811 11.36L8.5811 2.5C8.5811 2.08579 8.24531 1.75 7.8311 1.75C7.41688 1.75 7.0811 2.08579 7.0811 2.5L7.0811 11.3556L4.36354 8.63975C4.07055 8.34695 3.59568 8.3471 3.30288 8.64009C3.01008 8.93307 3.01023 9.40794 3.30321 9.70075L7.26816 13.6632Z"
|
||||
fill="#D92D20"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Revenue
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
|
||||
fill="#039855"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-px bg-gray-200 h-7 dark:bg-gray-800"></div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1 text-center text-gray-500 text-theme-xs dark:text-gray-400 sm:text-sm">
|
||||
Today
|
||||
</p>
|
||||
<p className="flex items-center justify-center gap-1 text-base font-semibold text-gray-800 dark:text-white/90 sm:text-lg">
|
||||
$20K
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.60141 2.33683C7.73885 2.18084 7.9401 2.08243 8.16435 2.08243C8.16475 2.08243 8.16516 2.08243 8.16556 2.08243C8.35773 2.08219 8.54998 2.15535 8.69664 2.30191L12.6968 6.29924C12.9898 6.59203 12.9899 7.0669 12.6971 7.3599C12.4044 7.6529 11.9295 7.65306 11.6365 7.36027L8.91435 4.64004L8.91435 13.5C8.91435 13.9142 8.57856 14.25 8.16435 14.25C7.75013 14.25 7.41435 13.9142 7.41435 13.5L7.41435 4.64442L4.69679 7.36025C4.4038 7.65305 3.92893 7.6529 3.63613 7.35992C3.34333 7.06693 3.34348 6.59206 3.63646 6.29926L7.60141 2.33683Z"
|
||||
fill="#039855"
|
||||
/>
|
||||
</svg>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white px-4 pb-3 pt-4 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6">
|
||||
<div className="flex flex-col gap-2 mb-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Recent Orders
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200">
|
||||
<svg
|
||||
className="stroke-current fill-white dark:fill-gray-800"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.29004 5.90393H17.7067"
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M17.7075 14.0961H2.29085"
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M12.0826 3.33331C13.5024 3.33331 14.6534 4.48431 14.6534 5.90414C14.6534 7.32398 13.5024 8.47498 12.0826 8.47498C10.6627 8.47498 9.51172 7.32398 9.51172 5.90415C9.51172 4.48432 10.6627 3.33331 12.0826 3.33331Z"
|
||||
fill=""
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M7.91745 11.525C6.49762 11.525 5.34662 12.676 5.34662 14.0959C5.34661 15.5157 6.49762 16.6667 7.91745 16.6667C9.33728 16.6667 10.4883 15.5157 10.4883 14.0959C10.4883 12.676 9.33728 11.525 7.91745 11.525Z"
|
||||
fill=""
|
||||
stroke=""
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
Filter
|
||||
</button>
|
||||
<button className="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-theme-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 hover:text-gray-800 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03] dark:hover:text-gray-200">
|
||||
See all
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<Table>
|
||||
{/* Table Header */}
|
||||
<TableHeader className="border-gray-100 dark:border-gray-800 border-y">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Products
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Category
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Price
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Status
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
{/* Table Body */}
|
||||
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
{tableData.map((product) => (
|
||||
<TableRow key={product.id} className="">
|
||||
<TableCell className="py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-[50px] w-[50px] overflow-hidden rounded-md">
|
||||
<Image
|
||||
width={50}
|
||||
height={50}
|
||||
src={product.image}
|
||||
className="h-[50px] w-[50px]"
|
||||
alt={product.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
{product.name}
|
||||
</p>
|
||||
<span className="text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
{product.variants}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
{product.price}
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
{product.category}
|
||||
</TableCell>
|
||||
<TableCell className="py-3 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
<Badge
|
||||
size="sm"
|
||||
color={
|
||||
product.status === "Delivered"
|
||||
? "success"
|
||||
: product.status === "Pending"
|
||||
? "warning"
|
||||
: "error"
|
||||
}
|
||||
>
|
||||
{product.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<HTMLInputElement>(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:
|
||||
'<svg class="stroke-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.5 15L7.5 10L12.5 5" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
nextArrow:
|
||||
'<svg class="stroke-current" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.5 15L12.5 10L7.5 5" stroke="" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
});
|
||||
|
||||
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 (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white px-5 pb-5 pt-5 dark:border-gray-800 dark:bg-white/[0.03] sm:px-6 sm:pt-6">
|
||||
<div className="flex flex-col gap-5 mb-6 sm:flex-row sm:justify-between">
|
||||
<div className="w-full">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Statistics
|
||||
</h3>
|
||||
<p className="mt-1 text-gray-500 text-theme-sm dark:text-gray-400">
|
||||
Target you've set for each month
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 sm:justify-end">
|
||||
<ChartTab />
|
||||
<div className="relative inline-flex items-center">
|
||||
<CalenderIcon className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 lg:left-3 lg:top-1/2 lg:translate-x-0 lg:-translate-y-1/2 text-gray-500 dark:text-gray-400 pointer-events-none z-10" />
|
||||
<input
|
||||
ref={datePickerRef}
|
||||
className="h-10 w-10 lg:w-40 lg:h-auto lg:pl-10 lg:pr-3 lg:py-2 rounded-lg border border-gray-200 bg-white text-sm font-medium text-transparent lg:text-gray-700 outline-none dark:border-gray-700 dark:bg-gray-800 dark:lg:text-gray-300 cursor-pointer"
|
||||
placeholder="Select date range"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-full overflow-x-auto custom-scrollbar">
|
||||
<div className="min-w-[1000px] xl:min-w-full">
|
||||
<Chart options={options} series={series} type="area" height={310} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div>
|
||||
<ComponentCard title="Default Modal">
|
||||
<Button size="sm" onClick={openModal}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
>
|
||||
<h4 className="font-semibold text-gray-800 mb-7 text-title-sm dark:text-white/90">
|
||||
Modal Heading
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
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.
|
||||
</p>
|
||||
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Pellentesque euismod est quis mauris lacinia pharetra. Sed a ligula
|
||||
ac odio.
|
||||
</p>
|
||||
<div className="flex items-center justify-end w-full gap-3 mt-8">
|
||||
<Button size="sm" variant="outline" onClick={closeModal}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</ComponentCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Form In Modal">
|
||||
<Button size="sm" onClick={openModal}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
className="max-w-[584px] p-5 lg:p-10"
|
||||
>
|
||||
<form className="">
|
||||
<h4 className="mb-6 text-lg font-medium text-gray-800 dark:text-white/90">
|
||||
Personal Information
|
||||
</h4>
|
||||
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-5 sm:grid-cols-2">
|
||||
<div className="col-span-1">
|
||||
<Label>First Name</Label>
|
||||
<Input type="text" placeholder="Emirhan" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-1">
|
||||
<Label>Last Name</Label>
|
||||
<Input type="text" placeholder="Boruch" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-1">
|
||||
<Label>Last Name</Label>
|
||||
<Input type="email" placeholder="emirhanboruch55@gmail.com" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-1">
|
||||
<Label>Phone</Label>
|
||||
<Input type="text" placeholder="+09 363 398 46" />
|
||||
</div>
|
||||
|
||||
<div className="col-span-1 sm:col-span-2">
|
||||
<Label>Bio</Label>
|
||||
<Input type="text" placeholder="Team Manager" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end w-full gap-3 mt-6">
|
||||
<Button size="sm" variant="outline" onClick={closeModal}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Full Screen Modal">
|
||||
<Button size="sm" onClick={openFullscreenModal}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
isOpen={isFullscreenModalOpen}
|
||||
onClose={closeFullscreenModal}
|
||||
isFullscreen={true}
|
||||
showCloseButton={true}
|
||||
>
|
||||
<div className="fixed top-0 left-0 flex flex-col justify-between w-full h-screen p-6 overflow-x-hidden overflow-y-auto bg-white dark:bg-gray-900 lg:p-10">
|
||||
<div>
|
||||
<h4 className="font-semibold text-gray-800 mb-7 text-title-sm dark:text-white/90">
|
||||
Modal Heading
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
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.
|
||||
</p>
|
||||
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
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.
|
||||
</p>
|
||||
<p className="mt-5 text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Pellentesque euismod est quis mauris lacinia pharetra.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-end w-full gap-3 mt-8">
|
||||
<Button size="sm" variant="outline" onClick={closeFullscreenModal}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Modal Based Alerts">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<button
|
||||
onClick={successModal.openModal}
|
||||
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-success-500 shadow-theme-xs hover:bg-success-600"
|
||||
>
|
||||
Success Alert
|
||||
</button>
|
||||
<button
|
||||
onClick={infoModal.openModal}
|
||||
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-blue-light-500 shadow-theme-xs hover:bg-blue-light-600"
|
||||
>
|
||||
Info Alert
|
||||
</button>
|
||||
<button
|
||||
onClick={warningModal.openModal}
|
||||
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-warning-500 shadow-theme-xs hover:bg-warning-600"
|
||||
>
|
||||
Warning Alert
|
||||
</button>
|
||||
<button
|
||||
onClick={errorModal.openModal}
|
||||
className="px-4 py-3 text-sm font-medium text-white rounded-lg bg-error-500 shadow-theme-xs hover:bg-error-600"
|
||||
>
|
||||
Danger Alert
|
||||
</button>
|
||||
</div>
|
||||
{/* Success Modal */}
|
||||
<Modal
|
||||
isOpen={successModal.isOpen}
|
||||
onClose={successModal.closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="relative flex items-center justify-center z-1 mb-7">
|
||||
<svg
|
||||
className="fill-success-50 dark:fill-success-500/15"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 90 90"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
|
||||
fill=""
|
||||
fillOpacity=""
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
|
||||
<svg
|
||||
className="fill-success-600 dark:fill-success-500"
|
||||
width="38"
|
||||
height="38"
|
||||
viewBox="0 0 38 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.9375 19.0004C5.9375 11.7854 11.7864 5.93652 19.0014 5.93652C26.2164 5.93652 32.0653 11.7854 32.0653 19.0004C32.0653 26.2154 26.2164 32.0643 19.0014 32.0643C11.7864 32.0643 5.9375 26.2154 5.9375 19.0004ZM19.0014 2.93652C10.1296 2.93652 2.9375 10.1286 2.9375 19.0004C2.9375 27.8723 10.1296 35.0643 19.0014 35.0643C27.8733 35.0643 35.0653 27.8723 35.0653 19.0004C35.0653 10.1286 27.8733 2.93652 19.0014 2.93652ZM24.7855 17.0575C25.3713 16.4717 25.3713 15.522 24.7855 14.9362C24.1997 14.3504 23.25 14.3504 22.6642 14.9362L17.7177 19.8827L15.3387 17.5037C14.7529 16.9179 13.8031 16.9179 13.2173 17.5037C12.6316 18.0894 12.6316 19.0392 13.2173 19.625L16.657 23.0647C16.9383 23.346 17.3199 23.504 17.7177 23.504C18.1155 23.504 18.4971 23.346 18.7784 23.0647L24.7855 17.0575Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
|
||||
Well Done!
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
|
||||
felis risus nisi non. Quisque eu ut tempor curabitur.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center w-full gap-3 mt-7">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-success-500 shadow-theme-xs hover:bg-success-600 sm:w-auto"
|
||||
>
|
||||
Okay, Got It
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Info Modal */}
|
||||
<Modal
|
||||
isOpen={infoModal.isOpen}
|
||||
onClose={infoModal.closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="relative flex items-center justify-center z-1 mb-7">
|
||||
<svg
|
||||
className="fill-blue-light-50 dark:fill-blue-light-500/15"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 90 90"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
|
||||
fill=""
|
||||
fillOpacity=""
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
|
||||
<svg
|
||||
className="fill-blue-light-500 dark:fill-blue-light-500"
|
||||
width="38"
|
||||
height="38"
|
||||
viewBox="0 0 38 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.85547 18.9998C5.85547 11.7396 11.7411 5.854 19.0013 5.854C26.2615 5.854 32.1471 11.7396 32.1471 18.9998C32.1471 26.2601 26.2615 32.1457 19.0013 32.1457C11.7411 32.1457 5.85547 26.2601 5.85547 18.9998ZM19.0013 2.854C10.0842 2.854 2.85547 10.0827 2.85547 18.9998C2.85547 27.9169 10.0842 35.1457 19.0013 35.1457C27.9184 35.1457 35.1471 27.9169 35.1471 18.9998C35.1471 10.0827 27.9184 2.854 19.0013 2.854ZM16.9999 11.9145C16.9999 13.0191 17.8953 13.9145 18.9999 13.9145H19.0015C20.106 13.9145 21.0015 13.0191 21.0015 11.9145C21.0015 10.81 20.106 9.91454 19.0015 9.91454H18.9999C17.8953 9.91454 16.9999 10.81 16.9999 11.9145ZM19.0014 27.8171C18.173 27.8171 17.5014 27.1455 17.5014 26.3171V17.3293C17.5014 16.5008 18.173 15.8293 19.0014 15.8293C19.8299 15.8293 20.5014 16.5008 20.5014 17.3293L20.5014 26.3171C20.5014 27.1455 19.8299 27.8171 19.0014 27.8171Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
|
||||
Information Alert!
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
|
||||
felis risus nisi non. Quisque eu ut tempor curabitur.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center w-full gap-3 mt-7">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-blue-light-500 shadow-theme-xs hover:bg-blue-light-600 sm:w-auto"
|
||||
>
|
||||
Okay, Got It
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Warning Modal */}
|
||||
<Modal
|
||||
isOpen={warningModal.isOpen}
|
||||
onClose={warningModal.closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="relative flex items-center justify-center z-1 mb-7">
|
||||
<svg
|
||||
className="fill-warning-50 dark:fill-warning-500/15"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 90 90"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
|
||||
fill=""
|
||||
fillOpacity=""
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
|
||||
<svg
|
||||
className="fill-warning-600 dark:fill-orange-400"
|
||||
width="38"
|
||||
height="38"
|
||||
viewBox="0 0 38 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M32.1445 19.0002C32.1445 26.2604 26.2589 32.146 18.9987 32.146C11.7385 32.146 5.85287 26.2604 5.85287 19.0002C5.85287 11.7399 11.7385 5.85433 18.9987 5.85433C26.2589 5.85433 32.1445 11.7399 32.1445 19.0002ZM18.9987 35.146C27.9158 35.146 35.1445 27.9173 35.1445 19.0002C35.1445 10.0831 27.9158 2.85433 18.9987 2.85433C10.0816 2.85433 2.85287 10.0831 2.85287 19.0002C2.85287 27.9173 10.0816 35.146 18.9987 35.146ZM21.0001 26.0855C21.0001 24.9809 20.1047 24.0855 19.0001 24.0855L18.9985 24.0855C17.894 24.0855 16.9985 24.9809 16.9985 26.0855C16.9985 27.19 17.894 28.0855 18.9985 28.0855L19.0001 28.0855C20.1047 28.0855 21.0001 27.19 21.0001 26.0855ZM18.9986 10.1829C19.827 10.1829 20.4986 10.8545 20.4986 11.6829L20.4986 20.6707C20.4986 21.4992 19.827 22.1707 18.9986 22.1707C18.1701 22.1707 17.4986 21.4992 17.4986 20.6707L17.4986 11.6829C17.4986 10.8545 18.1701 10.1829 18.9986 10.1829Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
|
||||
Warning Alert!
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
|
||||
felis risus nisi non. Quisque eu ut tempor curabitur.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center w-full gap-3 mt-7">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-warning-500 shadow-theme-xs hover:bg-warning-600 sm:w-auto"
|
||||
>
|
||||
Okay, Got It
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Error Modal */}
|
||||
<Modal
|
||||
isOpen={errorModal.isOpen}
|
||||
onClose={errorModal.closeModal}
|
||||
className="max-w-[600px] p-5 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="relative flex items-center justify-center z-1 mb-7">
|
||||
<svg
|
||||
className="fill-error-50 dark:fill-error-500/15"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 90 90"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M34.364 6.85053C38.6205 -2.28351 51.3795 -2.28351 55.636 6.85053C58.0129 11.951 63.5594 14.6722 68.9556 13.3853C78.6192 11.0807 86.5743 21.2433 82.2185 30.3287C79.7862 35.402 81.1561 41.5165 85.5082 45.0122C93.3019 51.2725 90.4628 63.9451 80.7747 66.1403C75.3648 67.3661 71.5265 72.2695 71.5572 77.9156C71.6123 88.0265 60.1169 93.6664 52.3918 87.3184C48.0781 83.7737 41.9219 83.7737 37.6082 87.3184C29.8831 93.6664 18.3877 88.0266 18.4428 77.9156C18.4735 72.2695 14.6352 67.3661 9.22531 66.1403C-0.462787 63.9451 -3.30193 51.2725 4.49185 45.0122C8.84391 41.5165 10.2138 35.402 7.78151 30.3287C3.42572 21.2433 11.3808 11.0807 21.0444 13.3853C26.4406 14.6722 31.9871 11.951 34.364 6.85053Z"
|
||||
fill=""
|
||||
fillOpacity=""
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span className="absolute -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
|
||||
<svg
|
||||
className="fill-error-600 dark:fill-error-500"
|
||||
width="38"
|
||||
height="38"
|
||||
viewBox="0 0 38 38"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.62684 11.7496C9.04105 11.1638 9.04105 10.2141 9.62684 9.6283C10.2126 9.04252 11.1624 9.04252 11.7482 9.6283L18.9985 16.8786L26.2485 9.62851C26.8343 9.04273 27.7841 9.04273 28.3699 9.62851C28.9556 10.2143 28.9556 11.164 28.3699 11.7498L21.1198 18.9999L28.3699 26.25C28.9556 26.8358 28.9556 27.7855 28.3699 28.3713C27.7841 28.9571 26.8343 28.9571 26.2485 28.3713L18.9985 21.1212L11.7482 28.3715C11.1624 28.9573 10.2126 28.9573 9.62684 28.3715C9.04105 27.7857 9.04105 26.836 9.62684 26.2502L16.8771 18.9999L9.62684 11.7496Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
|
||||
Danger Alert!
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet consectetur. Feugiat ipsum libero tempor
|
||||
felis risus nisi non. Quisque eu ut tempor curabitur.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center w-full gap-3 mt-7">
|
||||
<button
|
||||
type="button"
|
||||
className="flex justify-center w-full px-4 py-3 text-sm font-medium text-white rounded-lg bg-error-500 shadow-theme-xs hover:bg-error-600 sm:w-auto"
|
||||
>
|
||||
Okay, Got It
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Vertically Centered Modal">
|
||||
<Button size="sm" onClick={openModal}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
showCloseButton={false}
|
||||
className="max-w-[507px] p-6 lg:p-10"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h4 className="mb-2 text-2xl font-semibold text-gray-800 dark:text-white/90 sm:text-title-sm">
|
||||
All Done! Success Confirmed
|
||||
</h4>
|
||||
<p className="text-sm leading-6 text-gray-500 dark:text-gray-400">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Pellentesque euismod est quis mauris lacinia pharetra.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center w-full gap-3 mt-8">
|
||||
<Button size="sm" variant="outline" onClick={closeModal}>
|
||||
Close
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSave}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import React, { FC, ReactNode, FormEvent } from "react";
|
||||
|
||||
interface FormProps {
|
||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Form: FC<FormProps> = ({ onSubmit, children, className }) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault(); // Prevent default form submission
|
||||
onSubmit(event);
|
||||
}}
|
||||
className={` ${className}`} // Default spacing between form fields
|
||||
>
|
||||
{children}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default Form;
|
||||
@@ -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<MultiSelectProps> = ({
|
||||
label,
|
||||
options,
|
||||
defaultSelected = [],
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [selectedOptions, setSelectedOptions] =
|
||||
useState<string[]>(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 (
|
||||
<div className="w-full">
|
||||
<label className="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
{label}
|
||||
</label>
|
||||
|
||||
<div className="relative z-20 inline-block w-full">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<div onClick={toggleDropdown} className="w-full">
|
||||
<div className="mb-2 flex h-11 rounded-lg border border-gray-300 py-1.5 pl-3 pr-3 shadow-theme-xs outline-hidden transition focus:border-brand-300 focus:shadow-focus-ring dark:border-gray-700 dark:bg-gray-900 dark:focus:border-brand-300">
|
||||
<div className="flex flex-wrap flex-auto gap-2">
|
||||
{selectedValuesText.length > 0 ? (
|
||||
selectedValuesText.map((text, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="group flex items-center justify-center rounded-full border-[0.7px] border-transparent bg-gray-100 py-1 pl-2.5 pr-2 text-sm text-gray-800 hover:border-gray-200 dark:bg-gray-800 dark:text-white/90 dark:hover:border-gray-800"
|
||||
>
|
||||
<span className="flex-initial max-w-full">{text}</span>
|
||||
<div className="flex flex-row-reverse flex-auto">
|
||||
<div
|
||||
onClick={() =>
|
||||
removeOption(index, selectedOptions[index])
|
||||
}
|
||||
className="pl-2 text-gray-500 cursor-pointer group-hover:text-gray-400 dark:text-gray-400"
|
||||
>
|
||||
<svg
|
||||
className="fill-current"
|
||||
role="button"
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.40717 4.46881C3.11428 4.17591 3.11428 3.70104 3.40717 3.40815C3.70006 3.11525 4.17494 3.11525 4.46783 3.40815L6.99943 5.93975L9.53095 3.40822C9.82385 3.11533 10.2987 3.11533 10.5916 3.40822C10.8845 3.70112 10.8845 4.17599 10.5916 4.46888L8.06009 7.00041L10.5916 9.53193C10.8845 9.82482 10.8845 10.2997 10.5916 10.5926C10.2987 10.8855 9.82385 10.8855 9.53095 10.5926L6.99943 8.06107L4.46783 10.5927C4.17494 10.8856 3.70006 10.8856 3.40717 10.5927C3.11428 10.2998 3.11428 9.8249 3.40717 9.53201L5.93877 7.00041L3.40717 4.46881Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<input
|
||||
placeholder="Select option"
|
||||
className="w-full h-full p-1 pr-2 text-sm bg-transparent border-0 outline-hidden appearance-none placeholder:text-gray-800 focus:border-0 focus:outline-hidden focus:ring-0 dark:placeholder:text-white/90"
|
||||
readOnly
|
||||
value="Select option"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center py-1 pl-1 pr-1 w-7">
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleDropdown}
|
||||
className="w-5 h-5 text-gray-700 outline-hidden cursor-pointer focus:outline-hidden dark:text-gray-400"
|
||||
>
|
||||
<svg
|
||||
className={`stroke-current ${isOpen ? "rotate-180" : ""}`}
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.79175 7.39551L10.0001 12.6038L15.2084 7.39551"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div
|
||||
className="absolute left-0 z-40 w-full overflow-y-auto bg-white rounded-lg shadow-sm top-full max-h-select dark:bg-gray-900"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{options.map((option, index) => (
|
||||
<div key={index}>
|
||||
<div
|
||||
className={`hover:bg-primary/5 w-full cursor-pointer rounded-t border-b border-gray-200 dark:border-gray-800`}
|
||||
onClick={() => handleSelect(option.value)}
|
||||
>
|
||||
<div
|
||||
className={`relative flex w-full items-center p-2 pl-2 ${
|
||||
selectedOptions.includes(option.value)
|
||||
? "bg-primary/10"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="mx-2 leading-6 text-gray-800 dark:text-white/90">
|
||||
{option.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiSelect;
|
||||
@@ -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<SelectProps> = ({
|
||||
options,
|
||||
placeholder = "Select an option",
|
||||
onChange,
|
||||
className = "",
|
||||
defaultValue = "",
|
||||
}) => {
|
||||
// Manage the selected value
|
||||
const [selectedValue, setSelectedValue] = useState<string>(defaultValue);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value;
|
||||
setSelectedValue(value);
|
||||
onChange(value); // Trigger parent handler
|
||||
};
|
||||
|
||||
return (
|
||||
<select
|
||||
className={`h-11 w-full appearance-none rounded-lg border border-gray-300 px-4 py-2.5 pr-11 text-sm 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 ${
|
||||
selectedValue
|
||||
? "text-gray-800 dark:text-white/90"
|
||||
: "text-gray-400 dark:text-gray-400"
|
||||
} ${className}`}
|
||||
value={selectedValue}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{/* Placeholder option */}
|
||||
<option
|
||||
value=""
|
||||
disabled
|
||||
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
|
||||
>
|
||||
{placeholder}
|
||||
</option>
|
||||
{/* Map over options */}
|
||||
{options.map((option) => (
|
||||
<option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
|
||||
>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -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 (
|
||||
<div>
|
||||
{label && <Label htmlFor={id}>{label}</Label>}
|
||||
|
||||
<div className="relative">
|
||||
<input
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
className="h-11 w-full rounded-lg border appearance-none px-4 py-2.5 text-sm shadow-theme-xs placeholder:text-gray-400 focus:outline-hidden focus:ring-3 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 bg-transparent text-gray-800 border-gray-300 focus:border-brand-300 focus:ring-brand-500/20 dark:border-gray-700 dark:focus:border-brand-800"
|
||||
/>
|
||||
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<CalenderIcon className="size-6" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Checkbox">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox checked={isChecked} onChange={setIsChecked} />
|
||||
<span className="block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Default
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={isCheckedTwo}
|
||||
onChange={setIsCheckedTwo}
|
||||
label="Checked"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Checkbox
|
||||
checked={isCheckedDisabled}
|
||||
onChange={setIsCheckedDisabled}
|
||||
disabled
|
||||
label="Disabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Default Inputs">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<Label>Input</Label>
|
||||
<Input type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Input with Placeholder</Label>
|
||||
<Input type="text" placeholder="info@gmail.com" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>Select Input</Label>
|
||||
<div className="relative">
|
||||
<Select
|
||||
options={options}
|
||||
placeholder="Select an option"
|
||||
onChange={handleSelectChange}
|
||||
className="dark:bg-dark-900"
|
||||
/>
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<ChevronDownIcon/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Password Input</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="Enter your password"
|
||||
/>
|
||||
<button
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeIcon className="fill-gray-500 dark:fill-gray-400" />
|
||||
) : (
|
||||
<EyeCloseIcon className="fill-gray-500 dark:fill-gray-400" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DatePicker
|
||||
id="date-picker"
|
||||
label="Date Picker Input"
|
||||
placeholder="Select a date"
|
||||
onChange={(dates, currentDateString) => {
|
||||
// Handle your logic
|
||||
console.log({ dates, currentDateString });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="tm">Time Picker Input</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="time"
|
||||
id="tm"
|
||||
name="tm"
|
||||
onChange={(e) => console.log(e.target.value)}
|
||||
/>
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<TimeIcon />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="tm">Input with Payment</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Card number"
|
||||
className="pl-[62px]"
|
||||
/>
|
||||
<span className="absolute left-0 top-1/2 flex h-11 w-[46px] -translate-y-1/2 items-center justify-center border-r border-gray-200 dark:border-gray-800">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="6.25" cy="10" r="5.625" fill="#E80B26" />
|
||||
<circle cx="13.75" cy="10" r="5.625" fill="#F59D31" />
|
||||
<path
|
||||
d="M10 14.1924C11.1508 13.1625 11.875 11.6657 11.875 9.99979C11.875 8.33383 11.1508 6.8371 10 5.80713C8.84918 6.8371 8.125 8.33383 8.125 9.99979C8.125 11.6657 8.84918 13.1625 10 14.1924Z"
|
||||
fill="#FC6020"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Dropzone">
|
||||
<div className="transition border border-gray-300 border-dashed cursor-pointer dark:hover:border-brand-500 dark:border-gray-700 rounded-xl hover:border-brand-500">
|
||||
<form
|
||||
{...getRootProps()}
|
||||
className={`dropzone rounded-xl border-dashed border-gray-300 p-7 lg:p-10
|
||||
${
|
||||
isDragActive
|
||||
? "border-brand-500 bg-gray-100 dark:bg-gray-800"
|
||||
: "border-gray-300 bg-gray-50 dark:border-gray-700 dark:bg-gray-900"
|
||||
}
|
||||
`}
|
||||
id="demo-upload"
|
||||
>
|
||||
{/* Hidden Input */}
|
||||
<input {...getInputProps()} />
|
||||
|
||||
<div className="dz-message flex flex-col items-center m-0!">
|
||||
{/* Icon Container */}
|
||||
<div className="mb-[22px] flex justify-center">
|
||||
<div className="flex h-[68px] w-[68px] items-center justify-center rounded-full bg-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-400">
|
||||
<svg
|
||||
className="fill-current"
|
||||
width="29"
|
||||
height="28"
|
||||
viewBox="0 0 29 28"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.5019 3.91699C14.2852 3.91699 14.0899 4.00891 13.953 4.15589L8.57363 9.53186C8.28065 9.82466 8.2805 10.2995 8.5733 10.5925C8.8661 10.8855 9.34097 10.8857 9.63396 10.5929L13.7519 6.47752V18.667C13.7519 19.0812 14.0877 19.417 14.5019 19.417C14.9161 19.417 15.2519 19.0812 15.2519 18.667V6.48234L19.3653 10.5929C19.6583 10.8857 20.1332 10.8855 20.426 10.5925C20.7188 10.2995 20.7186 9.82463 20.4256 9.53184L15.0838 4.19378C14.9463 4.02488 14.7367 3.91699 14.5019 3.91699ZM5.91626 18.667C5.91626 18.2528 5.58047 17.917 5.16626 17.917C4.75205 17.917 4.41626 18.2528 4.41626 18.667V21.8337C4.41626 23.0763 5.42362 24.0837 6.66626 24.0837H22.3339C23.5766 24.0837 24.5839 23.0763 24.5839 21.8337V18.667C24.5839 18.2528 24.2482 17.917 23.8339 17.917C23.4197 17.917 23.0839 18.2528 23.0839 18.667V21.8337C23.0839 22.2479 22.7482 22.5837 22.3339 22.5837H6.66626C6.25205 22.5837 5.91626 22.2479 5.91626 21.8337V18.667Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Text Content */}
|
||||
<h4 className="mb-3 font-semibold text-gray-800 text-theme-xl dark:text-white/90">
|
||||
{isDragActive ? "Drop Files Here" : "Drag & Drop Files Here"}
|
||||
</h4>
|
||||
|
||||
<span className=" text-center mb-5 block w-full max-w-[290px] text-sm text-gray-700 dark:text-gray-400">
|
||||
Drag and drop your PNG, JPG, WebP, SVG images here or browse
|
||||
</span>
|
||||
|
||||
<span className="font-medium underline text-theme-sm text-brand-500">
|
||||
Browse File
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropzoneComponent;
|
||||
@@ -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<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
console.log("Selected file:", file.name);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ComponentCard title="File Input">
|
||||
<div>
|
||||
<Label>Upload file</Label>
|
||||
<FileInput onChange={handleFileChange} className="custom-class" />
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Input Group">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<Label>Email</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
placeholder="info@gmail.com"
|
||||
type="text"
|
||||
className="pl-[62px]"
|
||||
/>
|
||||
<span className="absolute left-0 top-1/2 -translate-y-1/2 border-r border-gray-200 px-3.5 py-3 text-gray-500 dark:border-gray-800 dark:text-gray-400">
|
||||
<EnvelopeIcon />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Phone</Label>
|
||||
<PhoneInput
|
||||
selectPosition="start"
|
||||
countries={countries}
|
||||
placeholder="+1 (555) 000-0000"
|
||||
onChange={handlePhoneNumberChange}
|
||||
/>
|
||||
</div>{" "}
|
||||
<div>
|
||||
<Label>Phone</Label>
|
||||
<PhoneInput
|
||||
selectPosition="end"
|
||||
countries={countries}
|
||||
placeholder="+1 (555) 000-0000"
|
||||
onChange={handlePhoneNumberChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setEmail(value);
|
||||
validateEmail(value);
|
||||
};
|
||||
return (
|
||||
<ComponentCard
|
||||
title="Input States"
|
||||
desc="Validation styles for error, success and disabled states on form controls."
|
||||
>
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
{/* Error Input */}
|
||||
<div>
|
||||
<Label>Email</Label>
|
||||
<Input
|
||||
type="email"
|
||||
defaultValue={email}
|
||||
error={error}
|
||||
onChange={handleEmailChange}
|
||||
placeholder="Enter your email"
|
||||
hint={error ? "This is an invalid email address." : ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Success Input */}
|
||||
<div>
|
||||
<Label>Email</Label>
|
||||
<Input
|
||||
type="email"
|
||||
defaultValue={email}
|
||||
success={!error}
|
||||
onChange={handleEmailChange}
|
||||
placeholder="Enter your email"
|
||||
hint={!error ? "Valid email!" : ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Disabled Input */}
|
||||
<div>
|
||||
<Label>Email</Label>
|
||||
<Input
|
||||
type="text"
|
||||
defaultValue="disabled@example.com"
|
||||
disabled={true}
|
||||
placeholder="Disabled email"
|
||||
hint="This field is disabled."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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<string>("option2");
|
||||
|
||||
const handleRadioChange = (value: string) => {
|
||||
setSelectedValue(value);
|
||||
};
|
||||
return (
|
||||
<ComponentCard title="Radio Buttons">
|
||||
<div className="flex flex-wrap items-center gap-8">
|
||||
<Radio
|
||||
id="radio1"
|
||||
name="group1"
|
||||
value="option1"
|
||||
checked={selectedValue === "option1"}
|
||||
onChange={handleRadioChange}
|
||||
label="Default"
|
||||
/>
|
||||
<Radio
|
||||
id="radio2"
|
||||
name="group1"
|
||||
value="option2"
|
||||
checked={selectedValue === "option2"}
|
||||
onChange={handleRadioChange}
|
||||
label="Selected"
|
||||
/>
|
||||
<Radio
|
||||
id="radio3"
|
||||
name="group1"
|
||||
value="option3"
|
||||
checked={selectedValue === "option3"}
|
||||
onChange={handleRadioChange}
|
||||
label="Disabled"
|
||||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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<string[]>([]);
|
||||
|
||||
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 (
|
||||
<ComponentCard title="Select Inputs">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<Label>Select Input</Label>
|
||||
<div className="relative">
|
||||
<Select
|
||||
options={options}
|
||||
placeholder="Select Option"
|
||||
onChange={handleSelectChange}
|
||||
className="dark:bg-dark-900"
|
||||
/>
|
||||
<span className="absolute text-gray-500 -translate-y-1/2 pointer-events-none right-3 top-1/2 dark:text-gray-400">
|
||||
<ChevronDownIcon/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<MultiSelect
|
||||
label="Multiple Select Options"
|
||||
options={multiOptions}
|
||||
defaultSelected={["1", "3"]}
|
||||
onChange={(values) => setSelectedValues(values)}
|
||||
/>
|
||||
<p className="sr-only">
|
||||
Selected Values: {selectedValues.join(", ")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Textarea input field">
|
||||
<div className="space-y-6">
|
||||
{/* Default TextArea */}
|
||||
<div>
|
||||
<Label>Description</Label>
|
||||
<TextArea
|
||||
value={message}
|
||||
onChange={(value) => setMessage(value)}
|
||||
rows={6}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Disabled TextArea */}
|
||||
<div>
|
||||
<Label>Description</Label>
|
||||
<TextArea rows={6} disabled />
|
||||
</div>
|
||||
|
||||
{/* Error TextArea */}
|
||||
<div>
|
||||
<Label>Description</Label>
|
||||
<TextArea
|
||||
rows={6}
|
||||
value={messageTwo}
|
||||
error
|
||||
onChange={(value) => setMessageTwo(value)}
|
||||
hint="Please enter a valid message."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<ComponentCard title="Toggle switch input">
|
||||
<div className="flex gap-4">
|
||||
<Switch
|
||||
label="Default"
|
||||
defaultChecked={true}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
<Switch
|
||||
label="Checked"
|
||||
defaultChecked={true}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
<Switch label="Disabled" disabled={true} />
|
||||
</div>{" "}
|
||||
<div className="flex gap-4">
|
||||
<Switch
|
||||
label="Default"
|
||||
defaultChecked={true}
|
||||
onChange={handleSwitchChange}
|
||||
color="gray"
|
||||
/>
|
||||
<Switch
|
||||
label="Checked"
|
||||
defaultChecked={true}
|
||||
onChange={handleSwitchChange}
|
||||
color="gray"
|
||||
/>
|
||||
<Switch label="Disabled" disabled={true} color="gray" />
|
||||
</div>
|
||||
</ComponentCard>
|
||||
);
|
||||
}
|
||||
@@ -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<PhoneInputProps> = ({
|
||||
countries,
|
||||
placeholder = "+1 (555) 000-0000",
|
||||
onChange,
|
||||
selectPosition = "start", // Default position is 'start'
|
||||
}) => {
|
||||
const [selectedCountry, setSelectedCountry] = useState<string>("US");
|
||||
const [phoneNumber, setPhoneNumber] = useState<string>("+1");
|
||||
|
||||
const countryCodes: Record<string, string> = countries.reduce(
|
||||
(acc, { code, label }) => ({ ...acc, [code]: label }),
|
||||
{}
|
||||
);
|
||||
|
||||
const handleCountryChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const newCountry = e.target.value;
|
||||
setSelectedCountry(newCountry);
|
||||
setPhoneNumber(countryCodes[newCountry]);
|
||||
if (onChange) {
|
||||
onChange(countryCodes[newCountry]);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePhoneNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newPhoneNumber = e.target.value;
|
||||
setPhoneNumber(newPhoneNumber);
|
||||
if (onChange) {
|
||||
onChange(newPhoneNumber);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex">
|
||||
{/* Dropdown position: Start */}
|
||||
{selectPosition === "start" && (
|
||||
<div className="absolute">
|
||||
<select
|
||||
value={selectedCountry}
|
||||
onChange={handleCountryChange}
|
||||
className="appearance-none bg-none rounded-l-lg border-0 border-r border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
|
||||
>
|
||||
{countries.map((country) => (
|
||||
<option
|
||||
key={country.code}
|
||||
value={country.code}
|
||||
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
|
||||
>
|
||||
{country.code}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none bg-none right-3 dark:text-gray-400">
|
||||
<svg
|
||||
className="stroke-current"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input field */}
|
||||
<input
|
||||
type="tel"
|
||||
value={phoneNumber}
|
||||
onChange={handlePhoneNumberChange}
|
||||
placeholder={placeholder}
|
||||
className={`dark:bg-dark-900 h-11 w-full ${
|
||||
selectPosition === "start" ? "pl-[84px]" : "pr-[84px]"
|
||||
} rounded-lg border border-gray-300 bg-transparent py-3 px-4 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`}
|
||||
/>
|
||||
|
||||
{/* Dropdown position: End */}
|
||||
{selectPosition === "end" && (
|
||||
<div className="absolute right-0">
|
||||
<select
|
||||
value={selectedCountry}
|
||||
onChange={handleCountryChange}
|
||||
className="appearance-none bg-none rounded-r-lg border-0 border-l border-gray-200 bg-transparent py-3 pl-3.5 pr-8 leading-tight text-gray-700 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-800 dark:text-gray-400"
|
||||
>
|
||||
{countries.map((country) => (
|
||||
<option
|
||||
key={country.code}
|
||||
value={country.code}
|
||||
className="text-gray-700 dark:bg-gray-900 dark:text-gray-400"
|
||||
>
|
||||
{country.code}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 flex items-center text-gray-700 pointer-events-none right-3 dark:text-gray-400">
|
||||
<svg
|
||||
className="stroke-current"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.79175 7.396L10.0001 12.6043L15.2084 7.396"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhoneInput;
|
||||
@@ -119,7 +119,7 @@ export default function UserDropdown() {
|
||||
<DropdownItem
|
||||
onItemClick={closeDropdown}
|
||||
tag="a"
|
||||
href="/user/account"
|
||||
href="/user"
|
||||
className="flex items-center gap-3 px-3 py-2 font-medium text-gray-700 rounded-lg group text-theme-sm hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-white/5 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg
|
||||
|
||||
@@ -1,294 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../ui/table";
|
||||
import Badge from "../ui/badge/Badge";
|
||||
import { Application } from "@/interface/historian";
|
||||
|
||||
export type AppSortColumn =
|
||||
| "created_at"
|
||||
| "status"
|
||||
| "verify_type"
|
||||
| "reviewed_at"
|
||||
| "reviewed_by"
|
||||
| "updated_at";
|
||||
|
||||
interface ApplicationTableProps {
|
||||
data: Application[];
|
||||
onSort: (column: AppSortColumn) => 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 (
|
||||
<div className="flex flex-col ml-2 opacity-50 cursor-pointer hover:opacity-100">
|
||||
<svg
|
||||
className={`w-3 h-3 ${isActive && sortOrder === "asc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M5 15l7-7 7 7"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
className={`w-3 h-3 -mt-1 ${isActive && sortOrder === "desc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string | number) => {
|
||||
const s = status?.toString();
|
||||
switch (s) {
|
||||
case "1":
|
||||
case "PENDING":
|
||||
return (
|
||||
<Badge size="sm" variant="light" color="warning">
|
||||
Đang chờ
|
||||
</Badge>
|
||||
);
|
||||
case "2":
|
||||
case "APPROVED":
|
||||
return (
|
||||
<Badge size="sm" variant="light" color="success">
|
||||
Đã duyệt
|
||||
</Badge>
|
||||
);
|
||||
case "3":
|
||||
case "REJECTED":
|
||||
return (
|
||||
<Badge size="sm" variant="light" color="error">
|
||||
Từ chối
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Badge size="sm" variant="light" color="light">
|
||||
{status || "N/A"}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderVerifyTypes = (
|
||||
verifyType: string | string[] | number | number[],
|
||||
) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
"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 (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{typesArray.map((type, index) => {
|
||||
const t = type?.toString();
|
||||
if (!t) return null;
|
||||
return (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-0.5 rounded-md bg-blue-50 text-blue-600 dark:bg-blue-500/10 dark:text-blue-400 text-[11px] font-medium whitespace-nowrap"
|
||||
>
|
||||
{typeMap[t] || t}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<div className="min-w-[1300px]">
|
||||
<Table>
|
||||
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Người gửi (ID)
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[220px]"
|
||||
>
|
||||
Loại xác minh
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Đính kèm
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("status")}
|
||||
>
|
||||
Trạng thái <SortIcon column="status" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("created_at")}
|
||||
>
|
||||
Ngày nộp <SortIcon column="created_at" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("reviewed_at")}
|
||||
>
|
||||
Cập nhật <SortIcon column="reviewed_at" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Cập nhật bởi
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 max-w-[200px]"
|
||||
>
|
||||
Ghi chú
|
||||
</TableCell>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-center text-gray-500 text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Thao tác
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
|
||||
{data.length > 0 ? (
|
||||
data.map((app) => (
|
||||
<TableRow
|
||||
key={app.id}
|
||||
className="hover:bg-gray-50/50 dark:hover:bg-white/[0.01] transition-colors"
|
||||
>
|
||||
<TableCell className="px-5 py-4 text-start font-mono text-theme-xs">
|
||||
{app.user.display_name}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-start">
|
||||
{renderVerifyTypes(app.verify_type)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-center text-theme-sm">
|
||||
<span className="font-bold text-gray-800 dark:text-white mr-1">
|
||||
{app.media?.length || 0}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-center ">
|
||||
{getStatusBadge(app.status)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
|
||||
{formatDate(app.created_at)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
|
||||
{formatDate(app.reviewed_at)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
|
||||
{app.reviewer?.display_name || "-"}
|
||||
</TableCell>
|
||||
<TableCell className="group relative px-5 pb-4 text-start text-theme-xs text-gray-500 dark:text-gray-400 max-w-50 min-w-50">
|
||||
<div className="truncate">{app.review_note || "-"}</div>
|
||||
|
||||
{app.review_note && (
|
||||
<div className="invisible group-hover:visible absolute z-50 bottom-full left-1/2 -translate-x-1/2 w-max max-w-75 p-2 backdrop-blur-xs text-black text-xs rounded-lg shadow-xl wrap-break-words whitespace-normal">
|
||||
{app.review_note}
|
||||
<div className="absolute top-full left-1/2 border-6 border-transparent border-t-white"></div>
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-center">
|
||||
<button
|
||||
onClick={() => onViewDetail(app)}
|
||||
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
|
||||
>
|
||||
Chi tiết
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={9}
|
||||
className="px-5 py-20 text-center text-gray-500 italic"
|
||||
>
|
||||
Không tìm thấy dữ liệu hồ sơ
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="flex flex-col ml-2 opacity-50 cursor-pointer hover:opacity-100">
|
||||
<svg
|
||||
className={`w-3 h-3 ${isActive && sortOrder === "asc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M5 15l7-7 7 7"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
className={`w-3 h-3 -mt-1 ${isActive && sortOrder === "desc" ? "text-blue-700 opacity-100" : "text-gray-400"}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-white/[0.05] dark:bg-white/[0.03]">
|
||||
<div className="max-w-full overflow-x-auto">
|
||||
<div className="min-w-[1100px]">
|
||||
<Table>
|
||||
<TableHeader className="border-b border-gray-100 dark:border-white/[0.05]">
|
||||
<TableRow>
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[265px] max-w-[265px]"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("display_name")}
|
||||
>
|
||||
Người dùng <SortIcon column="display_name" />
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[337px] max-w-[337px]"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("email")}
|
||||
>
|
||||
Email <SortIcon column="email" />
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-start text-theme-xs min-w-[188px] max-w-[188px]"
|
||||
>
|
||||
<div className="relative inline-flex items-center group">
|
||||
<select
|
||||
value={selectedRole}
|
||||
onChange={(e) => 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"
|
||||
>
|
||||
<option value="">Vai trò (Tất cả)</option>
|
||||
{roles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<svg
|
||||
className="w-3 h-3 absolute right-0 text-gray-400 pointer-events-none group-hover:text-brand-500"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={3}
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Trạng thái
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("created_at")}
|
||||
>
|
||||
Ngày tham gia <SortIcon column="created_at" />
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => onSort("updated_at")}
|
||||
>
|
||||
Cập nhật <SortIcon column="updated_at" />
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell
|
||||
isHeader
|
||||
className="px-5 py-3 font-medium text-gray-500 text-center text-theme-xs dark:text-gray-400"
|
||||
>
|
||||
Thao tác
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05]">
|
||||
{data.length > 0 ? (
|
||||
data.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
className="hover:bg-gray-50/50 dark:hover:bg-white/[0.01] transition-colors"
|
||||
>
|
||||
<TableCell className="px-5 py-4 text-start">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 overflow-hidden rounded-full flex-shrink-0 bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
|
||||
{user.profile?.avatar_url ? (
|
||||
<Image
|
||||
width={40}
|
||||
height={40}
|
||||
src={user.profile.avatar_url}
|
||||
alt="Avatar"
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
) : (
|
||||
<span className="font-bold text-brand-500 uppercase text-xs">
|
||||
{user.profile?.display_name?.charAt(0) || "U"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<span className="block font-medium text-gray-800 text-theme-sm dark:text-white/90">
|
||||
{user.profile?.display_name || "N/A"}
|
||||
</span>
|
||||
<span className="block text-gray-500 text-theme-xs dark:text-gray-400">
|
||||
ID: {user.id.slice(0, 8)}...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-start text-theme-sm dark:text-gray-400">
|
||||
{user.email}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="px-5 py-4 text-start text-theme-sm">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{user.roles?.map((role) => (
|
||||
<span
|
||||
key={role.id}
|
||||
className="px-2 py-0.5 rounded-md bg-brand-50 text-brand-600 dark:bg-brand-500/10 dark:text-brand-400 text-[10px] uppercase"
|
||||
>
|
||||
{role.name}
|
||||
</span>
|
||||
)) || (
|
||||
<span className="text-gray-400 italic text-[10px]">
|
||||
No Role
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="px-5 py-4 text-start">
|
||||
<Badge
|
||||
size="sm"
|
||||
variant="light"
|
||||
color={user.is_deleted ? "error" : "success"}
|
||||
>
|
||||
{user.is_deleted ? "Bị khóa" : "Hoạt động"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
|
||||
{formatDate(user.created_at)}
|
||||
</TableCell>
|
||||
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
|
||||
{formatDate(user.updated_at)}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="px-5 py-4 text-center">
|
||||
<button
|
||||
onClick={() => onViewDetail(user)}
|
||||
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
|
||||
>
|
||||
Chi tiết
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={7}
|
||||
className="px-5 py-24 text-center text-gray-500 italic"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<p className="text-theme-sm">
|
||||
Không tìm thấy dữ liệu người dùng
|
||||
</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<Role[]>([]);
|
||||
const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);
|
||||
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 (
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[400px] m-4">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-xl p-6 shadow-lg border border-gray-100 dark:border-gray-800 w-full relative">
|
||||
|
||||
<div className="mb-6 pr-6">
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
Vai trò người dùng
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">
|
||||
{user.profile?.display_name || user.email}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
{fetchingRoles ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="w-6 h-6 border-2 border-gray-200 border-t-brand-500 rounded-full animate-spin"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col space-y-1 max-h-[300px] overflow-y-auto custom-scrollbar -mx-2 px-2">
|
||||
{roles.map((role) => {
|
||||
const isSelected = selectedRoleIds.includes(role.id);
|
||||
const isDefault = role.name === DEFAULT_ROLE_NAME;
|
||||
|
||||
return (
|
||||
<label
|
||||
key={role.id}
|
||||
className={`flex items-center gap-3 p-2.5 rounded-lg transition-colors ${
|
||||
isDefault
|
||||
? "opacity-40 cursor-not-allowed bg-gray-50 dark:bg-gray-800/30"
|
||||
: "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isDefault ? true : isSelected}
|
||||
onChange={() => handleToggleRole(role.id, isDefault)}
|
||||
disabled={isDefault}
|
||||
className={`w-4 h-4 rounded border-gray-300 focus:ring-brand-500 dark:border-gray-600 dark:bg-gray-700 ${
|
||||
isDefault ? "text-gray-400" : "text-brand-500"
|
||||
}`}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className={`text-sm ${
|
||||
isDefault
|
||||
? "text-gray-500 font-normal"
|
||||
: isSelected
|
||||
? "text-gray-900 dark:text-white font-medium"
|
||||
: "text-gray-700 dark:text-gray-300"
|
||||
}`}>
|
||||
{role.name}
|
||||
</span>
|
||||
{isDefault && (
|
||||
<span className="text-[10px] text-gray-400 italic">
|
||||
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end gap-3 mt-8">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
disabled={loading || fetchingRoles}
|
||||
>
|
||||
Hủy
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
type="submit"
|
||||
disabled={loading || fetchingRoles}
|
||||
className="min-w-[100px]"
|
||||
>
|
||||
{loading ? "Đang lưu..." : "Lưu"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -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<MediaDto | null>(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 (
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[850px] m-4">
|
||||
<div className="no-scrollbar relative w-full max-w-[850px] overflow-y-auto rounded-3xl bg-white p-4 dark:bg-gray-900 lg:p-8">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h3 className="text-xl font-bold text-gray-800 dark:text-white">
|
||||
Chi tiết người dùng
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 custom-scrollbar max-h-[65vh] overflow-y-auto pr-2">
|
||||
<UserMetaCard data={formattedData} />
|
||||
<UserInfoCard data={formattedData} />
|
||||
|
||||
|
||||
<div className="min-h-[150px] relative">
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center justify-center py-10 space-y-3">
|
||||
<div className="w-10 h-10 border-4 border-gray-200 border-t-brand-500 rounded-full animate-spin"></div>
|
||||
<p className="text-sm text-gray-500 animate-pulse">Đang tải tài liệu...</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{(mediaData?.data?.length ?? 0) > 0 ? (
|
||||
<MediaCard data={mediaData!} />
|
||||
) : (
|
||||
<div className="p-5 border border-dashed border-gray-200 rounded-2xl text-center text-gray-400 text-sm">
|
||||
Người dùng này chưa có dữ liệu media.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-gray-100 dark:border-white/[0.05] flex items-center justify-between">
|
||||
<div className="text-sm text-gray-500">
|
||||
Thao tác quản trị viên
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
onClick={() => 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ò
|
||||
</button>
|
||||
|
||||
{user.is_deleted ? (
|
||||
<button
|
||||
onClick={() => 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
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => 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
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -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<AlertProps> = ({
|
||||
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: (
|
||||
<svg
|
||||
className="fill-current"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.70186 12.0001C3.70186 7.41711 7.41711 3.70186 12.0001 3.70186C16.5831 3.70186 20.2984 7.41711 20.2984 12.0001C20.2984 16.5831 16.5831 20.2984 12.0001 20.2984C7.41711 20.2984 3.70186 16.5831 3.70186 12.0001ZM12.0001 1.90186C6.423 1.90186 1.90186 6.423 1.90186 12.0001C1.90186 17.5772 6.423 22.0984 12.0001 22.0984C17.5772 22.0984 22.0984 17.5772 22.0984 12.0001C22.0984 6.423 17.5772 1.90186 12.0001 1.90186ZM15.6197 10.7395C15.9712 10.388 15.9712 9.81819 15.6197 9.46672C15.2683 9.11525 14.6984 9.11525 14.347 9.46672L11.1894 12.6243L9.6533 11.0883C9.30183 10.7368 8.73198 10.7368 8.38051 11.0883C8.02904 11.4397 8.02904 12.0096 8.38051 12.3611L10.553 14.5335C10.7217 14.7023 10.9507 14.7971 11.1894 14.7971C11.428 14.7971 11.657 14.7023 11.8257 14.5335L15.6197 10.7395Z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
error: (
|
||||
<svg
|
||||
className="fill-current"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20.3499 12.0004C20.3499 16.612 16.6115 20.3504 11.9999 20.3504C7.38832 20.3504 3.6499 16.612 3.6499 12.0004C3.6499 7.38881 7.38833 3.65039 11.9999 3.65039C16.6115 3.65039 20.3499 7.38881 20.3499 12.0004ZM11.9999 22.1504C17.6056 22.1504 22.1499 17.6061 22.1499 12.0004C22.1499 6.3947 17.6056 1.85039 11.9999 1.85039C6.39421 1.85039 1.8499 6.3947 1.8499 12.0004C1.8499 17.6061 6.39421 22.1504 11.9999 22.1504ZM13.0008 16.4753C13.0008 15.923 12.5531 15.4753 12.0008 15.4753L11.9998 15.4753C11.4475 15.4753 10.9998 15.923 10.9998 16.4753C10.9998 17.0276 11.4475 17.4753 11.9998 17.4753L12.0008 17.4753C12.5531 17.4753 13.0008 17.0276 13.0008 16.4753ZM11.9998 6.62898C12.414 6.62898 12.7498 6.96476 12.7498 7.37898L12.7498 13.0555C12.7498 13.4697 12.414 13.8055 11.9998 13.8055C11.5856 13.8055 11.2498 13.4697 11.2498 13.0555L11.2498 7.37898C11.2498 6.96476 11.5856 6.62898 11.9998 6.62898Z"
|
||||
fill="#F04438"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
warning: (
|
||||
<svg
|
||||
className="fill-current"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.6501 12.0001C3.6501 7.38852 7.38852 3.6501 12.0001 3.6501C16.6117 3.6501 20.3501 7.38852 20.3501 12.0001C20.3501 16.6117 16.6117 20.3501 12.0001 20.3501C7.38852 20.3501 3.6501 16.6117 3.6501 12.0001ZM12.0001 1.8501C6.39441 1.8501 1.8501 6.39441 1.8501 12.0001C1.8501 17.6058 6.39441 22.1501 12.0001 22.1501C17.6058 22.1501 22.1501 17.6058 22.1501 12.0001C22.1501 6.39441 17.6058 1.8501 12.0001 1.8501ZM10.9992 7.52517C10.9992 8.07746 11.4469 8.52517 11.9992 8.52517H12.0002C12.5525 8.52517 13.0002 8.07746 13.0002 7.52517C13.0002 6.97289 12.5525 6.52517 12.0002 6.52517H11.9992C11.4469 6.52517 10.9992 6.97289 10.9992 7.52517ZM12.0002 17.3715C11.586 17.3715 11.2502 17.0357 11.2502 16.6215V10.945C11.2502 10.5308 11.586 10.195 12.0002 10.195C12.4144 10.195 12.7502 10.5308 12.7502 10.945V16.6215C12.7502 17.0357 12.4144 17.3715 12.0002 17.3715Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
info: (
|
||||
<svg
|
||||
className="fill-current"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3.6501 11.9996C3.6501 7.38803 7.38852 3.64961 12.0001 3.64961C16.6117 3.64961 20.3501 7.38803 20.3501 11.9996C20.3501 16.6112 16.6117 20.3496 12.0001 20.3496C7.38852 20.3496 3.6501 16.6112 3.6501 11.9996ZM12.0001 1.84961C6.39441 1.84961 1.8501 6.39392 1.8501 11.9996C1.8501 17.6053 6.39441 22.1496 12.0001 22.1496C17.6058 22.1496 22.1501 17.6053 22.1501 11.9996C22.1501 6.39392 17.6058 1.84961 12.0001 1.84961ZM10.9992 7.52468C10.9992 8.07697 11.4469 8.52468 11.9992 8.52468H12.0002C12.5525 8.52468 13.0002 8.07697 13.0002 7.52468C13.0002 6.9724 12.5525 6.52468 12.0002 6.52468H11.9992C11.4469 6.52468 10.9992 6.9724 10.9992 7.52468ZM12.0002 17.371C11.586 17.371 11.2502 17.0352 11.2502 16.621V10.9445C11.2502 10.5303 11.586 10.1945 12.0002 10.1945C12.4144 10.1945 12.7502 10.5303 12.7502 10.9445V16.621C12.7502 17.0352 12.4144 17.371 12.0002 17.371Z"
|
||||
fill=""
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-xl border p-4 ${variantClasses[variant].container}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`-mt-0.5 ${variantClasses[variant].icon}`}>
|
||||
{icons[variant]}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="mb-1 text-sm font-semibold text-gray-800 dark:text-white/90">
|
||||
{title}
|
||||
</h4>
|
||||
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{message}</p>
|
||||
|
||||
{showLink && (
|
||||
<Link
|
||||
href={linkHref}
|
||||
className="inline-block mt-3 text-sm font-medium text-gray-500 underline dark:text-gray-400"
|
||||
>
|
||||
{linkText}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
interface AvatarTextProps {
|
||||
name: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AvatarText: React.FC<AvatarTextProps> = ({ 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 (
|
||||
<div
|
||||
className={`flex h-10 w-10 ${className} items-center justify-center rounded-full ${getColorClass(
|
||||
name
|
||||
)}`}
|
||||
>
|
||||
<span className="text-sm font-medium">{initials}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvatarText;
|
||||
@@ -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<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 [messages, setMessages] = useState<Message[]>([]);
|
||||
const [preCursor, setPreCursor] = useState<string | null>(null);
|
||||
const [isFetchingHistory, setIsFetchingHistory] = useState(false);
|
||||
const [hasLoadedHistory, setHasLoadedHistory] = useState(false);
|
||||
const [input, setInput] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(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 (
|
||||
<div className="fixed bottom-8 right-8 z-50">
|
||||
{!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"
|
||||
/>
|
||||
</svg>
|
||||
Trợ lý lịch sử.
|
||||
<span>Trợ lý lịch sử</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => 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"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -171,18 +247,32 @@ export default function ChatbotWidget({
|
||||
|
||||
{/* Nội dung Chat */}
|
||||
<div className="flex-1 p-4 overflow-y-auto flex flex-col gap-3 bg-gray-50 dark:bg-[#0d1117] text-sm">
|
||||
{messages.map((msg) => (
|
||||
{/* Load older messages button */}
|
||||
{preCursor && (
|
||||
<div className="flex justify-center mb-1">
|
||||
<button
|
||||
onClick={() => 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"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMessages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`max-w-[85%] rounded-2xl px-4 py-2 shadow-sm ${
|
||||
msg.sender === "user"
|
||||
? "bg-brand-500 text-white self-end rounded-br-sm"
|
||||
: "bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 border border-gray-100 dark:border-gray-700 self-start rounded-bl-sm"
|
||||
? "bg-brand-500 text-white self-end rounded-br-sm animate-in fade-in duration-200 slide-in-from-bottom-1"
|
||||
: "bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-300 border border-gray-100 dark:border-gray-700 self-start rounded-bl-sm animate-in fade-in duration-200 slide-in-from-bottom-1"
|
||||
}`}
|
||||
>
|
||||
{msg.text}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isLoading && (
|
||||
<div className="bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 self-start rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm flex items-center gap-1.5 max-w-[80%]">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
|
||||
@@ -196,6 +286,13 @@ export default function ChatbotWidget({
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isFetchingHistory && !preCursor && (
|
||||
<div className="flex justify-center py-4">
|
||||
<div className="w-5 h-5 border-2 border-brand-500 border-t-transparent rounded-full animate-spin"></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
@@ -203,6 +300,7 @@ export default function ChatbotWidget({
|
||||
<div className="p-3 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="Nhập câu hỏi..."
|
||||
value={input}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
export default function ResponsiveImage() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="overflow-hidden">
|
||||
<Image
|
||||
src="/images/grid-image/image-01.png"
|
||||
alt="Cover"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={1054}
|
||||
height={600}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
export default function ThreeColumnImageGrid() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<div>
|
||||
<Image
|
||||
src="/images/grid-image/image-04.png"
|
||||
alt=" grid"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={338}
|
||||
height={192}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Image
|
||||
src="/images/grid-image/image-05.png"
|
||||
alt=" grid"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={338}
|
||||
height={192}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Image
|
||||
src="/images/grid-image/image-06.png"
|
||||
alt=" grid"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={338}
|
||||
height={192}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
export default function TwoColumnImageGrid() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
<div>
|
||||
<Image
|
||||
src="/images/grid-image/image-02.png"
|
||||
alt=" grid"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={517}
|
||||
height={295}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Image
|
||||
src="/images/grid-image/image-03.png"
|
||||
alt=" grid"
|
||||
className="w-full border border-gray-200 rounded-xl dark:border-gray-800"
|
||||
width={517}
|
||||
height={295}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from "react";
|
||||
import YouTubeEmbed from "./YouTubeEmbed";
|
||||
import ComponentCard from "@/components/common/ComponentCard";
|
||||
|
||||
export default function VideosExample() {
|
||||
return (
|
||||
<div>
|
||||
<div className="grid grid-cols-1 gap-5 sm:gap-6 xl:grid-cols-2">
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<ComponentCard title="Video Ratio 16:9">
|
||||
<YouTubeEmbed videoId="dQw4w9WgXcQ" />
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Video Ratio 4:3">
|
||||
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="4:3" />
|
||||
</ComponentCard>
|
||||
</div>
|
||||
<div className="space-y-5 sm:space-y-6">
|
||||
<ComponentCard title="Video Ratio 21:9">
|
||||
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="21:9" />
|
||||
</ComponentCard>
|
||||
<ComponentCard title="Video Ratio 1:1">
|
||||
<YouTubeEmbed videoId="dQw4w9WgXcQ" aspectRatio="1:1" />
|
||||
</ComponentCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<YouTubeEmbedProps> = ({
|
||||
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 (
|
||||
<div
|
||||
className={`overflow-hidden rounded-lg ${aspectRatioClass} ${className}`}
|
||||
>
|
||||
<iframe
|
||||
src={`https://www.youtube.com/embed/${videoId}`}
|
||||
title={title}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default YouTubeEmbed;
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function FourIsToThree() {
|
||||
return (
|
||||
<div className="aspect-4/3 overflow-hidden rounded-lg">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
||||
title="YouTube video"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function OneIsToOne() {
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg aspect-square">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
||||
title="YouTube video"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function SixteenIsToNine() {
|
||||
return (
|
||||
<div className="aspect-4/3 overflow-hidden rounded-lg">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
||||
title="YouTube video"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function TwentyOneIsToNine() {
|
||||
return (
|
||||
<div className="aspect-21/9 overflow-hidden rounded-lg">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
||||
title="YouTube video"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
className="w-full h-full"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -328,12 +328,12 @@ const AppSidebar: React.FC = () => {
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
<li>
|
||||
<Link
|
||||
href="/user/account"
|
||||
href="/user"
|
||||
className={`menu-item group ${
|
||||
isActive("/user/account") ? "menu-item-active" : "menu-item-icon-inactive"
|
||||
isActive("/user") ? "menu-item-active" : "menu-item-icon-inactive"
|
||||
} ${!isExpanded ? "lg:justify-center px-2" : "lg:justify-start"}`}
|
||||
>
|
||||
<span className={`menu-item-icon ${isActive("/user/account") ? "text-gray-950" : "text-gray-400"}`}>
|
||||
<span className={`menu-item-icon ${isActive("/user") ? "text-gray-950" : "text-gray-400"}`}>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M12 3.5C7.30558 3.5 3.5 7.30558 3.5 12C3.5 14.1526 4.3002 16.1184 5.61936 17.616C6.17279 15.3096 8.24852 13.5955 10.7246 13.5955H13.2746C15.7509 13.5955 17.8268 15.31 18.38 17.6167C19.6996 16.119 20.5 14.153 20.5 12C20.5 7.30558 16.6944 3.5 12 3.5ZM17.0246 18.8566V18.8455C17.0246 16.7744 15.3457 15.0955 13.2746 15.0955H10.7246C8.65354 15.0955 6.97461 16.7744 6.97461 18.8455V18.856C8.38223 19.8895 10.1198 20.5 12 20.5C13.8798 20.5 15.6171 19.8898 17.0246 18.8566ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM11.9991 7.25C10.8847 7.25 9.98126 8.15342 9.98126 9.26784C9.98126 10.3823 10.8847 11.2857 11.9991 11.2857C13.1135 11.2857 14.0169 10.3823 14.0169 9.26784C14.0169 8.15342 13.1135 7.25 11.9991 7.25ZM8.48126 9.26784C8.48126 7.32499 10.0563 5.75 11.9991 5.75C13.9419 5.75 15.5169 7.32499 15.5169 9.26784C15.5169 11.2107 13.9419 12.7857 11.9991 12.7857C10.0563 12.7857 8.48126 11.2107 8.48126 9.26784Z" fill="currentColor" />
|
||||
</svg>
|
||||
|
||||
@@ -3,6 +3,11 @@ import { API } from "../../api";
|
||||
import { ChatbotPayload, ChatbotResponse } from "@/interface/chatbot";
|
||||
|
||||
export const apiChatbot = async (payload: ChatbotPayload): Promise<ChatbotResponse> => {
|
||||
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<any> => {
|
||||
const response = await api.get(API.Chatbot.HISTORY, { params });
|
||||
return await response?.data;
|
||||
};
|
||||
Reference in New Issue
Block a user