Compare commits
No commits in common. "main" and "dev/ui-work" have entirely different histories.
main
...
dev/ui-wor
@ -1,5 +1,5 @@
|
|||||||
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
||||||
NEXT_PUBLIC_APPWRITE_PROJECT_ID=67e1445400053dca1d9b
|
NEXT_PUBLIC_APPWRITE_PROJECT_ID=67e1445400053dca1d9b
|
||||||
NEXT_PUBLIC_APPWRITE_DATABASE_ID=67e1452b00016444b37f
|
NEXT_PUBLIC_APPWRITE_DATABASE_ID=67e1452b00016444b37f
|
||||||
NEXT_PUBLIC_APPWRITE_COLLECTION_ID=681c367b0016f9ba0e8e
|
NEXT_PUBLIC_APPWRITE_COLLECTION_ID=67fe4029000f7e0a7b92
|
||||||
NEXT_PUBLIC_APPWRITE_USERS_COLLECTION_ID=681c35690038f9798152
|
NEXT_PUBLIC_APPWRITE_USERS_COLLECTION_ID=67ff72bd00370366ae3e
|
||||||
|
@ -6,6 +6,8 @@ import { useRouter } from "next/navigation";
|
|||||||
import { useTheme } from "../context/ThemeContext";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
import { account } from "../lib/appwrite";
|
import { account } from "../lib/appwrite";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
import { Bell } from "lucide-react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import { MdOutlineSettings, MdSupport } from "react-icons/md";
|
import { MdOutlineSettings, MdSupport } from "react-icons/md";
|
||||||
import { AiOutlineUser } from "react-icons/ai";
|
import { AiOutlineUser } from "react-icons/ai";
|
||||||
import NotificationBell from './NotificationBell';
|
import NotificationBell from './NotificationBell';
|
||||||
@ -66,44 +68,18 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, isCollapsed, setIsCollapsed }) =>
|
|||||||
console.error("Logout failed:", error);
|
console.error("Logout failed:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// ----------------notificatio logic------------------
|
||||||
if (darkMode === undefined) {
|
|
||||||
return (
|
|
||||||
<nav className="bg-white dark:bg-gray-900 shadow-md sticky top-0 w-full z-30 p-2 sm:p-4 flex items-center justify-between">
|
|
||||||
<div className="animate-pulse flex space-x-4">
|
|
||||||
<div className="h-10 w-10 rounded-lg bg-gray-200 dark:bg-gray-700"></div>
|
|
||||||
<div className="h-10 w-[300px] rounded bg-gray-200 dark:bg-gray-700 hidden md:block"></div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="h-8 w-8 rounded-full bg-gray-200 dark:bg-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={`
|
<nav className="bg-white dark:bg-gray-900 shadow-md sticky top-0 w-full z-30 p-2 sm:p-4 flex items-center justify-between">
|
||||||
${darkMode ? 'bg-gray-900 border-gray-700 shadow-gray-800/30' : 'bg-white border-gray-200 shadow-md'}
|
|
||||||
sticky top-0 w-full z-30
|
|
||||||
p-2 sm:p-4
|
|
||||||
flex items-center justify-between
|
|
||||||
transition-colors duration-300
|
|
||||||
`}>
|
|
||||||
{/* Left Side */}
|
{/* Left Side */}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{/* Hamburger Menu Button */}
|
{/* Hamburger Menu Button - Always visible */}
|
||||||
<button
|
<button
|
||||||
onClick={handleSidebarToggle}
|
onClick={handleSidebarToggle}
|
||||||
className={`
|
className={`flex h-10 w-10 items-center justify-center rounded-lg border border-gray-300 dark:border-gray-600 text-gray-500 dark:text-gray-300 mr-2 ${
|
||||||
flex h-10 w-10 items-center justify-center rounded-lg
|
sidebarOpen || isCollapsed ? 'bg-gray-100 dark:bg-gray-800' : ''
|
||||||
border ${darkMode ? 'border-gray-600 bg-gray-800' : 'border-gray-300 bg-white'}
|
}`}
|
||||||
${sidebarOpen || isCollapsed ? (darkMode ? 'bg-gray-700' : 'bg-gray-100') : ''}
|
|
||||||
text-gray-500 dark:text-gray-300
|
|
||||||
mr-2
|
|
||||||
transition-all duration-200
|
|
||||||
hover:bg-gray-100 dark:hover:bg-gray-700
|
|
||||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50
|
|
||||||
`}
|
|
||||||
aria-label="Toggle sidebar"
|
aria-label="Toggle sidebar"
|
||||||
>
|
>
|
||||||
{sidebarOpen || isCollapsed ? (
|
{sidebarOpen || isCollapsed ? (
|
||||||
@ -139,39 +115,17 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, isCollapsed, setIsCollapsed }) =>
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Search Box */}
|
{/* Search Box - Hidden on mobile */}
|
||||||
<div className={`
|
<div className="hidden md:flex items-center gap-[2px] border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 p-2 rounded h-[45px] w-[300px] lg:w-[400px] ml-2">
|
||||||
hidden md:flex items-center gap-[2px]
|
|
||||||
border ${darkMode ? 'border-gray-600 bg-gray-800' : 'border-gray-300 bg-white'}
|
|
||||||
p-2 rounded-lg
|
|
||||||
h-[45px] w-[300px] lg:w-[400px] ml-2
|
|
||||||
transition-colors duration-300
|
|
||||||
focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50
|
|
||||||
${darkMode ? 'focus-within:border-gray-500' : 'focus-within:border-blue-500'}
|
|
||||||
`}>
|
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<FaSearch className={`
|
<FaSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
|
||||||
absolute left-3 top-1/2 transform -translate-y-1/2
|
|
||||||
${darkMode ? 'text-gray-400' : 'text-gray-500'}
|
|
||||||
`} />
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search or type command..."
|
placeholder="Search or type command..."
|
||||||
className={`
|
className="w-full bg-transparent border-none pl-10 pr-10 py-2 focus:outline-none text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500"
|
||||||
w-full bg-transparent border-none
|
|
||||||
pl-10 pr-10 py-2
|
|
||||||
focus:outline-none
|
|
||||||
${darkMode ? 'text-gray-200 placeholder-gray-500' : 'text-gray-800 placeholder-gray-400'}
|
|
||||||
text-sm
|
|
||||||
`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`
|
<div className="px-2 py-1 bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-300 text-xs rounded-md">
|
||||||
px-2 py-1
|
|
||||||
${darkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-100 text-gray-500'}
|
|
||||||
text-xs rounded-md
|
|
||||||
font-mono
|
|
||||||
`}>
|
|
||||||
⌘K
|
⌘K
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -180,26 +134,14 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, isCollapsed, setIsCollapsed }) =>
|
|||||||
{/* Right Side */}
|
{/* Right Side */}
|
||||||
<div className="flex items-center gap-2 sm:gap-4">
|
<div className="flex items-center gap-2 sm:gap-4">
|
||||||
{/* Mobile Search Button */}
|
{/* Mobile Search Button */}
|
||||||
<button className={`
|
<button className="md:hidden p-2 rounded-full text-gray-600 dark:text-gray-300 hover:text-gray-800 dark:hover:text-white">
|
||||||
md:hidden p-2 rounded-full
|
|
||||||
${darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-800'}
|
|
||||||
hover:bg-gray-200 dark:hover:bg-gray-700
|
|
||||||
transition-colors duration-200
|
|
||||||
`}>
|
|
||||||
<FaSearch className="text-lg" />
|
<FaSearch className="text-lg" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Dark Mode Toggle */}
|
{/* Dark Mode Toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={toggleDarkMode}
|
onClick={toggleDarkMode}
|
||||||
className={`
|
className="p-2 rounded-full border-2 border-gray-300 dark:border-gray-600 flex items-center justify-center w-8 h-8 sm:w-10 sm:h-10 transition duration-300 hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
p-2 rounded-full
|
|
||||||
${darkMode ? 'bg-gray-700 hover:bg-gray-600' : 'bg-gray-100 hover:bg-gray-200'}
|
|
||||||
flex items-center justify-center
|
|
||||||
w-8 h-8 sm:w-10 sm:h-10
|
|
||||||
transition-all duration-200
|
|
||||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50
|
|
||||||
`}
|
|
||||||
aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"}
|
aria-label={darkMode ? "Switch to light mode" : "Switch to dark mode"}
|
||||||
>
|
>
|
||||||
{darkMode ? (
|
{darkMode ? (
|
||||||
@ -232,20 +174,13 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, isCollapsed, setIsCollapsed }) =>
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Notifications */}
|
{/* Notifications */}
|
||||||
<NotificationBell darkMode={darkMode} />
|
<NotificationBell />
|
||||||
|
|
||||||
{/* User Profile */}
|
{/* User Profile */}
|
||||||
<div className="relative" ref={dropdownRef}>
|
<div className="relative" ref={dropdownRef}>
|
||||||
<button
|
<button
|
||||||
onClick={toggleDropdown}
|
onClick={toggleDropdown}
|
||||||
className={`
|
className="flex items-center gap-1 sm:gap-2 cursor-pointer hover:text-blue-500 dark:hover:text-blue-400"
|
||||||
flex items-center gap-1 sm:gap-2
|
|
||||||
cursor-pointer
|
|
||||||
hover:text-blue-500 dark:hover:text-blue-400
|
|
||||||
transition-colors duration-200
|
|
||||||
p-1 rounded-full
|
|
||||||
${dropdownOpen ? (darkMode ? 'bg-gray-700' : 'bg-gray-100') : ''}
|
|
||||||
`}
|
|
||||||
aria-label="User menu"
|
aria-label="User menu"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -253,113 +188,49 @@ const Navbar = ({ sidebarOpen, setSidebarOpen, isCollapsed, setIsCollapsed }) =>
|
|||||||
alt="User profile"
|
alt="User profile"
|
||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
className={`
|
className="rounded-full w-8 h-8 sm:w-10 sm:h-10"
|
||||||
rounded-full w-8 h-8 sm:w-10 sm:h-10
|
|
||||||
border-2 ${darkMode ? 'border-gray-600' : 'border-gray-200'}
|
|
||||||
transition-all duration-200
|
|
||||||
${dropdownOpen ? 'ring-2 ring-blue-500' : ''}
|
|
||||||
`}
|
|
||||||
/>
|
/>
|
||||||
<span className={`
|
<span className="hidden sm:inline text-gray-700 dark:text-gray-200">
|
||||||
hidden sm:inline
|
|
||||||
${darkMode ? 'text-gray-500' : 'text-gray-700'}
|
|
||||||
text-sm font-medium
|
|
||||||
`}>
|
|
||||||
{currentUser?.name || "User"}
|
{currentUser?.name || "User"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{dropdownOpen && currentUser && (
|
{dropdownOpen && currentUser && (
|
||||||
<div className={`
|
<div className="absolute right-0 mt-2 w-56 sm:w-64 bg-white dark:bg-gray-800 shadow-lg rounded-lg p-2 sm:p-4 z-50 border border-gray-200 dark:border-gray-700">
|
||||||
absolute right-0 mt-2 w-56 sm:w-64
|
|
||||||
${darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200'}
|
|
||||||
shadow-lg rounded-lg
|
|
||||||
p-2 sm:p-4
|
|
||||||
z-50 border
|
|
||||||
transition-all duration-200
|
|
||||||
origin-top-right
|
|
||||||
animate-scaleIn
|
|
||||||
`}>
|
|
||||||
{/* User Info */}
|
{/* User Info */}
|
||||||
<div className="mb-2 sm:mb-3">
|
<div className="mb-2 sm:mb-3">
|
||||||
<h3 className={`
|
<h3 className="font-semibold text-gray-800 dark:text-gray-200 text-sm sm:text-base">
|
||||||
font-semibold
|
|
||||||
${darkMode ? 'text-gray-800' : 'text-gray-500'}
|
|
||||||
text-sm sm:text-base
|
|
||||||
`}>
|
|
||||||
{currentUser.name || "User Name"}
|
{currentUser.name || "User Name"}
|
||||||
</h3>
|
</h3>
|
||||||
<p className={`
|
<p className="text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||||||
text-xs sm:text-sm
|
|
||||||
${darkMode ? 'text-gray-400' : 'text-gray-500'}
|
|
||||||
truncate
|
|
||||||
`}>
|
|
||||||
{currentUser.email}
|
{currentUser.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className={`
|
<hr className="border-gray-200 dark:border-gray-700 my-1 sm:my-2" />
|
||||||
${darkMode ? 'border-gray-700' : 'border-gray-200'}
|
|
||||||
my-1 sm:my-2
|
|
||||||
`} />
|
|
||||||
|
|
||||||
{/* Menu Items */}
|
{/* Menu Items */}
|
||||||
<div className="space-y-1 sm:space-y-2">
|
<div className="space-y-1 sm:space-y-2">
|
||||||
<button className={`
|
<button className="flex items-center gap-2 w-full text-left p-1 sm:p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md text-sm sm:text-base text-gray-700 dark:text-gray-200">
|
||||||
flex items-center gap-2 w-full text-left
|
<AiOutlineUser className="text-gray-500 dark:text-gray-400" />
|
||||||
p-1 sm:p-2
|
|
||||||
${darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}
|
|
||||||
rounded-md
|
|
||||||
text-sm sm:text-base
|
|
||||||
${darkMode ? 'text-gray-200' : 'text-gray-700'}
|
|
||||||
transition-colors duration-200
|
|
||||||
`}>
|
|
||||||
<AiOutlineUser className={darkMode ? 'text-gray-400' : 'text-gray-500'} />
|
|
||||||
Edit profile
|
Edit profile
|
||||||
</button>
|
</button>
|
||||||
<button className={`
|
<button className="flex items-center gap-2 w-full text-left p-1 sm:p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md text-sm sm:text-base text-gray-700 dark:text-gray-200">
|
||||||
flex items-center gap-2 w-full text-left
|
<MdOutlineSettings className="text-gray-500 dark:text-gray-400" />
|
||||||
p-1 sm:p-2
|
|
||||||
${darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}
|
|
||||||
rounded-md
|
|
||||||
text-sm sm:text-base
|
|
||||||
${darkMode ? 'text-gray-200' : 'text-gray-700'}
|
|
||||||
transition-colors duration-200
|
|
||||||
`}>
|
|
||||||
<MdOutlineSettings className={darkMode ? 'text-gray-400' : 'text-gray-500'} />
|
|
||||||
Account settings
|
Account settings
|
||||||
</button>
|
</button>
|
||||||
<button className={`
|
<button className="flex items-center gap-2 w-full text-left p-1 sm:p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md text-sm sm:text-base text-gray-700 dark:text-gray-200">
|
||||||
flex items-center gap-2 w-full text-left
|
<MdSupport className="text-gray-500 dark:text-gray-400" />
|
||||||
p-1 sm:p-2
|
|
||||||
${darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}
|
|
||||||
rounded-md
|
|
||||||
text-sm sm:text-base
|
|
||||||
${darkMode ? 'text-gray-200' : 'text-gray-700'}
|
|
||||||
transition-colors duration-200
|
|
||||||
`}>
|
|
||||||
<MdSupport className={darkMode ? 'text-gray-400' : 'text-gray-500'} />
|
|
||||||
Support
|
Support
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className={`
|
<hr className="border-gray-200 dark:border-gray-700 my-1 sm:my-2" />
|
||||||
${darkMode ? 'border-gray-700' : 'border-gray-200'}
|
|
||||||
my-1 sm:my-2
|
|
||||||
`} />
|
|
||||||
|
|
||||||
{/* Sign Out */}
|
{/* Sign Out */}
|
||||||
<button
|
<button
|
||||||
onClick={handleSignOut}
|
onClick={handleSignOut}
|
||||||
className={`
|
className="flex items-center gap-2 w-full text-left p-1 sm:p-2 text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md text-sm sm:text-base"
|
||||||
flex items-center gap-2 w-full text-left
|
|
||||||
p-1 sm:p-2
|
|
||||||
text-red-500 hover:text-red-600 dark:hover:text-red-400
|
|
||||||
${darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}
|
|
||||||
rounded-md
|
|
||||||
text-sm sm:text-base
|
|
||||||
transition-colors duration-200
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<FiLogOut />
|
<FiLogOut />
|
||||||
Sign out
|
Sign out
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { usePathname, useRouter } from "next/navigation";
|
||||||
import { FileText } from "lucide-react";
|
import { FileText, Moon, Sun } from "lucide-react";
|
||||||
|
import { FaLock, FaChevronDown, FaChevronUp } from "react-icons/fa";
|
||||||
import { useTheme } from "../context/ThemeContext";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
import { account } from "../lib/appwrite";
|
|
||||||
|
|
||||||
const Sidebar = ({ sidebarOpen, setSidebarOpen, isCollapsed }) => {
|
const Sidebar = ({ sidebarOpen, setSidebarOpen, isCollapsed }) => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@ -25,51 +25,10 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, isCollapsed }) => {
|
|||||||
setSidebarOpen(false);
|
setSidebarOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleLogout = async () => {
|
const handleLogout = () => {
|
||||||
try {
|
localStorage.removeItem("token"); // or account.deleteSession("current") if using Appwrite
|
||||||
await account.deleteSession("current");
|
router.push("/signup");
|
||||||
localStorage.removeItem("token");
|
|
||||||
// Force a full page reload to reset all state
|
|
||||||
window.location.href = "/";
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Logout failed:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------logo handling in dark mode-----------------------
|
|
||||||
// Create a separate LogoImage component for better error handling
|
|
||||||
function LogoImage({ darkSrc, lightSrc, darkMode, className, alt }) {
|
|
||||||
const [imgSrc, setImgSrc] = useState(darkMode ? darkSrc : lightSrc);
|
|
||||||
const [errored, setErrored] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setImgSrc(darkMode ? darkSrc : lightSrc);
|
|
||||||
setErrored(false); // Reset error state when mode changes
|
|
||||||
}, [darkMode, darkSrc, lightSrc]);
|
|
||||||
|
|
||||||
const handleError = () => {
|
|
||||||
if (!errored) {
|
|
||||||
// Try falling back to opposite mode's image
|
|
||||||
setImgSrc(darkMode ? lightSrc : darkSrc);
|
|
||||||
setErrored(true);
|
|
||||||
} else {
|
|
||||||
// Ultimate fallback to placeholder
|
|
||||||
setImgSrc('/images/logo/placeholder.svg');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={imgSrc}
|
|
||||||
alt={alt}
|
|
||||||
className={className}
|
|
||||||
onError={handleError}
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={`fixed lg:relative z-50 h-screen transition-all duration-300 ${darkMode ? 'bg-gray-800 text-gray-200' : 'bg-white text-gray-900'
|
className={`fixed lg:relative z-50 h-screen transition-all duration-300 ${darkMode ? 'bg-gray-800 text-gray-200' : 'bg-white text-gray-900'
|
||||||
@ -78,7 +37,21 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, isCollapsed }) => {
|
|||||||
>
|
>
|
||||||
<div className="h-full flex flex-col border-r border-gray-200 p-4 overflow-y-auto scrollbar-hide">
|
<div className="h-full flex flex-col border-r border-gray-200 p-4 overflow-y-auto scrollbar-hide">
|
||||||
{/* Logo Section */}
|
{/* Logo Section */}
|
||||||
|
<div className={`flex items-center gap-2 mb-6 ${isCollapsed ? 'justify-center' : ''}`}>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<img
|
||||||
|
src={darkMode ? "/images/logo/logo-icon-white.svg" : "/images/logo/logo-icon.svg"}
|
||||||
|
alt="Logo"
|
||||||
|
className="w-8"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={darkMode ? "/images/logo/logo-white.svg" : "/images/logo/logo.svg"}
|
||||||
|
alt="Logo"
|
||||||
|
className="h-8"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* MENU section - Only show when not collapsed */}
|
{/* MENU section - Only show when not collapsed */}
|
||||||
{!isCollapsed && (
|
{!isCollapsed && (
|
||||||
@ -98,23 +71,20 @@ const Sidebar = ({ sidebarOpen, setSidebarOpen, isCollapsed }) => {
|
|||||||
? "bg-blue-900 text-blue-200"
|
? "bg-blue-900 text-blue-200"
|
||||||
: "bg-blue-100 text-blue-800"
|
: "bg-blue-100 text-blue-800"
|
||||||
: darkMode
|
: darkMode
|
||||||
? "hover:bg-gray-700 text-gray-200"
|
? "hover:bg-gray-700"
|
||||||
: "hover:bg-gray-200"
|
: "hover:bg-gray-200"
|
||||||
} ${isCollapsed ? 'justify-center' : ''}`}
|
} ${isCollapsed ? 'justify-center' : ''}`}
|
||||||
onClick={closeSidebarOnMobile}
|
onClick={closeSidebarOnMobile}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={darkMode ? "/images/icons/grid.svg" : "/images/icons/grid.svg"}
|
src={darkMode ? "/images/icons/grid-white.svg" : "/images/icons/grid.svg"}
|
||||||
alt="Dashboard"
|
alt="Dashboard"
|
||||||
className={`w-5 ${darkMode ? 'filter brightness-0 invert' : ''}`}
|
width={18}
|
||||||
onError={(e) => {
|
className="w-5"
|
||||||
e.target.src = darkMode ? "/images/icons/grid.svg" : "/images/icons/grid.svg";
|
|
||||||
e.target.className = 'w-5'; // Remove filter if fallback
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{!isCollapsed && "Dashboard"}
|
{!isCollapsed && "Dashboard"}
|
||||||
</Link>
|
</Link>
|
||||||
{/* -----------singleBooking/Add token-------------- */}
|
{/* -----------singleBooking-------------- */}
|
||||||
<Link
|
<Link
|
||||||
href="/pages/SingleBooking"
|
href="/pages/SingleBooking"
|
||||||
className={`flex items-center gap-2 p-3 rounded-md text-sm font-semibold transition-colors ${isActive("/pages/UserProfile")
|
className={`flex items-center gap-2 p-3 rounded-md text-sm font-semibold transition-colors ${isActive("/pages/UserProfile")
|
||||||
|
@ -55,15 +55,15 @@ export default function TokenTable({ statusFilter }) {
|
|||||||
|
|
||||||
const applyFilters = (data, search) => {
|
const applyFilters = (data, search) => {
|
||||||
let filtered = data.filter((entry) => {
|
let filtered = data.filter((entry) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
entry.patientName.toLowerCase().includes(search.toLowerCase()) ||
|
entry.patientName.toLowerCase().includes(search.toLowerCase()) ||
|
||||||
entry.tokenNumber.toString().includes(search);
|
entry.tokenNumber.toString().includes(search);
|
||||||
|
|
||||||
const matchesFilter =
|
const matchesFilter =
|
||||||
statusFilter === "all" ||
|
statusFilter === "all" ||
|
||||||
entry.status === statusFilter ||
|
entry.status === statusFilter ||
|
||||||
(statusFilter === "booked" && entry.status === "booked");
|
(statusFilter === "booked" && entry.status === "booked");
|
||||||
|
|
||||||
return matchesSearch && matchesFilter;
|
return matchesSearch && matchesFilter;
|
||||||
});
|
});
|
||||||
setFilteredEntries(filtered);
|
setFilteredEntries(filtered);
|
||||||
@ -94,136 +94,86 @@ export default function TokenTable({ statusFilter }) {
|
|||||||
const currentEntries = filteredEntries.slice(indexOfFirstEntry, indexOfLastEntry);
|
const currentEntries = filteredEntries.slice(indexOfFirstEntry, indexOfLastEntry);
|
||||||
const totalPages = Math.ceil(filteredEntries.length / entriesPerPage);
|
const totalPages = Math.ceil(filteredEntries.length / entriesPerPage);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Handle theme loading state
|
|
||||||
if (darkMode === undefined) {
|
|
||||||
return (
|
|
||||||
<div className={`flex items-center justify-center min-h-[200px] ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
|
||||||
<div className="text-center">
|
|
||||||
<div
|
|
||||||
className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${darkMode === true ? "border-gray-100" : "border-blue-400"
|
|
||||||
}`}
|
|
||||||
></div>
|
|
||||||
<p className={`mt-4 text-lg font-semibold ${darkMode === true ? 'text-gray-100' : 'text-gray-900'
|
|
||||||
}`}>
|
|
||||||
Loading...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (loading) return (
|
if (loading) return (
|
||||||
<div className={`text-center py-10 ${darkMode ? 'bg-gray-900' : 'bg-white'}`}>
|
<div className="text-center mt-50">
|
||||||
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 mx-auto ${darkMode ? 'border-gray-100' : 'border-blue-400'
|
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto`}></div>
|
||||||
}`}></div>
|
<p className={`mt-4 text-lg font-semibold`}>Loading...</p>
|
||||||
<p className={`mt-4 text-lg font-semibold ${darkMode ? 'text-gray-100' : 'text-gray-900'
|
|
||||||
}`}>
|
|
||||||
Loading...
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (error) return (
|
if (error) return <div className="p-4 text-red-500">Error: {error}</div>;
|
||||||
<div className={`p-4 rounded ${darkMode ? 'bg-red-900 text-red-100' : 'bg-red-100 text-red-900'}`}>
|
|
||||||
Error: {error}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`transition-colors duration-300 ${darkMode ? 'text-gray-100' : 'text-gray-800'}`}>
|
<>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by token or name"
|
placeholder="Search by token or name"
|
||||||
className={`mb-4 p-2 border rounded w-full max-w-sm transition-colors ${darkMode
|
className="mb-4 p-2 border rounded bg-white w-full max-w-sm"
|
||||||
? 'bg-gray-800 border-gray-700 text-white placeholder-gray-400 focus:ring-blue-500 focus:border-blue-500'
|
|
||||||
: 'bg-white border-gray-300 placeholder-gray-500 focus:ring-blue-500 focus:border-blue-500'
|
|
||||||
}`}
|
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{filteredEntries.length === 0 ? (
|
{filteredEntries.length === 0 ? (
|
||||||
<div className={`p-4 rounded ${darkMode ? 'bg-gray-800' : 'bg-gray-50'}`}>
|
<div className="p-4">No entries found.</div>
|
||||||
No entries found.
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="overflow-x-auto">
|
<table className="w-full border-collapse">
|
||||||
<table className="w-full border-collapse">
|
<thead className={darkMode ? "bg-gray-800" : "bg-gray-100"}>
|
||||||
<thead className={darkMode ? "bg-gray-800" : "bg-gray-100"}>
|
<tr>
|
||||||
<tr>
|
<th className="p-3 text-left">Token</th>
|
||||||
<th className={`p-3 text-left ${darkMode ? 'text-gray-200' : 'text-gray-700'}`}>Token</th>
|
<th className="p-3 text-left">Name</th>
|
||||||
<th className={`p-3 text-left ${darkMode ? 'text-gray-200' : 'text-gray-700'}`}>Name</th>
|
<th className="p-3 text-left">Status</th>
|
||||||
<th className={`p-3 text-left ${darkMode ? 'text-gray-200' : 'text-gray-700'}`}>Status</th>
|
<th className="p-3 text-left">Actions</th>
|
||||||
<th className={`p-3 text-left ${darkMode ? 'text-gray-200' : 'text-gray-700'}`}>Actions</th>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{currentEntries.map((entry) => (
|
||||||
|
<tr
|
||||||
|
key={entry.$id}
|
||||||
|
className={`border ${darkMode ? 'border-gray-700' : 'border-gray-200'}`}
|
||||||
|
>
|
||||||
|
<td className="p-3 font-mono">{entry.tokenNumber}</td>
|
||||||
|
<td className="p-3">{entry.patientName}</td>
|
||||||
|
<td className="p-3">
|
||||||
|
<span
|
||||||
|
className={`px-2 py-1 rounded-full text-xs ${entry.status === "done"
|
||||||
|
? "bg-green-100 text-green-800"
|
||||||
|
: entry.status === "booked"
|
||||||
|
? "bg-yellow-100 text-yellow-800"
|
||||||
|
: "bg-red-100 text-red-800"
|
||||||
|
} font-semibold`}
|
||||||
|
>
|
||||||
|
{entry.status === "booked" ? "In-Queue" : entry.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="p-3 flex gap-2">
|
||||||
|
{entry.status === "booked" && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => updateStatus(entry.$id, "done")}
|
||||||
|
className="border border-green-600 text-green-600 rounded-full px-2 py-1 text-xs hover:bg-green-50"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => updateStatus(entry.$id, "missed")}
|
||||||
|
className="border border-red-600 text-red-600 rounded-full px-2 py-1 text-xs hover:bg-red-50"
|
||||||
|
>
|
||||||
|
Missed
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
))}
|
||||||
<tbody>
|
</tbody>
|
||||||
{currentEntries.map((entry) => (
|
</table>
|
||||||
<tr
|
|
||||||
key={entry.$id}
|
|
||||||
className={`${darkMode ? 'bg-gray-900 border-gray-700 hover:bg-gray-800' : 'bg-white border-gray-200 hover:bg-gray-50'}`}
|
|
||||||
>
|
|
||||||
<td className="p-3 font-mono">{entry.tokenNumber}</td>
|
|
||||||
<td className="p-3">{entry.patientName}</td>
|
|
||||||
<td className="p-3">
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 rounded-full text-xs font-semibold ${entry.status === "done"
|
|
||||||
? darkMode
|
|
||||||
? "bg-green-900 text-green-100"
|
|
||||||
: "bg-green-100 text-green-800"
|
|
||||||
: entry.status === "booked"
|
|
||||||
? darkMode
|
|
||||||
? "bg-yellow-900 text-yellow-100"
|
|
||||||
: "bg-yellow-100 text-yellow-800"
|
|
||||||
: darkMode
|
|
||||||
? "bg-red-900 text-red-100"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{entry.status === "booked" ? "In-Queue" : entry.status}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="p-3 flex gap-2">
|
|
||||||
{entry.status === "booked" && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={() => updateStatus(entry.$id, "done")}
|
|
||||||
className={`border rounded-full px-2 py-1 text-xs transition-colors ${darkMode
|
|
||||||
? 'border-green-400 text-green-400 hover:bg-green-900'
|
|
||||||
: 'border-green-600 text-green-600 hover:bg-green-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => updateStatus(entry.$id, "missed")}
|
|
||||||
className={`border rounded-full px-2 py-1 text-xs transition-colors ${darkMode
|
|
||||||
? 'border-red-400 text-red-400 hover:bg-red-900'
|
|
||||||
: 'border-red-600 text-red-600 hover:bg-red-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Missed
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={`flex justify-between items-center mt-4 p-2 rounded ${darkMode ? 'bg-gray-800' : 'bg-gray-100'
|
<div className={`flex justify-between items-center mt-4 ${darkMode ? 'text-white' : 'text-gray-700'}`}>
|
||||||
}`}>
|
|
||||||
<button
|
<button
|
||||||
disabled={currentPage === 1}
|
disabled={currentPage === 1}
|
||||||
onClick={() => setCurrentPage((prev) => prev - 1)}
|
onClick={() => setCurrentPage((prev) => prev - 1)}
|
||||||
className={`px-3 py-1 border rounded disabled:opacity-50 transition-colors ${darkMode
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
? 'border-gray-600 hover:bg-gray-700 disabled:hover:bg-transparent'
|
|
||||||
: 'border-gray-300 hover:bg-gray-200 disabled:hover:bg-transparent'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Previous
|
Previous
|
||||||
</button>
|
</button>
|
||||||
@ -233,16 +183,13 @@ export default function TokenTable({ statusFilter }) {
|
|||||||
<button
|
<button
|
||||||
disabled={currentPage === totalPages}
|
disabled={currentPage === totalPages}
|
||||||
onClick={() => setCurrentPage((prev) => prev + 1)}
|
onClick={() => setCurrentPage((prev) => prev + 1)}
|
||||||
className={`px-3 py-1 border rounded disabled:opacity-50 transition-colors ${darkMode
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
? 'border-gray-600 hover:bg-gray-700 disabled:hover:bg-transparent'
|
|
||||||
: 'border-gray-300 hover:bg-gray-200 disabled:hover:bg-transparent'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
92
src/app/components/partials/alert/alert-error.js
Normal file
92
src/app/components/partials/alert/alert-error.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
export default function SuccessMessage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* First Message with Link */}
|
||||||
|
<div
|
||||||
|
className="rounded-xl border p-4"
|
||||||
|
style={{
|
||||||
|
borderColor: '#f04438',
|
||||||
|
backgroundColor: '#fef3f2',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5" style={{ color: '#3b82f6' }}>
|
||||||
|
<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="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-gray-800 dark:text-white/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
You can insert a description for the message here. The text relates
|
||||||
|
to the action that has been performed.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-3 inline-block text-sm font-medium underline"
|
||||||
|
style={{ color: '#f87171' }} // optional: changed to a more visible red
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second Message without Link */}
|
||||||
|
<div
|
||||||
|
className="rounded-xl border p-4 mt-4"
|
||||||
|
style={{
|
||||||
|
borderColor: '#f04438',
|
||||||
|
backgroundColor: '#fef3f2',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5" style={{ color: '#3b82f6' }}>
|
||||||
|
<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="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-gray-800 dark:text-white/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
You can insert a description for the message here. The text relates
|
||||||
|
to the action that has been performed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
84
src/app/components/partials/alert/alert-info.js
Normal file
84
src/app/components/partials/alert/alert-info.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const AlertInfo = () => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 p-4">
|
||||||
|
{/* First Alert with Learn More link */}
|
||||||
|
<div className="rounded-xl border border-[#36bffa] bg-[#f0f9ff] p-4 dark:border-[#36bffa]/30 dark:bg-[#36bffa]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#36bffa]">
|
||||||
|
<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="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1d2939] dark:text-white/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98a2b3]">
|
||||||
|
You can insert a description for the message here.The text relates to
|
||||||
|
the action that has been performed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-3 inline-block text-sm font-medium text-[#667085] underline dark:text-[#98a2b3]"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Second Alert without Learn More link */}
|
||||||
|
<div className="rounded-xl border border-[#36bffa] bg-[#f0f9ff] p-4 dark:border-[#36bffa]/30 dark:bg-[#36bffa]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#36bffa]">
|
||||||
|
<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="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1d2939] dark:text-white/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98a2b3]">
|
||||||
|
You can insert a description for the message here.The text relates to
|
||||||
|
the action that has been performed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlertInfo;
|
78
src/app/components/partials/alert/alert-success.js
Normal file
78
src/app/components/partials/alert/alert-success.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// app/alert-success/page.js
|
||||||
|
export default function AlertSuccessPage() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 p-6">
|
||||||
|
{/* Alert 1 */}
|
||||||
|
<div className="rounded-xl border border-[#12B76A] bg-[#ECFDF3] p-4 dark:border-[#12B76A]/30 dark:bg-[#12B76A]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#12B76A]">
|
||||||
|
<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.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"
|
||||||
|
fill="#12B76A"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1D2939] dark:text-[#FFFFFF]/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98A2B3]">
|
||||||
|
You can insert a description for the message here. The text relates to
|
||||||
|
the action that has been performed.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-3 inline-block text-sm font-medium text-[#667085] underline dark:text-[#98A2B3]"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Alert 2 (without link) */}
|
||||||
|
<div className="rounded-xl border border-[#12B76A] bg-[#ECFDF3] p-4 dark:border-[#12B76A]/30 dark:bg-[#12B76A]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#12B76A]">
|
||||||
|
<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.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"
|
||||||
|
fill="#12B76A"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1D2939] dark:text-[#FFFFFF]/90">
|
||||||
|
Success Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98A2B3]">
|
||||||
|
You can insert a description for the message here. The text relates to
|
||||||
|
the action that has been performed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
79
src/app/components/partials/alert/alert-warning.js
Normal file
79
src/app/components/partials/alert/alert-warning.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function AlertWarning() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 p-6">
|
||||||
|
{/* Alert 1 */}
|
||||||
|
<div className="rounded-xl border border-[#F79009] bg-[#FFFAEB] p-4 dark:border-[#F79009]/30 dark:bg-[#F79009]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#F79009] dark:text-[#FDB022]">
|
||||||
|
<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="#F79009"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1D2939] dark:text-[#FFFFFF]/90">
|
||||||
|
Warning Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98A2B3]">
|
||||||
|
You can insert a description for the message here. The text
|
||||||
|
relates to the action that has been performed.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="mt-3 inline-block text-sm font-medium text-[#667085] underline dark:text-[#98A2B3]"
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Alert 2 */}
|
||||||
|
<div className="rounded-xl border border-[#F79009] bg-[#FFFAEB] p-4 dark:border-[#F79009]/30 dark:bg-[#F79009]/15">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="-mt-0.5 text-[#F79009] dark:text-[#FDB022]">
|
||||||
|
<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="#F79009"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-1 text-sm font-semibold text-[#1D2939] dark:text-[#FFFFFF]/90">
|
||||||
|
Warning Message
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-[#667085] dark:text-[#98A2B3]">
|
||||||
|
You can insert a description for the message here. The text
|
||||||
|
relates to the action that has been performed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
32
src/app/components/partials/avatar/avatar01.js
Normal file
32
src/app/components/partials/avatar/avatar01.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// components/partials/avatar/Avatar01.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
const Avatar01 = () => {
|
||||||
|
const sizes = [
|
||||||
|
{ container: 'h-6 w-6', max: 'max-w-6' },
|
||||||
|
{ container: 'h-8 w-8', max: 'max-w-8' },
|
||||||
|
{ container: 'h-10 w-10', max: 'max-w-10' },
|
||||||
|
{ container: 'h-12 w-12', max: 'max-w-12' },
|
||||||
|
{ container: 'h-14 w-14', max: 'max-w-14' },
|
||||||
|
{ container: 'h-16 w-16', max: 'max-w-16' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||||
|
{sizes.map((size, index) => (
|
||||||
|
<div key={index} className={`relative ${size.container} ${size.max} rounded-full`}>
|
||||||
|
<Image
|
||||||
|
src="/images/user/user-01.jpg"
|
||||||
|
alt="user"
|
||||||
|
width={parseInt(size.container.split('-')[1]) * 4} // Convert to pixels
|
||||||
|
height={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
className="overflow-hidden rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar01;
|
35
src/app/components/partials/avatar/avatar02.js
Normal file
35
src/app/components/partials/avatar/avatar02.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// components/partials/avatar/Avatar02.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
const Avatar02 = () => {
|
||||||
|
const sizes = [
|
||||||
|
{ container: 'h-6 w-6', indicator: 'h-1.5 w-1.5' },
|
||||||
|
{ container: 'h-8 w-8', indicator: 'h-2 w-2' },
|
||||||
|
{ container: 'h-10 w-10', indicator: 'h-2.5 w-2.5' },
|
||||||
|
{ container: 'h-12 w-12', indicator: 'h-3 w-3' },
|
||||||
|
{ container: 'h-14 w-14', indicator: 'h-3.5 w-3.5' },
|
||||||
|
{ container: 'h-16 w-16', indicator: 'h-4 w-4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||||
|
{sizes.map((size, index) => (
|
||||||
|
<div key={index} className={`relative ${size.container} rounded-full`}>
|
||||||
|
<Image
|
||||||
|
src="/images/user/user-01.jpg"
|
||||||
|
alt="user"
|
||||||
|
width={parseInt(size.container.split('-')[1]) * 4} // Convert tailwind size to pixels
|
||||||
|
height={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
className="overflow-hidden rounded-full"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`absolute bottom-0 right-0 ${size.indicator} rounded-full border-[1.5px] border-white bg-[#12b76a] dark:border-gray-900`}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar02;
|
35
src/app/components/partials/avatar/avatar03.js
Normal file
35
src/app/components/partials/avatar/avatar03.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// components/partials/avatar/Avatar03.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
const Avatar03 = () => {
|
||||||
|
const sizes = [
|
||||||
|
{ container: 'h-6 w-6', indicator: 'h-1.5 w-1.5' },
|
||||||
|
{ container: 'h-8 w-8', indicator: 'h-2 w-2' },
|
||||||
|
{ container: 'h-10 w-10', indicator: 'h-2.5 w-2.5' },
|
||||||
|
{ container: 'h-12 w-12', indicator: 'h-3 w-3' },
|
||||||
|
{ container: 'h-14 w-14', indicator: 'h-3.5 w-3.5' },
|
||||||
|
{ container: 'h-16 w-16', indicator: 'h-4 w-4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||||
|
{sizes.map((size, index) => (
|
||||||
|
<div key={index} className={`relative ${size.container} rounded-full`}>
|
||||||
|
<Image
|
||||||
|
src="/images/user/user-01.jpg"
|
||||||
|
alt="user"
|
||||||
|
width={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
height={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
className="overflow-hidden rounded-full"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`absolute bottom-0 right-0 ${size.indicator} rounded-full border-[1.5px] border-white bg-[#f04438] dark:border-gray-900`}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar03;
|
35
src/app/components/partials/avatar/avatar04.js
Normal file
35
src/app/components/partials/avatar/avatar04.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// components/partials/avatar/Avatar04.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
const Avatar04 = () => {
|
||||||
|
const sizes = [
|
||||||
|
{ container: 'h-6 w-6', indicator: 'h-1.5 w-1.5' },
|
||||||
|
{ container: 'h-8 w-8', indicator: 'h-2 w-2' },
|
||||||
|
{ container: 'h-10 w-10', indicator: 'h-2.5 w-2.5' },
|
||||||
|
{ container: 'h-12 w-12', indicator: 'h-3 w-3' },
|
||||||
|
{ container: 'h-14 w-14', indicator: 'h-3.5 w-3.5' },
|
||||||
|
{ container: 'h-16 w-16', indicator: 'h-4 w-4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-5 sm:flex-row">
|
||||||
|
{sizes.map((size, index) => (
|
||||||
|
<div key={index} className={`relative ${size.container} rounded-full`}>
|
||||||
|
<Image
|
||||||
|
src="/images/user/user-01.jpg"
|
||||||
|
alt="user"
|
||||||
|
width={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
height={parseInt(size.container.split('-')[1]) * 4}
|
||||||
|
className="overflow-hidden rounded-full"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`absolute bottom-0 right-0 ${size.indicator} rounded-full border-[1.5px] border-white bg-[#f79009] dark:border-gray-900`}
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Avatar04;
|
45
src/app/components/partials/badge/Badge01.js
Normal file
45
src/app/components/partials/badge/Badge01.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// components/partials/badge/Badge01.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Badge01 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#ecf3ff] px-2.5 py-0.5 text-sm font-medium text-[#465fff] dark:bg-[#465fff]/15 dark:text-[#3641f5]">
|
||||||
|
Primary
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#ecfdf3] px-2.5 py-0.5 text-sm font-medium text-[#12b76a] dark:bg-[#12b76a]/15 dark:text-[#039855]">
|
||||||
|
Success
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#fef3f2] px-2.5 py-0.5 text-sm font-medium text-[#f04438] dark:bg-[#f04438]/15 dark:text-[#d92d20]">
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#fffaeb] px-2.5 py-0.5 text-sm font-medium text-[#f79009] dark:bg-[#f79009]/15 dark:text-[#fd853a]">
|
||||||
|
Warning
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f0f9ff] px-2.5 py-0.5 text-sm font-medium text-[#0ba5ec] dark:bg-[#0ba5ec]/15 dark:text-[#0086c9]">
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f2f4f7] px-2.5 py-0.5 text-sm font-medium text-[#475467] dark:bg-white/5 dark:text-white/80">
|
||||||
|
Light
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#667085] px-2.5 py-0.5 text-sm font-medium text-white dark:bg-white/5 dark:text-white">
|
||||||
|
Dark
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge01;
|
45
src/app/components/partials/badge/Badge02.js
Normal file
45
src/app/components/partials/badge/Badge02.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// components/partials/badge/Badge02.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Badge02 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#465fff] px-2.5 py-0.5 text-sm font-medium text-white">
|
||||||
|
Primary
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#12b76a] px-2.5 py-0.5 text-sm font-medium text-white">
|
||||||
|
Success
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f04438] px-2.5 py-0.5 text-sm font-medium text-white">
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f79009] px-2.5 py-0.5 text-sm font-medium text-white">
|
||||||
|
Warning
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#0ba5ec] px-2.5 py-0.5 text-sm font-medium text-white">
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#98a2b3] px-2.5 py-0.5 text-sm font-medium text-white dark:bg-white/5 dark:text-white/80">
|
||||||
|
Light
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#1d2939] px-2.5 py-0.5 text-sm font-medium text-white dark:bg-white/15 dark:text-white">
|
||||||
|
Dark
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge02;
|
69
src/app/components/partials/badge/Badge03.js
Normal file
69
src/app/components/partials/badge/Badge03.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// components/partials/badge/Badge03.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const PlusIcon = () => (
|
||||||
|
<svg
|
||||||
|
className="fill-current"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Badge03 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#ecf3ff] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#465fff] dark:bg-[#465fff]/15 dark:text-[#3641f5]">
|
||||||
|
<PlusIcon />
|
||||||
|
Primary
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#ecfdf3] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#12b76a] dark:bg-[#12b76a]/15 dark:text-[#039855]">
|
||||||
|
<PlusIcon />
|
||||||
|
Success
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#fef3f2] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#f04438] dark:bg-[#f04438]/15 dark:text-[#d92d20]">
|
||||||
|
<PlusIcon />
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#fffaeb] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#f79009] dark:bg-[#f79009]/15 dark:text-[#fd853a]">
|
||||||
|
<PlusIcon />
|
||||||
|
Warning
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f0f9ff] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#0ba5ec] dark:bg-[#0ba5ec]/15 dark:text-[#0086c9]">
|
||||||
|
<PlusIcon />
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f2f4f7] py-0.5 pl-2 pr-2.5 text-sm font-medium text-[#475467] dark:bg-white/5 dark:text-white/80">
|
||||||
|
<PlusIcon />
|
||||||
|
Light
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#667085] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white dark:bg-white/5 dark:text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Dark
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge03;
|
69
src/app/components/partials/badge/Badge04.js
Normal file
69
src/app/components/partials/badge/Badge04.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// components/partials/badge/Badge04.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const PlusIcon = () => (
|
||||||
|
<svg
|
||||||
|
className="fill-current"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Badge04 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#465fff] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Primary
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#12b76a] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Success
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f04438] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#f79009] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Warning
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#0ba5ec] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#98a2b3] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white dark:bg-white/5 dark:text-white/80">
|
||||||
|
<PlusIcon />
|
||||||
|
Light
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span className="inline-flex items-center justify-center gap-1 rounded-full bg-[#1d2939] py-0.5 pl-2 pr-2.5 text-sm font-medium text-white dark:bg-white/15 dark:text-white">
|
||||||
|
<PlusIcon />
|
||||||
|
Dark
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge04;
|
90
src/app/components/partials/badge/Badge05.js
Normal file
90
src/app/components/partials/badge/Badge05.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// components/partials/badge/Badge05.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const PlusIcon = () => (
|
||||||
|
<svg
|
||||||
|
className="fill-current"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Badge05 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#ecf3ff', color: '#465fff' }}
|
||||||
|
>
|
||||||
|
Primary
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#ecfdf3', color: '#039855' }}
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#fef3f2', color: '#d92d20' }}
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#fffaeb', color: '#fd853a' }}
|
||||||
|
>
|
||||||
|
Warning
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#f0f9ff', color: '#0ba5ec' }}
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#f2f4f7', color: '#344054' }}
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium"
|
||||||
|
style={{ backgroundColor: '#667085', color: '#ffffff' }}
|
||||||
|
>
|
||||||
|
Dark
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge05;
|
95
src/app/components/partials/badge/Badge06.js
Normal file
95
src/app/components/partials/badge/Badge06.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const PlusIcon = () => (
|
||||||
|
<svg
|
||||||
|
className="fill-current"
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M5.25012 3C5.25012 2.58579 5.58591 2.25 6.00012 2.25C6.41433 2.25 6.75012 2.58579 6.75012 3V5.25012L9.00034 5.25012C9.41455 5.25012 9.75034 5.58591 9.75034 6.00012C9.75034 6.41433 9.41455 6.75012 9.00034 6.75012H6.75012V9.00034C6.75012 9.41455 6.41433 9.75034 6.00012 9.75034C5.58591 9.75034 5.25012 9.41455 5.25012 9.00034L5.25012 6.75012H3C2.58579 6.75012 2.25 6.41433 2.25 6.00012C2.25 5.58591 2.58579 5.25012 3 5.25012H5.25012V3Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Badge06 = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap gap-4 sm:items-center sm:justify-center">
|
||||||
|
{/* Primary Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{ backgroundColor: '#3B82F6' }} // Tailwind's brand-500 equivalent
|
||||||
|
>
|
||||||
|
Primary
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Success Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{ backgroundColor: '#22C55E' }} // Tailwind's success-500 equivalent
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Error Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{ backgroundColor: '#EF4444' }} // Tailwind's error-500 equivalent
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Warning Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{ backgroundColor: '#F59E0B' }} // Tailwind's warning-500 equivalent
|
||||||
|
>
|
||||||
|
Warning
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Info Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{ backgroundColor: '#38BDF8' }} // Tailwind's blue-light-500 equivalent
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Light Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#9CA3AF', // Tailwind's gray-400
|
||||||
|
color: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Light
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Dark Badge */}
|
||||||
|
<span
|
||||||
|
className="inline-flex items-center justify-center gap-1 rounded-full py-0.5 pl-2.5 pr-2 text-sm font-medium text-white"
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#1F2937', // Tailwind's gray-800
|
||||||
|
color: '#ffffff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Dark
|
||||||
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Badge06;
|
@ -1,105 +1,90 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
|
||||||
|
|
||||||
export default function Header({
|
export default function Header({
|
||||||
currentToken,
|
currentToken,
|
||||||
previousToken,
|
previousToken,
|
||||||
nextToken,
|
nextToken,
|
||||||
missedTokens,
|
missedTokens,
|
||||||
entries = []
|
entries = [] // Add entries prop to calculate counts
|
||||||
}) {
|
}) {
|
||||||
|
// Calculate counts from entries
|
||||||
const totalTokens = entries.length;
|
const totalTokens = entries.length;
|
||||||
const doneTokens = entries.filter(entry => entry.status === "done").length;
|
const doneTokens = entries.filter(entry => entry.status === "done").length;
|
||||||
const missedTokensCount = entries.filter(entry => entry.status === "missed").length;
|
const missedTokensCount = entries.filter(entry => entry.status === "missed").length;
|
||||||
const inQueueTokens = entries.filter(entry => entry.status === "booked").length;
|
const inQueueTokens = entries.filter(entry => entry.status === "booked").length;
|
||||||
const { darkMode } = useTheme();
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex grow flex-col items-center justify-between lg:flex-row lg:px-6 w-full">
|
||||||
|
<div className="hidden sm:hidden md:hidden lg:flex lg:justify-center lg:items-center lg:w-full lg:px-6 lg:py-3 xl:flex xl:justify-center xl:items-center xl:w-full xl:px-6 2xl:flex 2xl:justify-center 2xl:items-center 2xl:w-full 2xl:px-6">
|
||||||
return (
|
<div className="flex flex-col gap-3">
|
||||||
<div className={`flex grow flex-col items-center ${darkMode ? 'bg-gray-900' : 'bg-white'} justify-between lg:flex-row lg:px-6 w-full`}> <div className="hidden lg:flex lg:justify-center lg:items-center lg:w-full lg:px-6 lg:py-3 xl:flex xl:justify-center xl:items-center xl:w-full xl:px-6 2xl:flex 2xl:justify-center 2xl:items-center 2xl:w-full 2xl:px-6">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
<div className={`flex flex-col gap-3 p-4 rounded-lg ${darkMode ? 'bg-gray-800 text-gray-200' : 'bg-gray-50 text-gray-600'}`}>
|
{/* ----------Total Tokens Card---------------- */}
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="border-3 border-gray-300 rounded-[10px] px-2 py-2 flex flex-col gap-3 w-[10rem]">
|
||||||
{/* ----------Total Tokens Card---------------- */}
|
<h2 className="font-semibold text-[14px] text-[#667085]">Total Tokens</h2>
|
||||||
<div className={`border rounded-lg px-3 py-3 flex flex-col gap-3 w-[10rem] transition-colors ${
|
<div className="flex gap-5 -mt-1">
|
||||||
darkMode ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-200'
|
<p className="text-[25px]">{totalTokens}</p>
|
||||||
}`}>
|
<p className="text-[8px] text-[#667085] mt-[18px]">
|
||||||
<h2 className="font-semibold text-sm text-gray-500 dark:text-gray-300">Total Tokens</h2>
|
<span className="text-green-400">+{inQueueTokens}</span> in queue
|
||||||
<div className="flex gap-5 items-baseline">
|
</p>
|
||||||
<p className="text-2xl font-medium dark:text-white">{totalTokens}</p>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
<span className="text-green-500 dark:text-green-400">+{inQueueTokens}</span> in queue
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button className={`bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600
|
|
||||||
text-white text-xs py-1.5 w-full rounded transition-colors`}>
|
|
||||||
View All
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ----------Done Tokens Card---------------- */}
|
|
||||||
<div className={`border rounded-lg px-3 py-3 flex flex-col gap-3 w-[10rem] transition-colors ${
|
|
||||||
darkMode ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-200'
|
|
||||||
}`}>
|
|
||||||
<h2 className="font-semibold text-sm text-gray-500 dark:text-gray-300">Done</h2>
|
|
||||||
<div className="flex gap-5 items-baseline">
|
|
||||||
<p className="text-2xl font-medium dark:text-white">{doneTokens}</p>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
<span className="text-green-500 dark:text-green-400">
|
|
||||||
{totalTokens > 0 ? Math.round((doneTokens/totalTokens)*100) : 0}%
|
|
||||||
</span> of total
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button className={`bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600
|
|
||||||
text-white text-xs py-1.5 w-full rounded transition-colors`}>
|
|
||||||
View List
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ----------Missed Tokens Card---------------- */}
|
|
||||||
<div className={`border rounded-lg px-3 py-3 flex flex-col gap-3 w-[10rem] transition-colors ${
|
|
||||||
darkMode ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-200'
|
|
||||||
}`}>
|
|
||||||
<h2 className="font-semibold text-sm text-gray-500 dark:text-gray-300">Missed Tokens</h2>
|
|
||||||
<div className="flex gap-5 items-baseline">
|
|
||||||
<p className="text-2xl font-medium dark:text-white">{missedTokensCount}</p>
|
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{missedTokensCount > 0 ? (
|
|
||||||
<span className="text-red-500 dark:text-red-400">{missedTokens}</span>
|
|
||||||
) : (
|
|
||||||
"All attended"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button className={`bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600
|
|
||||||
text-white text-xs py-1.5 w-full rounded transition-colors`}>
|
|
||||||
View List
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ----------Current Token Card---------------- */}
|
|
||||||
<div className={`border rounded-lg px-3 py-3 flex flex-col gap-3 w-[10rem] transition-colors ${
|
|
||||||
darkMode ? 'bg-gray-700 border-gray-600' : 'bg-white border-gray-200'
|
|
||||||
}`}>
|
|
||||||
<h2 className="font-semibold text-sm text-gray-500 dark:text-gray-300">Current Token</h2>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<p className="text-2xl font-medium text-center dark:text-white">
|
|
||||||
{currentToken || "---"}
|
|
||||||
</p>
|
|
||||||
<div className="flex justify-between text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
<span>Prev: {previousToken || "---"}</span>
|
|
||||||
<span>Next: {nextToken || "---"}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<button className="bg-[#465FFF] text-white text-[8px] px-[24px] py-[4px] w-full rounded-[4px]">
|
||||||
|
View All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ----------Done Tokens Card---------------- */}
|
||||||
|
<div className="border-3 border-gray-300 rounded-[10px] px-2 py-2 flex flex-col gap-3 w-[10rem]">
|
||||||
|
<h2 className="font-semibold text-[14px] text-[#667085]">Done</h2>
|
||||||
|
<div className="flex gap-5 -mt-1">
|
||||||
|
<p className="text-[25px]">{doneTokens}</p>
|
||||||
|
<p className="text-[8px] text-[#667085] mt-[18px]">
|
||||||
|
<span className="text-green-400">
|
||||||
|
{totalTokens > 0 ? Math.round((doneTokens/totalTokens)*100) : 0}%
|
||||||
|
</span> of total
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button className="bg-[#465FFF] text-white text-[8px] px-[24px] py-[4px] w-full rounded-[4px]">
|
||||||
|
View List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ----------Missed Tokens Card---------------- */}
|
||||||
|
<div className="border-3 border-gray-300 rounded-[10px] px-2 py-2 flex flex-col gap-3 w-[10rem]">
|
||||||
|
<h2 className="font-semibold text-[14px] text-[#667085]">Missed Tokens</h2>
|
||||||
|
<div className="flex gap-5 -mt-1">
|
||||||
|
<p className="text-[25px]">{missedTokensCount}</p>
|
||||||
|
<p className="text-[8px] text-[#667085] mt-[18px]">
|
||||||
|
{missedTokensCount > 0 ? (
|
||||||
|
<span className="text-red-400">{missedTokens}</span>
|
||||||
|
) : (
|
||||||
|
"All attended"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button className="bg-[#465FFF] text-white text-[8px] px-[24px] py-[4px] w-full rounded-[4px]">
|
||||||
|
View List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ----------Current Token Card---------------- */}
|
||||||
|
<div className="border-3 border-gray-300 rounded-[10px] px-2 py-2 flex flex-col gap-3 w-[10rem]">
|
||||||
|
<h2 className="font-semibold text-[14px] text-[#667085]">Current Token</h2>
|
||||||
|
<div className="flex flex-col gap-1 -mt-1">
|
||||||
|
<p className="text-[25px] text-center">
|
||||||
|
{currentToken || "---"}
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-between text-[8px] text-[#667085]">
|
||||||
|
<span>Prev: {previousToken || "---"}</span>
|
||||||
|
<span>Next: {nextToken || "---"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="bg-[#465FFF] text-white text-[8px] px-[24px] py-[4px] w-full rounded-[4px]">
|
||||||
|
Call Next
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className={`bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600
|
|
||||||
text-white text-xs py-1.5 w-full rounded transition-colors`}>
|
|
||||||
Call Next
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
@ -41,7 +41,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
if (error.code === 401) {
|
if (error.code === 401) {
|
||||||
localStorage.removeItem("isLoggedIn");
|
localStorage.removeItem("isLoggedIn");
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
router.push("/");
|
router.push("/signup");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
0
src/app/context/DashboardContext.js
Normal file
0
src/app/context/DashboardContext.js
Normal file
@ -1,11 +1,11 @@
|
|||||||
|
// src/context/ThemeContext.js
|
||||||
"use client";
|
"use client";
|
||||||
import { createContext, useContext, useState, useEffect } from "react";
|
import { createContext, useContext, useState, useEffect } from "react";
|
||||||
|
|
||||||
const ThemeContext = createContext();
|
const ThemeContext = createContext();
|
||||||
|
|
||||||
export function ThemeProvider({ children }) {
|
export function ThemeProvider({ children }) {
|
||||||
// Initialize with undefined to detect client-side
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
const [darkMode, setDarkMode] = useState(undefined);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check for saved preference or system preference
|
// Check for saved preference or system preference
|
||||||
@ -14,8 +14,6 @@ export function ThemeProvider({ children }) {
|
|||||||
setDarkMode(savedMode === 'true');
|
setDarkMode(savedMode === 'true');
|
||||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
setDarkMode(true);
|
setDarkMode(true);
|
||||||
} else {
|
|
||||||
setDarkMode(false); // Explicitly set light mode if no preference
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,38 +1,30 @@
|
|||||||
|
// src/app/dashboard/page.js
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTheme } from "../context/ThemeContext";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
import EntriesTable from "../pages/entries/EntriesTable";
|
import EntriesTable from "../pages/entries/EntriesTable";
|
||||||
|
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isAuthenticated, loading, userRole } = useAuth();
|
const { isAuthenticated, loading, userRole } = useAuth();
|
||||||
const { darkMode } = useTheme();
|
const { darkMode } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading && !isAuthenticated) {
|
if (!loading && !isAuthenticated) {
|
||||||
router.push("/");
|
router.push("/signup");
|
||||||
}
|
}
|
||||||
}, [isAuthenticated, loading, router]);
|
}, [isAuthenticated, loading, router]);
|
||||||
|
|
||||||
// Handle theme loading state
|
if (loading) {
|
||||||
if (darkMode === undefined || loading) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-white dark:bg-gray-900">
|
<div className="flex items-center justify-center min-h-screen bg-white dark:bg-gray-900">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div
|
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${darkMode ? 'border-gray-100' : 'border-gray-900'} mx-auto`}></div>
|
||||||
className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${
|
<p className={`mt-4 text-lg font-semibold ${darkMode ? 'text-gray-100' : 'text-gray-900'}`}>
|
||||||
darkMode === true ? "border-gray-100" : "border-gray-900"
|
|
||||||
}`}
|
|
||||||
></div>
|
|
||||||
<p
|
|
||||||
className={`mt-4 text-lg font-semibold ${
|
|
||||||
darkMode === true ? "text-gray-100" : "text-gray-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Loading...
|
Loading...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -44,29 +36,9 @@ export default function DashboardPage() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user is admin or staff to show entries table
|
|
||||||
const canViewEntries = userRole === "admin" || userRole === "staff";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main
|
<div className="min-h-screen bg-white dark:bg-gray-900 p-6">
|
||||||
className={`min-h-screen transition-colors duration-300 ${
|
<EntriesTable />
|
||||||
darkMode ? "dark bg-gray-900" : "bg-white"
|
</div>
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="p-1">
|
|
||||||
{canViewEntries ? (
|
|
||||||
<EntriesTable />
|
|
||||||
) : (
|
|
||||||
<div className="text-center p-8">
|
|
||||||
<h2 className={`text-2xl font-semibold ${darkMode ? "text-white" : "text-gray-800"}`}>
|
|
||||||
Welcome to your Dashboard
|
|
||||||
</h2>
|
|
||||||
<p className={`mt-4 ${darkMode ? "text-gray-300" : "text-gray-600"}`}>
|
|
||||||
You don't have permission to view entries.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -6,6 +6,6 @@
|
|||||||
|
|
||||||
// lib/config.js
|
// lib/config.js
|
||||||
export const DATABASE_ID = "67e1452b00016444b37f";
|
export const DATABASE_ID = "67e1452b00016444b37f";
|
||||||
export const COLLECTION_ID = "681c367b0016f9ba0e8e";
|
export const COLLECTION_ID = "67fe4029000f7e0a7b92";
|
||||||
export const SETTINGS_COLLECTION_ID = "settings";
|
export const SETTINGS_COLLECTION_ID = "settings";
|
||||||
export const AVG_CONSULTATION_KEY = "avg_consultation";
|
export const AVG_CONSULTATION_KEY = "avg_consultation";
|
380
src/app/page.js
380
src/app/page.js
@ -1,152 +1,32 @@
|
|||||||
"use client";
|
// src/app/page.js
|
||||||
|
"use client"; // Mark this as a client component
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "./context/AuthContext";
|
import { useAuth } from "./context/AuthContext"; // Import useAuth
|
||||||
import { useState, useEffect } from "react";
|
import { useEffect } from "react"; // Import useEffect
|
||||||
import Link from "next/link";
|
|
||||||
import Button1 from "./ui/buttons/button1";
|
|
||||||
import { account, ID } from "./lib/appwrite";
|
|
||||||
|
|
||||||
export default function AuthPage() {
|
export default function Home() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isAuthenticated, loading: authLoading, setIsAuthenticated } = useAuth();
|
const { isAuthenticated, loading } = useAuth();
|
||||||
const [isLoginForm, setIsLoginForm] = useState(true);
|
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
|
|
||||||
// Form states
|
|
||||||
const [loginData, setLoginData] = useState({
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
rememberMe: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [signupData, setSignupData] = useState({
|
|
||||||
fname: "",
|
|
||||||
lname: "",
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
agreeTerms: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [errors, setErrors] = useState({
|
|
||||||
agreeTerms: "",
|
|
||||||
general: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!authLoading && isAuthenticated) {
|
if (!loading) {
|
||||||
router.push("/dashboard");
|
if (isAuthenticated) {
|
||||||
}
|
|
||||||
}, [isAuthenticated, authLoading, router]);
|
|
||||||
|
|
||||||
const handleLoginChange = (e) => {
|
|
||||||
const { name, value, type, checked } = e.target;
|
|
||||||
setLoginData({
|
|
||||||
...loginData,
|
|
||||||
[name]: type === "checkbox" ? checked : value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSignupChange = (e) => {
|
|
||||||
const { name, value, type, checked } = e.target;
|
|
||||||
setSignupData({
|
|
||||||
...signupData,
|
|
||||||
[name]: type === "checkbox" ? checked : value,
|
|
||||||
});
|
|
||||||
if (name === "agreeTerms") {
|
|
||||||
setErrors({ ...errors, agreeTerms: "" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateSignupForm = () => {
|
|
||||||
let isValid = true;
|
|
||||||
const newErrors = { ...errors };
|
|
||||||
|
|
||||||
if (!signupData.agreeTerms) {
|
|
||||||
newErrors.agreeTerms = "You must agree to the terms and conditions";
|
|
||||||
isValid = false;
|
|
||||||
} else {
|
|
||||||
newErrors.agreeTerms = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
setErrors(newErrors);
|
|
||||||
return isValid;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogin = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setLoading(true);
|
|
||||||
setErrors({ ...errors, general: "" });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const session = await account.createEmailPasswordSession(
|
|
||||||
loginData.email,
|
|
||||||
loginData.password
|
|
||||||
);
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
const user = await account.get();
|
|
||||||
if (user) {
|
|
||||||
localStorage.setItem("isLoggedIn", "true");
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
window.location.href = '/dashboard';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
let errorMessage = 'Login failed. Please try again.';
|
|
||||||
if (error.type === 'user_invalid_credentials') {
|
|
||||||
errorMessage = 'Invalid email or password';
|
|
||||||
} else if (error.type === 'general_argument_invalid') {
|
|
||||||
errorMessage = 'Invalid email format';
|
|
||||||
}
|
|
||||||
setErrors({ ...errors, general: errorMessage });
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSignup = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!validateSignupForm()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
setErrors({ ...errors, general: "" });
|
|
||||||
|
|
||||||
try {
|
|
||||||
await account.create(
|
|
||||||
ID.unique(),
|
|
||||||
signupData.email,
|
|
||||||
signupData.password,
|
|
||||||
`${signupData.fname} ${signupData.lname}`
|
|
||||||
);
|
|
||||||
await account.createEmailPasswordSession(signupData.email, signupData.password);
|
|
||||||
const user = await account.get();
|
|
||||||
if (user) {
|
|
||||||
localStorage.setItem("isLoggedIn", "true");
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
router.push("/dashboard");
|
router.push("/dashboard");
|
||||||
|
} else {
|
||||||
|
router.push("/signup");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Signup error:", error);
|
|
||||||
setErrors({
|
|
||||||
...errors,
|
|
||||||
general: error.message || "Signup failed. Please try again.",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
}, [isAuthenticated, loading, router]);
|
||||||
|
|
||||||
if (authLoading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
|
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-900">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
{/* Spinner */}
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 dark:border-gray-100 mx-auto"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900 dark:border-gray-100 mx-auto"></div>
|
||||||
<p className="mt-4 text-lg font-semibold text-gray-900 dark:text-gray-100">
|
{/* Loading Text */}
|
||||||
|
<p className="mt-4 text-lg font-semibold text-gray-900 dark:text-gray-100 ">
|
||||||
Loading...
|
Loading...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -154,231 +34,5 @@ export default function AuthPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAuthenticated) {
|
return null; // No need to render anything here
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`min-h-screen ${darkMode ? "dark bg-gray-900" : "bg-white"}`}>
|
|
||||||
<div className="flex flex-col items-center justify-center p-6">
|
|
||||||
<div className="w-full max-w-md">
|
|
||||||
<div className="mb-8">
|
|
||||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white/90">
|
|
||||||
{isLoginForm ? "Sign In" : "Sign Up"}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{isLoginForm
|
|
||||||
? "Enter your email and password to sign In!"
|
|
||||||
: "Enter your deatils to create an account!"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errors.general && (
|
|
||||||
<div className="mb-4 p-3 text-sm text-red-600 bg-red-50 rounded-lg dark:bg-red-900/20 dark:text-red-300">
|
|
||||||
{errors.general}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isLoginForm ? (
|
|
||||||
// Login Form
|
|
||||||
<form onSubmit={handleLogin} className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
Email*
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
value={loginData.email}
|
|
||||||
onChange={handleLoginChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="your@email.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
Password*
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="password"
|
|
||||||
value={loginData.password}
|
|
||||||
onChange={handleLoginChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 dark:text-gray-400"
|
|
||||||
>
|
|
||||||
{showPassword ? "Hide" : "Show"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<label className="flex items-center space-x-2 text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="rememberMe"
|
|
||||||
checked={loginData.rememberMe}
|
|
||||||
onChange={handleLoginChange}
|
|
||||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
|
||||||
/>
|
|
||||||
<span>Remember me</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<a href="#" className="text-sm text-blue-600 hover:underline dark:text-blue-400">
|
|
||||||
Forgot password?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button1
|
|
||||||
type="submit"
|
|
||||||
className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "Signing in..." : "Sign in"}
|
|
||||||
</Button1>
|
|
||||||
</form>
|
|
||||||
) : (
|
|
||||||
// Sign Up Form
|
|
||||||
<form onSubmit={handleSignup} className="space-y-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
First Name*
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="fname"
|
|
||||||
value={signupData.fname}
|
|
||||||
onChange={handleSignupChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="John"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
Last Name*
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="lname"
|
|
||||||
value={signupData.lname}
|
|
||||||
onChange={handleSignupChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="Doe"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
Email*
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
value={signupData.email}
|
|
||||||
onChange={handleSignupChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="your@email.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
||||||
Password*
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
name="password"
|
|
||||||
value={signupData.password}
|
|
||||||
onChange={handleSignupChange}
|
|
||||||
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
placeholder="••••••••"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 dark:text-gray-400"
|
|
||||||
>
|
|
||||||
{showPassword ? "Hide" : "Show"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label className="flex items-start space-x-2 text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="agreeTerms"
|
|
||||||
checked={signupData.agreeTerms}
|
|
||||||
onChange={handleSignupChange}
|
|
||||||
className="mt-1 rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
By creating an account means you agree to <a href="#" className="text-blue-600 hover:underline dark:text-blue-400">Terms</a>and<a href="#" className="text-blue-600 hover:underline dark:text-blue-400">Conditions</a> and our <a href="#" className="text-blue-600 hover:underline dark:text-blue-400">Privacy Policy</a>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
{errors.agreeTerms && (
|
|
||||||
<p className="mt-1 text-sm text-red-600 dark:text-red-400">
|
|
||||||
{errors.agreeTerms}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button1
|
|
||||||
type="submit"
|
|
||||||
className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition"
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "Creating account..." : "Sign up"}
|
|
||||||
</Button1>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
||||||
{isLoginForm ? "Don't have an account?" : "Already have an account?"}
|
|
||||||
<button
|
|
||||||
onClick={() => setIsLoginForm(!isLoginForm)}
|
|
||||||
className="ml-1 text-blue-600 hover:underline dark:text-blue-400 font-medium"
|
|
||||||
>
|
|
||||||
{isLoginForm ? "Sign up" : "Sign in"}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dark mode toggle */}
|
|
||||||
<button
|
|
||||||
onClick={() => setDarkMode(!darkMode)}
|
|
||||||
className="fixed bottom-6 right-6 p-3 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
|
|
||||||
>
|
|
||||||
{darkMode ? (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
@ -82,7 +82,7 @@ const createEntries = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setBookings(updatedBookings);
|
setBookings(updatedBookings);
|
||||||
// alert(`${validBookings.length} tokens created successfully!`);
|
alert(`${validBookings.length} tokens created successfully!`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Creation error:", error);
|
console.error("Creation error:", error);
|
||||||
setError(error.message);
|
setError(error.message);
|
||||||
|
@ -1,126 +1,44 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from 'react';
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from 'next/navigation';
|
||||||
import Header from "../../components/partials/header";
|
import Header from "../../components/partials/header";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
import { useTheme } from "../../context/ThemeContext";
|
||||||
import { databases, Query } from "../../lib/appwrite";
|
|
||||||
import { DATABASE_ID, COLLECTION_ID } from "../../lib/api";
|
|
||||||
|
|
||||||
export default function MultiBooking() {
|
export default function MultiBooking() {
|
||||||
const { darkMode } = useTheme();
|
const { darkMode } = useTheme();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [appointmentDate, setAppointmentDate] = useState(new Date().toISOString().split('T')[0]);
|
||||||
const [appointmentDate, setAppointmentDate] = useState(
|
|
||||||
new Date().toISOString().split("T")[0]
|
|
||||||
);
|
|
||||||
const [patients, setPatients] = useState(
|
const [patients, setPatients] = useState(
|
||||||
Array(5)
|
Array(5).fill().map((_, i) => ({ id: i+1, name: "" }))
|
||||||
.fill()
|
|
||||||
.map((_, i) => ({ id: i + 1, name: "" }))
|
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
// Header related states - matching EntriesTable
|
|
||||||
const [entries, setEntries] = useState([]);
|
|
||||||
const [currentToken, setCurrentToken] = useState(null);
|
|
||||||
const [previousToken, setPreviousToken] = useState(null);
|
|
||||||
const [nextToken, setNextToken] = useState(null);
|
|
||||||
const [missedTokens, setMissedTokens] = useState("");
|
|
||||||
const [dataLoading, setDataLoading] = useState(true);
|
|
||||||
|
|
||||||
const handleNameChange = (id, value) => {
|
const handleNameChange = (id, value) => {
|
||||||
setPatients(patients.map((p) => (p.id === id ? { ...p, name: value } : p)));
|
setPatients(patients.map(p =>
|
||||||
|
p.id === id ? { ...p, name: value } : p
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get entries from database - similar to EntriesTable
|
|
||||||
const getEntries = async () => {
|
|
||||||
try {
|
|
||||||
const today = new Date().toISOString().split("T")[0];
|
|
||||||
const response = await databases.listDocuments(
|
|
||||||
DATABASE_ID,
|
|
||||||
COLLECTION_ID,
|
|
||||||
[Query.equal("date", today), Query.orderAsc("tokenNumber")],
|
|
||||||
100
|
|
||||||
);
|
|
||||||
return response.documents;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Fetch error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update token information - same as EntriesTable
|
|
||||||
const updateTokenInfo = (entries) => {
|
|
||||||
if (entries.length === 0) {
|
|
||||||
setCurrentToken(null);
|
|
||||||
setPreviousToken(null);
|
|
||||||
setNextToken(null);
|
|
||||||
setMissedTokens("");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentIndex = entries.findIndex(
|
|
||||||
(entry) => entry.status === "booked"
|
|
||||||
);
|
|
||||||
const current = currentIndex >= 0 ? entries[currentIndex] : null;
|
|
||||||
|
|
||||||
setCurrentToken(current?.tokenNumber || null);
|
|
||||||
setPreviousToken(
|
|
||||||
currentIndex > 0 ? entries[currentIndex - 1]?.tokenNumber : null
|
|
||||||
);
|
|
||||||
setNextToken(
|
|
||||||
currentIndex < entries.length - 1
|
|
||||||
? entries[currentIndex + 1]?.tokenNumber
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const missed = entries
|
|
||||||
.filter((entry) => entry.status === "missed")
|
|
||||||
.map((entry) => entry.tokenNumber)
|
|
||||||
.join(", ");
|
|
||||||
setMissedTokens(missed || "None");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load token data - similar to EntriesTable
|
|
||||||
useEffect(() => {
|
|
||||||
const loadTokenData = async () => {
|
|
||||||
try {
|
|
||||||
setDataLoading(true);
|
|
||||||
const data = await getEntries();
|
|
||||||
setEntries(data);
|
|
||||||
updateTokenInfo(data);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error loading token data:", err);
|
|
||||||
} finally {
|
|
||||||
setDataLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadTokenData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const validPatients = patients.filter((p) => p.name.trim() !== "");
|
// Filter out empty names and validate
|
||||||
|
const validPatients = patients.filter(p => p.name.trim() !== "");
|
||||||
if (validPatients.length === 0) {
|
if (validPatients.length === 0) {
|
||||||
throw new Error("Please enter at least one patient name.");
|
throw new Error("Please enter at least one patient name");
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionStorage.setItem(
|
// Store in session to use in MultiBooked page
|
||||||
"multiBookingData",
|
sessionStorage.setItem('multiBookingData', JSON.stringify({
|
||||||
JSON.stringify({
|
date: appointmentDate,
|
||||||
date: appointmentDate,
|
patients: validPatients
|
||||||
patients: validPatients,
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
router.push("/pages/MultiBooked");
|
router.push('/pages/MultiBooked');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error.message);
|
setError(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
@ -129,33 +47,24 @@ export default function MultiBooking() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addMorePatients = () => {
|
const addMorePatients = () => {
|
||||||
if (patients.length >= 20) return;
|
const newId = patients.length > 0 ?
|
||||||
const newId =
|
Math.max(...patients.map(p => p.id)) + 1 : 1;
|
||||||
patients.length > 0 ? Math.max(...patients.map((p) => p.id)) + 1 : 1;
|
|
||||||
setPatients([...patients, { id: newId, name: "" }]);
|
setPatients([...patients, { id: newId, name: "" }]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={`flex h-screen overflow-hidden ${darkMode ? 'dark bg-gray-900' : 'bg-white'}`}>
|
||||||
className={`flex h-screen overflow-hidden ${
|
{/* Content Area */}
|
||||||
darkMode ? "dark bg-gray-900" : "bg-white"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="relative flex flex-1 flex-col overflow-x-hidden overflow-y-auto">
|
<div className="relative flex flex-1 flex-col overflow-x-hidden overflow-y-auto">
|
||||||
|
{/* Sticky Header */}
|
||||||
<header className="sticky top-0 z-50 bg-white shadow-sm dark:bg-gray-800">
|
<header className="sticky top-0 z-50 bg-white shadow-sm dark:bg-gray-800">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between p-4">
|
||||||
{/* Pass the same props to Header as in EntriesTable */}
|
<Header />
|
||||||
<Header
|
|
||||||
currentToken={currentToken}
|
|
||||||
previousToken={previousToken}
|
|
||||||
nextToken={nextToken}
|
|
||||||
missedTokens={missedTokens}
|
|
||||||
entries={entries}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto scrollbar-hide">
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 overflow-y-auto">
|
||||||
<div className="mx-auto max-w-[--breakpoint-2xl] p-4 md:p-6 pt-20">
|
<div className="mx-auto max-w-[--breakpoint-2xl] p-4 md:p-6 pt-20">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-4 bg-red-100 text-red-700 rounded-md">
|
<div className="mb-4 p-4 bg-red-100 text-red-700 rounded-md">
|
||||||
@ -164,72 +73,47 @@ export default function MultiBooking() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h2
|
<h2 className={`text-2xl mb-2 ${darkMode ? 'text-white' : 'text-black'}`}>
|
||||||
className={`text-2xl mb-2 ${
|
|
||||||
darkMode ? "text-white" : "text-black"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Staff Multi-Booking
|
Staff Multi-Booking
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label
|
<label className={`mr-2 ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||||
className={`mr-2 ${
|
|
||||||
darkMode ? "text-gray-300" : "text-gray-700"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Date:
|
Date:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={appointmentDate}
|
value={appointmentDate}
|
||||||
onChange={(e) => setAppointmentDate(e.target.value)}
|
onChange={(e) => setAppointmentDate(e.target.value)}
|
||||||
className={`p-1 border rounded ${
|
className={`p-1 border rounded ${darkMode ? 'bg-gray-800 text-white border-gray-700' : 'bg-white'}`}
|
||||||
darkMode
|
|
||||||
? "bg-gray-800 text-white border-gray-700"
|
|
||||||
: "bg-white"
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto my-8">
|
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto my-8">
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
{patients.map((patient) => (
|
{patients.map((patient) => (
|
||||||
<div key={patient.id} className="flex items-center gap-3">
|
<div key={patient.id} className="flex items-center gap-3">
|
||||||
<span
|
<span className={`w-6 text-right ${darkMode ? 'text-gray-300' : 'text-gray-700'}`}>
|
||||||
className={`w-6 text-right ${
|
|
||||||
darkMode ? "text-gray-300" : "text-gray-700"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{patient.id}.
|
{patient.id}.
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={patient.name}
|
value={patient.name}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleNameChange(patient.id, e.target.value)}
|
||||||
handleNameChange(patient.id, e.target.value)
|
|
||||||
}
|
|
||||||
placeholder="Patient name"
|
placeholder="Patient name"
|
||||||
className={`flex-1 shadow-theme-xs h-11 rounded-lg border px-4 py-2.5 text-sm focus:outline-none focus:ring-2 ${
|
className={`flex-1 shadow-theme-xs h-11 rounded-lg border px-4 py-2.5 text-sm focus:outline-none focus:ring-2 ${
|
||||||
darkMode
|
darkMode ?
|
||||||
? "bg-gray-800 border-gray-700 text-white placeholder-gray-500 focus:border-blue-800 focus:ring-blue-800/30"
|
'bg-gray-800 border-gray-700 text-white placeholder-gray-500 focus:border-blue-800 focus:ring-blue-800/30' :
|
||||||
: "bg-white border-gray-300 text-gray-800 placeholder-gray-400 focus:border-blue-300 focus:ring-blue-500/10"
|
'bg-white border-gray-300 text-gray-800 placeholder-gray-400 focus:border-blue-300 focus:ring-blue-500/10'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{patient.id > 5 && (
|
{patient.id > 5 && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() => setPatients(patients.filter(p => p.id !== patient.id))}
|
||||||
setPatients(
|
|
||||||
patients.filter((p) => p.id !== patient.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className={`p-2 rounded-full ${
|
className={`p-2 rounded-full ${
|
||||||
darkMode
|
darkMode ? 'text-red-400 hover:bg-gray-700' : 'text-red-500 hover:bg-gray-100'
|
||||||
? "text-red-400 hover:bg-gray-700"
|
|
||||||
: "text-red-500 hover:bg-gray-100"
|
|
||||||
}`}
|
}`}
|
||||||
title="Remove"
|
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@ -238,27 +122,20 @@ export default function MultiBooking() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-3 mb-6 items-center">
|
<div className="flex flex-wrap gap-3 mb-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addMorePatients}
|
onClick={addMorePatients}
|
||||||
disabled={patients.length >= 20}
|
|
||||||
className={`px-4 py-2 rounded-lg text-sm font-medium ${
|
className={`px-4 py-2 rounded-lg text-sm font-medium ${
|
||||||
darkMode
|
darkMode ?
|
||||||
? "bg-gray-700 text-white hover:bg-gray-600"
|
'bg-gray-700 text-white hover:bg-gray-600' :
|
||||||
: "bg-gray-200 text-gray-700 hover:bg-gray-300"
|
'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
||||||
} ${
|
|
||||||
patients.length >= 20 ? "opacity-50 cursor-not-allowed" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
+ Add More Patients
|
+ Add More Patients
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||||
className={`text-sm mt-1 ${
|
{patients.length} patients entered
|
||||||
darkMode ? "text-gray-400" : "text-gray-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{patients.length} / 20 patients entered
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -267,43 +144,34 @@ export default function MultiBooking() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={`text-center rounded-lg px-5 py-3.5 text-sm font-medium text-white shadow-theme-xs transition w-full ${
|
className={`text-center rounded-lg px-5 py-3.5 text-sm font-medium text-white shadow-theme-xs transition w-full ${
|
||||||
loading
|
loading ? 'bg-blue-400 cursor-not-allowed' : 'bg-blue-700 hover:bg-blue-600'
|
||||||
? "bg-blue-400 cursor-not-allowed"
|
|
||||||
: "bg-blue-700 hover:bg-blue-600"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{loading ? "Processing..." : "Generate Tokens"}
|
{loading ? 'Processing...' : 'Generate Tokens'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => router.push("/entries")}
|
onClick={() => router.push('/entries')}
|
||||||
className={`text-center rounded-lg px-5 py-3.5 text-sm font-medium shadow-theme-xs ring-1 ring-inset transition w-full ${
|
className={`text-center rounded-lg px-5 py-3.5 text-sm font-medium shadow-theme-xs ring-1 ring-inset transition w-full ${
|
||||||
darkMode
|
darkMode ?
|
||||||
? "bg-gray-800 text-gray-300 ring-gray-700 hover:bg-gray-700"
|
'bg-gray-800 text-gray-300 ring-gray-700 hover:bg-gray-700' :
|
||||||
: "bg-white text-gray-700 ring-gray-300 hover:bg-gray-50"
|
'bg-white text-gray-700 ring-gray-300 hover:bg-gray-50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
View All Entries
|
View All Entries
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div
|
<div className={`text-center mt-8 text-sm ${
|
||||||
className={`text-center mt-8 text-sm ${
|
darkMode ? 'text-gray-400' : 'text-gray-600'
|
||||||
darkMode ? "text-gray-400" : "text-gray-600"
|
}`}>
|
||||||
}`}
|
<p>Staff can book multiple tokens at once. All tokens will be assigned for the same date.</p>
|
||||||
>
|
<p className="mt-1">Minimum 1 patient required. Maximum 20 patients per batch.</p>
|
||||||
<p>
|
|
||||||
Staff can book multiple tokens at once. All tokens will be
|
|
||||||
assigned for the same date.
|
|
||||||
</p>
|
|
||||||
<p className="mt-1">
|
|
||||||
Minimum 1 patient required. Maximum 20 patients per batch.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -17,24 +17,16 @@ export default function ResetPasswordPage() {
|
|||||||
const { darkMode, toggleDarkMode } = useTheme();
|
const { darkMode, toggleDarkMode } = useTheme();
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [message, setMessage] = useState({ text: "", type: "" }); // type can be "success" or "error"
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setMessage({ text: "", type: "" });
|
|
||||||
try {
|
try {
|
||||||
await account.createRecovery(email, "http://localhost:3000/reset-password");
|
await account.createRecovery(email, "http://localhost:3000/reset-password");
|
||||||
setMessage({
|
alert("Password reset link sent to your email!");
|
||||||
text: "Password reset link sent to your email!",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
setMessage({
|
alert(`Failed to send reset link: ${error.message}`);
|
||||||
text: `Failed to send reset link: ${error.message}`,
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -61,15 +53,6 @@ export default function ResetPasswordPage() {
|
|||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
Enter your email address linked to your account, and we'll send you a link to reset your password.
|
Enter your email address linked to your account, and we'll send you a link to reset your password.
|
||||||
</p>
|
</p>
|
||||||
{message.text && (
|
|
||||||
<div className={`mt-4 p-3 rounded-md ${
|
|
||||||
message.type === "success"
|
|
||||||
? "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-200"
|
|
||||||
: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-200"
|
|
||||||
}`}>
|
|
||||||
{message.text}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
@ -102,4 +85,4 @@ export default function ResetPasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ export default function SignInPage() {
|
|||||||
{/* Left Side - Form */}
|
{/* Left Side - Form */}
|
||||||
<div className="w-full flex flex-col justify-center p-6 sm:p-12 lg:p-16">
|
<div className="w-full flex flex-col justify-center p-6 sm:p-12 lg:p-16">
|
||||||
<div className="w-full max-w-md mx-auto">
|
<div className="w-full max-w-md mx-auto">
|
||||||
{/* <Link
|
<Link
|
||||||
href="/dashboard"
|
href="/dashboard"
|
||||||
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 mb-10"
|
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 mb-10"
|
||||||
>
|
>
|
||||||
@ -108,7 +108,7 @@ export default function SignInPage() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Back to dashboard
|
Back to dashboard
|
||||||
</Link> */}
|
</Link>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="mb-2 text-2xl font-bold text-gray-800 dark:text-white/90 sm:text-3xl">
|
<h1 className="mb-2 text-2xl font-bold text-gray-800 dark:text-white/90 sm:text-3xl">
|
||||||
|
@ -86,7 +86,7 @@ export default function SignUpPage() {
|
|||||||
{/* Form */}
|
{/* Form */}
|
||||||
<div className="flex flex-col flex-1 w-full">
|
<div className="flex flex-col flex-1 w-full">
|
||||||
<div className="w-full max-w-md pt-5 mx-auto sm:py-10">
|
<div className="w-full max-w-md pt-5 mx-auto sm:py-10">
|
||||||
{/* <Link
|
<Link
|
||||||
href="/dashboard"
|
href="/dashboard"
|
||||||
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
@ -107,7 +107,7 @@ export default function SignUpPage() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Back to dashboard
|
Back to dashboard
|
||||||
</Link> */}
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-center flex-1 w-full max-w-md mx-auto">
|
<div className="flex flex-col justify-center flex-1 w-full max-w-md mx-auto">
|
||||||
<div className="mb-5 sm:mb-8">
|
<div className="mb-5 sm:mb-8">
|
||||||
|
@ -2,12 +2,26 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
import Head from 'next/head';
|
||||||
|
import Preloader from '../../components/partials/preloaders';
|
||||||
|
|
||||||
export default function SingleBooking() {
|
export default function SingleBooking() {
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { darkMode } = useTheme();
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedDarkMode = JSON.parse(localStorage.getItem('darkMode'));
|
||||||
|
if (savedDarkMode !== null) {
|
||||||
|
setDarkMode(savedDarkMode);
|
||||||
|
}
|
||||||
|
setLoaded(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('darkMode', JSON.stringify(darkMode));
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -17,47 +31,31 @@ export default function SingleBooking() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current date in YYYY-MM-DD format
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const appointmentDate = today.toISOString().split('T')[0];
|
const appointmentDate = today.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Navigate to confirmation page with name and current date as query params
|
||||||
router.push(`/pages/SingleBooked?name=${encodeURIComponent(name)}&date=${encodeURIComponent(appointmentDate)}`);
|
router.push(`/pages/SingleBooked?name=${encodeURIComponent(name)}&date=${encodeURIComponent(appointmentDate)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle theme loading state (same as dashboard)
|
if (!loaded) {
|
||||||
if (darkMode === undefined) {
|
return <Preloader />;
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${
|
|
||||||
darkMode === true ? 'border-gray-100' : 'border-gray-900'
|
|
||||||
}`}></div>
|
|
||||||
<p className={`mt-4 text-lg font-semibold ${
|
|
||||||
darkMode === true ? 'text-gray-100' : 'text-gray-900'
|
|
||||||
}`}>
|
|
||||||
Loading...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen transition-colors duration-300 ${
|
<div className={`flex flex-col min-h-screen ${darkMode ? 'dark bg-gray-900' : ''}`}>
|
||||||
darkMode ? 'dark bg-gray-900' : 'bg-white'
|
|
||||||
}`}>
|
|
||||||
<div className="flex h-screen overflow-hidden">
|
<div className="flex h-screen overflow-hidden">
|
||||||
<div className="relative flex flex-col flex-1 overflow-x-hidden overflow-y-auto">
|
<div className="relative flex flex-col flex-1 overflow-x-hidden overflow-y-auto">
|
||||||
<main>
|
<main>
|
||||||
<div className="p-4 mx-auto max-w-4xl md:p-6">
|
<div className="p-4 mx-auto max-w-4xl md:p-6">
|
||||||
<h2 className="text-2xl font-semibold text-gray-800 dark:text-gray-100">
|
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white">Book Appointment</h2>
|
||||||
Book Appointment
|
<p className='mb-6'>Date: {new Date().toLocaleDateString('en-GB')}</p>
|
||||||
</h2>
|
|
||||||
<p className={`mb-6 dark:text-gray-300`}>
|
|
||||||
Date: {new Date().toLocaleDateString('en-GB')}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5">
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1.5">
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -65,20 +63,20 @@ export default function SingleBooking() {
|
|||||||
placeholder="John Doe"
|
placeholder="John Doe"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
className="h-11 w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-4 py-2.5 text-sm text-gray-800 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="h-11 w-full rounded-lg border border-gray-300 bg-white dark:bg-gray-900 px-4 py-2.5 text-sm text-gray-800 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full rounded-lg bg-blue-600 dark:bg-blue-700 px-5 py-3.5 text-sm font-medium text-white hover:bg-blue-700 dark:hover:bg-blue-800 transition duration-200"
|
className="w-full rounded-lg bg-blue-700 px-5 py-3.5 text-sm font-medium text-white hover:bg-blue-800 transition duration-200"
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm text-gray-600 dark:text-gray-400">
|
<div className="mt-6 text-center text-sm text-gray-600 dark:text-gray-300">
|
||||||
Note: Token booking is available for today's date only.
|
Note: Token booking is available for today's date only.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
109
src/app/pages/alerts/page.js
Normal file
109
src/app/pages/alerts/page.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Preloader from "../../components/partials/preloaders";
|
||||||
|
// import Sidebar from './partials/sidebar';
|
||||||
|
import Overlay from "../../components/partials/overlay";
|
||||||
|
import Header from "../../components/partials/header";
|
||||||
|
import Breadcrumb from "../../components/partials/breadcrumb";
|
||||||
|
import AlertSuccess from '../../components/partials/alert/alert-success';
|
||||||
|
import AlertWarning from "../../components/partials/alert/alert-warning";
|
||||||
|
import AlertError from '../../components/partials/alert/alert-error';
|
||||||
|
import AlertInfo from '../../components/partials/alert/alert-info';
|
||||||
|
|
||||||
|
export default function AlertsPage() {
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
const [loaded, setLoaded] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Initialize dark mode from localStorage
|
||||||
|
const savedDarkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||||
|
setDarkMode(savedDarkMode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Save dark mode preference to localStorage
|
||||||
|
localStorage.setItem('darkMode', JSON.stringify(darkMode));
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
return <Preloader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${darkMode ? 'dark bg-[#101828]' : ''}`}>
|
||||||
|
<div className="flex h-screen overflow-hidden">
|
||||||
|
{/* <Sidebar /> */}
|
||||||
|
|
||||||
|
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
||||||
|
<Overlay />
|
||||||
|
<div className='flex bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50 w-full'>
|
||||||
|
<Header />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div className="mx-auto max-w-[--breakpoint-2xl] p-4 md:p-6">
|
||||||
|
<Breadcrumb pageName="Alerts" />
|
||||||
|
|
||||||
|
<div className="space-y-5 sm:space-y-6">
|
||||||
|
<div className="rounded-2xl border border-[#E4E7EC] bg-white dark:border-[#1A2231] dark:bg-white/5">
|
||||||
|
<div className="px-6 py-5">
|
||||||
|
<h3 className="text-base font-medium text-[#1D2939] dark:text-white/90">
|
||||||
|
Success Alert
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-[#F2F4F7] p-4 dark:border-[#1A2231] sm:p-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<AlertSuccess />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-2xl border border-[#E4E7EC] bg-white dark:border-[#1A2231] dark:bg-white/5">
|
||||||
|
<div className="px-6 py-5">
|
||||||
|
<h3 className="text-base font-medium text-[#1D2939] dark:text-white/90">
|
||||||
|
Warning Alert
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-[#F2F4F7] p-4 dark:border-[#1A2231] sm:p-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<AlertWarning />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-2xl border border-[#E4E7EC] bg-white dark:border-[#1A2231] dark:bg-white/5">
|
||||||
|
<div className="px-6 py-5">
|
||||||
|
<h3 className="text-base font-medium text-[#1D2939] dark:text-white/90">
|
||||||
|
Error Alert
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-[#F2F4F7] p-4 dark:border-[#1A2231] sm:p-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<AlertError />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-2xl border border-[#E4E7EC] bg-white dark:border-[#1A2231] dark:bg-white/5">
|
||||||
|
<div className="px-6 py-5">
|
||||||
|
<h3 className="text-base font-medium text-[#1D2939] dark:text-white/90">
|
||||||
|
Info Alert
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-[#F2F4F7] p-4 dark:border-[#1A2231] sm:p-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<AlertInfo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
};
|
137
src/app/pages/avatars/page.js
Normal file
137
src/app/pages/avatars/page.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// app/avatars/page.tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Preloader from "../../components/partials/preloaders";
|
||||||
|
// import Sidebar from '@/components/partials/Sidebar';
|
||||||
|
import Overlay from "../../components/partials/overlay";
|
||||||
|
import Header from "../../components/partials/header";
|
||||||
|
import Breadcrumb from "../../components/partials/breadcrumb";
|
||||||
|
import Avatar01 from "../../components/partials/avatar/avatar01";
|
||||||
|
import Avatar02 from"../../components/partials/avatar/avatar02";
|
||||||
|
import Avatar03 from "../../components/partials/avatar/avatar03";
|
||||||
|
import Avatar04 from "../../components/partials/avatar/avatar04";
|
||||||
|
|
||||||
|
export default function AvatarsPage() {
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
const [stickyMenu, setStickyMenu] = useState(false);
|
||||||
|
const [sidebarToggle, setSidebarToggle] = useState(false);
|
||||||
|
const [scrollTop, setScrollTop] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const savedDarkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||||
|
setDarkMode(savedDarkMode);
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setLoaded(true);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('darkMode', JSON.stringify(darkMode));
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
|
const pageName = 'Avatars';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<div className={`${darkMode ? 'dark bg-gray-900' : ''}`}>
|
||||||
|
{/* Preloader */}
|
||||||
|
{!loaded && <Preloader />}
|
||||||
|
|
||||||
|
{/* Page Wrapper */}
|
||||||
|
<div className="flex h-screen overflow-hidden">
|
||||||
|
{/* Sidebar */}
|
||||||
|
{/* <Sidebar
|
||||||
|
activePage="avatars"
|
||||||
|
darkMode={darkMode}
|
||||||
|
sidebarToggle={sidebarToggle}
|
||||||
|
setSidebarToggle={setSidebarToggle}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
{/* Content Area */}
|
||||||
|
<div className="relative flex flex-col flex-1 overflow-x-hidden overflow-y-auto">
|
||||||
|
{/* Small Device Overlay */}
|
||||||
|
<Overlay
|
||||||
|
sidebarToggle={sidebarToggle}
|
||||||
|
setSidebarToggle={setSidebarToggle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<Header
|
||||||
|
darkMode={darkMode}
|
||||||
|
setDarkMode={setDarkMode}
|
||||||
|
stickyMenu={stickyMenu}
|
||||||
|
setStickyMenu={setStickyMenu}
|
||||||
|
sidebarToggle={sidebarToggle}
|
||||||
|
setSidebarToggle={setSidebarToggle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main>
|
||||||
|
<div className="p-4 mx-auto max-w-[--breakpoint-2xl] md:p-6">
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<Breadcrumb pageName={pageName} />
|
||||||
|
|
||||||
|
<div className="space-y-5 sm:space-y-6">
|
||||||
|
{/* Default Avatar */}
|
||||||
|
<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">
|
||||||
|
Default Avatar
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<Avatar01 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Avatar with online indicator */}
|
||||||
|
<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">
|
||||||
|
Avatar with online indicator
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<Avatar02 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Avatar with Offline indicator */}
|
||||||
|
<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">
|
||||||
|
Avatar with Offline indicator
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<Avatar03 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Avatar with busy indicator */}
|
||||||
|
<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">
|
||||||
|
Avatar with busy indicator
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 border-t border-gray-100 dark:border-gray-800">
|
||||||
|
<Avatar04 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
161
src/app/pages/badge/page.js
Normal file
161
src/app/pages/badge/page.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// app/badge/page.tsx
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import Preloader from "../../components/partials/preloaders";
|
||||||
|
// import Sidebar from '@/components/partials/Sidebar';
|
||||||
|
import Overlay from "../../components/partials/overlay";
|
||||||
|
import Header from "../../components/partials/header";
|
||||||
|
import Breadcrumb from "../../components/partials/breadcrumb";
|
||||||
|
import Badge01 from "../../components/partials/badge/Badge01";
|
||||||
|
import Badge02 from "../../components/partials/badge/Badge02";
|
||||||
|
import Badge03 from "../../components/partials/badge/Badge03";
|
||||||
|
import Badge04 from "../../components/partials/badge/Badge04";
|
||||||
|
import Badge05 from "../../components/partials/badge/Badge05";
|
||||||
|
import Badge06 from "../../components/partials/badge/Badge06";
|
||||||
|
|
||||||
|
export default function BadgePage() {
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
const [stickyMenu, setStickyMenu] = useState(false);
|
||||||
|
const [sidebarToggle, setSidebarToggle] = useState(false);
|
||||||
|
const [scrollTop, setScrollTop] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Load dark mode preference from localStorage
|
||||||
|
const savedDarkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||||
|
setDarkMode(savedDarkMode);
|
||||||
|
|
||||||
|
// Simulate loading
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setLoaded(true);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Save dark mode preference to localStorage
|
||||||
|
localStorage.setItem('darkMode', JSON.stringify(darkMode));
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
|
const pageName = 'Badge';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<div className={`${darkMode ? 'dark bg-gray-900' : ''}`}>
|
||||||
|
{/* Preloader */}
|
||||||
|
{!loaded && <Preloader />}
|
||||||
|
|
||||||
|
{/* Page Wrapper */}
|
||||||
|
<div className="flex h-screen overflow-hidden">
|
||||||
|
{/* Sidebar */}
|
||||||
|
{/* <Sidebar
|
||||||
|
activePage="badge"
|
||||||
|
darkMode={darkMode}
|
||||||
|
sidebarToggle={sidebarToggle}
|
||||||
|
setSidebarToggle={setSidebarToggle}
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
{/* Content Area */}
|
||||||
|
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
|
||||||
|
{/* Small Device Overlay */}
|
||||||
|
<Overlay
|
||||||
|
sidebarToggle={sidebarToggle}
|
||||||
|
setSidebarToggle={setSidebarToggle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div darkMode={darkMode} setDarkMode={setDarkMode} className='flex bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50 w-full'>
|
||||||
|
<Header />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main>
|
||||||
|
<div className="mx-auto max-w-[--breakpoint-2xl] p-4 md:p-6">
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<Breadcrumb pageName={pageName} />
|
||||||
|
|
||||||
|
<div className="space-y-5 sm:space-y-6">
|
||||||
|
{/* With Light Background */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge01 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* With Solid Background */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge02 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Light Background with Left Icon */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge03 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Solid Background with Left Icon */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge04 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Light Background with Right Icon */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge05 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Solid Background with Right Icon */}
|
||||||
|
<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="border-t border-gray-100 p-6 dark:border-gray-800 xl:p-10">
|
||||||
|
<Badge06 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,40 +1,11 @@
|
|||||||
"use client";
|
|
||||||
import TokenTable from "../../components/TokenTable";
|
import TokenTable from "../../components/TokenTable";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
|
||||||
|
|
||||||
export default function DoneTokensPage() {
|
export default function DoneTokensPage() {
|
||||||
const { darkMode } = useTheme();
|
|
||||||
|
|
||||||
// Handle theme loading state (consistent with dashboard)
|
|
||||||
if (darkMode === undefined) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${
|
|
||||||
darkMode === true ? 'border-gray-100' : 'border-gray-900'
|
|
||||||
}`}></div>
|
|
||||||
<p className={`mt-4 text-lg font-semibold ${
|
|
||||||
darkMode === true ? 'text-gray-800' : 'text-gray-900'
|
|
||||||
}`}>
|
|
||||||
Loading...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen p-6 transition-colors duration-300 ${
|
<div>
|
||||||
darkMode ? 'dark bg-gray-900' : 'bg-white'
|
<h1 className="text-2xl font-bold mb-4">Done Tokens</h1>
|
||||||
}`}>
|
<TokenTable statusFilter="done" />
|
||||||
<div className="max-w-7xl mx-auto">
|
|
||||||
<h1 className={`text-2xl font-bold mb-4 ${
|
|
||||||
darkMode ? 'text-gray-100' : 'text-gray-300'
|
|
||||||
}`}>
|
|
||||||
Done Tokens
|
|
||||||
</h1>
|
|
||||||
<TokenTable statusFilter="done" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -5,7 +5,6 @@ import { DATABASE_ID, COLLECTION_ID } from "../../lib/api";
|
|||||||
import Header from "../../components/partials/header";
|
import Header from "../../components/partials/header";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
import { useTheme } from "../../context/ThemeContext";
|
||||||
import { useAuth } from "../../context/AuthContext";
|
import { useAuth } from "../../context/AuthContext";
|
||||||
import { FaSearch } from 'react-icons/fa';
|
|
||||||
|
|
||||||
export default function EntriesTable() {
|
export default function EntriesTable() {
|
||||||
const { userRole } = useAuth();
|
const { userRole } = useAuth();
|
||||||
@ -14,7 +13,7 @@ export default function EntriesTable() {
|
|||||||
const [filteredEntries, setFilteredEntries] = useState([]);
|
const [filteredEntries, setFilteredEntries] = useState([]);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [avgConsultTime, setAvgConsultTime] = useState(15);
|
const [avgConsultTime, setAvgConsultTime] = useState(15); // Default 15 mins
|
||||||
const entriesPerPage = 20;
|
const entriesPerPage = 20;
|
||||||
|
|
||||||
const [currentToken, setCurrentToken] = useState(null);
|
const [currentToken, setCurrentToken] = useState(null);
|
||||||
@ -24,21 +23,23 @@ export default function EntriesTable() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
// Calculate waiting time based on position in queue
|
||||||
const calculateWaitTime = (tokenNumber) => {
|
const calculateWaitTime = (tokenNumber) => {
|
||||||
const bookedEntries = entries.filter((e) => e.status === "booked");
|
const bookedEntries = entries.filter(e => e.status === "booked");
|
||||||
const position = bookedEntries.findIndex(
|
const position = bookedEntries.findIndex(e => e.tokenNumber === tokenNumber);
|
||||||
(e) => e.tokenNumber === tokenNumber
|
return position >= 0 ? (position * avgConsultTime) : 0;
|
||||||
);
|
|
||||||
return position >= 0 ? position * avgConsultTime : 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntries = async () => {
|
const getEntries = async () => {
|
||||||
try {
|
try {
|
||||||
const today = new Date().toISOString().split("T")[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const response = await databases.listDocuments(
|
const response = await databases.listDocuments(
|
||||||
DATABASE_ID,
|
DATABASE_ID,
|
||||||
COLLECTION_ID,
|
COLLECTION_ID,
|
||||||
[Query.equal("date", today), Query.orderAsc("tokenNumber")],
|
[
|
||||||
|
Query.equal('date', today),
|
||||||
|
Query.orderAsc('tokenNumber')
|
||||||
|
],
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
return response.documents;
|
return response.documents;
|
||||||
@ -62,9 +63,7 @@ export default function EntriesTable() {
|
|||||||
|
|
||||||
setCurrentToken(current?.tokenNumber || null);
|
setCurrentToken(current?.tokenNumber || null);
|
||||||
setPreviousToken(currentIndex > 0 ? entries[currentIndex - 1]?.tokenNumber : null);
|
setPreviousToken(currentIndex > 0 ? entries[currentIndex - 1]?.tokenNumber : null);
|
||||||
setNextToken(
|
setNextToken(currentIndex < entries.length - 1 ? entries[currentIndex + 1]?.tokenNumber : null);
|
||||||
currentIndex < entries.length - 1 ? entries[currentIndex + 1]?.tokenNumber : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const missed = entries
|
const missed = entries
|
||||||
.filter((entry) => entry.status === "missed")
|
.filter((entry) => entry.status === "missed")
|
||||||
@ -76,9 +75,12 @@ export default function EntriesTable() {
|
|||||||
const updateStatus = async (entryId, newStatus) => {
|
const updateStatus = async (entryId, newStatus) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await databases.updateDocument(DATABASE_ID, COLLECTION_ID, entryId, {
|
await databases.updateDocument(
|
||||||
status: newStatus,
|
DATABASE_ID,
|
||||||
});
|
COLLECTION_ID,
|
||||||
|
entryId,
|
||||||
|
{ status: newStatus }
|
||||||
|
);
|
||||||
const data = await getEntries();
|
const data = await getEntries();
|
||||||
setEntries(data);
|
setEntries(data);
|
||||||
setFilteredEntries(data);
|
setFilteredEntries(data);
|
||||||
@ -92,13 +94,18 @@ export default function EntriesTable() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userRole === "patient") return;
|
if (userRole === "patient") return; // Don't load data if patient
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getEntries();
|
const data = await getEntries();
|
||||||
setEntries(data);
|
setEntries(data);
|
||||||
setFilteredEntries(data);
|
setFilteredEntries(data);
|
||||||
updateTokenInfo(data);
|
updateTokenInfo(data);
|
||||||
|
|
||||||
|
// Load settings (avg consultation time)
|
||||||
|
// const settings = await getSettings();
|
||||||
|
// setAvgConsultTime(settings?.avgConsultationTime || 15);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
@ -124,19 +131,17 @@ export default function EntriesTable() {
|
|||||||
const currentEntries = filteredEntries.slice(indexOfFirstEntry, indexOfLastEntry);
|
const currentEntries = filteredEntries.slice(indexOfFirstEntry, indexOfLastEntry);
|
||||||
const totalPages = Math.ceil(filteredEntries.length / entriesPerPage);
|
const totalPages = Math.ceil(filteredEntries.length / entriesPerPage);
|
||||||
|
|
||||||
if (loading)
|
if (loading) return <div className="text-center mt-50">
|
||||||
return (
|
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto`}></div>
|
||||||
<div className="text-center mt-20">
|
<p className={`mt-4 text-lg font-semibold`}>Loading...</p>
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto"></div>
|
</div>;
|
||||||
<p className="mt-4 text-lg font-semibold">Loading...</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) return <div className="p-4 text-red-500">Error: {error}</div>;
|
if (error) return <div className="p-4 text-red-500">Error: {error}</div>;
|
||||||
|
|
||||||
|
// Check user role and show permission message if patient
|
||||||
if (userRole === "patient") {
|
if (userRole === "patient") {
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen w-full ${darkMode ? "bg-gray-900 text-white" : "bg-white text-black"}`}>
|
<div className={`min-h-screen w-full ${darkMode ? 'bg-gray-900 text-white' : 'bg-white text-black'}`}>
|
||||||
<div className="flex items-center justify-center h-screen">
|
<div className="flex items-center justify-center h-screen">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="text-2xl font-bold mb-4">Access Denied</h1>
|
<h1 className="text-2xl font-bold mb-4">Access Denied</h1>
|
||||||
@ -148,141 +153,119 @@ export default function EntriesTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen w-full ${darkMode ? "bg-gray-900 text-white" : "bg-white text-black"}`}>
|
<div className={`min-h-screen w-full ${darkMode ? 'bg-gray-900 text-white' : 'bg-white text-black'}`}>
|
||||||
<div className="sticky top-0 z-10 bg-white shadow-md mb-4">
|
<div className="sticky top-0 z-10 bg-white shadow-md mb-4">
|
||||||
<Header
|
<Header
|
||||||
currentToken={currentToken}
|
currentToken={currentToken}
|
||||||
previousToken={previousToken}
|
previousToken={previousToken}
|
||||||
nextToken={nextToken}
|
nextToken={nextToken}
|
||||||
missedTokens={missedTokens}
|
missedTokens={missedTokens}
|
||||||
entries={entries}
|
entries={entries} // Pass the entries data
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold mb-4">Today's Entries</h1>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search by token or name"
|
||||||
|
className="mb-4 p-2 border rounded bg-white w-full max-w-sm"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="p-4">
|
{filteredEntries.length === 0 ? (
|
||||||
<h1 className="text-2xl font-bold mb-4">Today's Entries</h1>
|
<div className="p-4">No entries found for today.</div>
|
||||||
<div className={`
|
) : (
|
||||||
hidden md:flex items-center gap-[2px]
|
<>
|
||||||
border ${darkMode ? 'border-gray-600 bg-gray-800' : 'border-gray-300 bg-white'}
|
<table className="w-full border-collapse">
|
||||||
p-2 rounded-lg
|
<thead className={darkMode ? "bg-gray-800" : "bg-gray-100"}>
|
||||||
h-[45px] w-[300px] lg:w-[400px] ml-2 mb-6
|
<tr>
|
||||||
transition-colors duration-300
|
<th className="p-3 text-left">Token</th>
|
||||||
focus-within:ring-2 focus-within:ring-blue-500 focus-within:ring-opacity-50
|
<th className="p-3 text-left">Name</th>
|
||||||
${darkMode ? 'focus-within:border-gray-500' : 'focus-within:border-blue-500'}
|
{/* <th className="p-3 text-left">Booked By</th> */}
|
||||||
`}>
|
<th className="p-3 text-left">Status</th>
|
||||||
<div className="relative flex-1">
|
{/* <th className="p-3 text-left">Wait Time</th> */}
|
||||||
<FaSearch className={`
|
<th className="p-3 text-left">Actions</th>
|
||||||
absolute left-3 top-1/2 transform -translate-y-1/2
|
</tr>
|
||||||
${darkMode ? 'text-gray-400' : 'text-gray-500'}
|
</thead>
|
||||||
`} />
|
<tbody>
|
||||||
<input
|
{currentEntries.map((entry) => (
|
||||||
type="text"
|
<tr
|
||||||
placeholder="Search by token or name"
|
key={entry.$id}
|
||||||
className={`
|
className={`border ${darkMode ? 'border-gray-700' : 'border-gray-200'}`}
|
||||||
w-full bg-transparent border-none
|
>
|
||||||
pl-10 pr-10 py-2
|
<td className="p-3 font-mono">{entry.tokenNumber}</td>
|
||||||
focus:outline-none
|
<td className="p-3">{entry.patientName}</td>
|
||||||
${darkMode ? 'text-gray-200 placeholder-gray-500' : 'text-gray-800 placeholder-gray-400'}
|
{/* <td className="p-3">
|
||||||
text-sm
|
<span className={`text-xs px-2 py-1 rounded ${entry.bookedBy === 'staff' ?
|
||||||
`}
|
'bg-purple-100 text-purple-800' :
|
||||||
value={searchQuery}
|
'bg-blue-100 text-blue-800'
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
}`}>
|
||||||
/>
|
{entry.bookedBy}
|
||||||
</div>
|
</span>
|
||||||
<div className={`
|
</td> */}
|
||||||
px-2 py-1
|
<td className="p-3">
|
||||||
${darkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-100 text-gray-500'}
|
<span
|
||||||
text-xs rounded-md
|
className={`px-2 py-1 rounded-full text-xs ${entry.status === "done"
|
||||||
font-mono
|
? "bg-green-100 text-green-800"
|
||||||
`}>
|
: entry.status === "booked"
|
||||||
⌘K
|
? "bg-yellow-100 text-yellow-800"
|
||||||
</div>
|
: "bg-red-100 text-red-800"
|
||||||
</div>
|
} font-semibold`}
|
||||||
|
>
|
||||||
{filteredEntries.length === 0 ? (
|
{entry.status === "booked" ? "In-Queue" : entry.status}
|
||||||
<div className="p-4">No entries found for today.</div>
|
</span>
|
||||||
) : (
|
</td>
|
||||||
<>
|
{/* <td className="p-3">
|
||||||
<table className="w-full border-collapse">
|
{entry.status === "booked" ?
|
||||||
<thead className={darkMode ? "bg-gray-800" : "bg-gray-100"}>
|
`~${calculateWaitTime(entry.tokenNumber)} mins` :
|
||||||
<tr>
|
'-'}
|
||||||
<th className="p-3 text-left">Token</th>
|
</td> */}
|
||||||
<th className="p-3 text-left">Name</th>
|
<td className="p-3 flex gap-2">
|
||||||
<th className="p-3 text-left">Status</th>
|
{entry.status === "booked" && (
|
||||||
<th className="p-3 text-left">Actions</th>
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => updateStatus(entry.$id, "done")}
|
||||||
|
className="border border-green-600 text-green-600 rounded-full px-2 py-1 text-xs hover:bg-green-50"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => updateStatus(entry.$id, "missed")}
|
||||||
|
className="border border-red-600 text-red-600 rounded-full px-2 py-1 text-xs hover:bg-red-50"
|
||||||
|
>
|
||||||
|
Missed
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
))}
|
||||||
<tbody>
|
</tbody>
|
||||||
{currentEntries.map((entry) => (
|
</table>
|
||||||
<tr
|
|
||||||
key={entry.$id}
|
|
||||||
className={`border ${darkMode ? "border-gray-700" : "border-gray-200"}`}
|
|
||||||
>
|
|
||||||
<td className="p-3 font-mono">{entry.tokenNumber}</td>
|
|
||||||
<td className="p-3">{entry.patientName}</td>
|
|
||||||
<td className="p-3">
|
|
||||||
<span
|
|
||||||
className={`px-2 py-1 rounded-full text-xs font-semibold ${
|
|
||||||
entry.status === "done"
|
|
||||||
? "bg-green-100 text-green-800"
|
|
||||||
: entry.status === "booked"
|
|
||||||
? "bg-yellow-100 text-yellow-800"
|
|
||||||
: "bg-red-100 text-red-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{entry.status === "booked" ? "In-Queue" : entry.status}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="p-3 flex gap-2">
|
|
||||||
{entry.status === "booked" && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
onClick={() => updateStatus(entry.$id, "done")}
|
|
||||||
className="border border-green-600 text-green-600 rounded-full px-2 py-1 text-xs hover:bg-green-50"
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => updateStatus(entry.$id, "missed")}
|
|
||||||
className="border border-red-600 text-red-600 rounded-full px-2 py-1 text-xs hover:bg-red-50"
|
|
||||||
>
|
|
||||||
Missed
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<div
|
<div className={`flex justify-between items-center mt-4 ${darkMode ? 'text-white' : 'text-gray-700'
|
||||||
className={`flex justify-between items-center mt-6 ${
|
}`}>
|
||||||
darkMode ? "text-white" : "text-gray-700"
|
<button
|
||||||
}`}
|
disabled={currentPage === 1}
|
||||||
|
onClick={() => setCurrentPage((prev) => prev - 1)}
|
||||||
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<button
|
Previous
|
||||||
disabled={currentPage === 1}
|
</button>
|
||||||
onClick={() => setCurrentPage((prev) => prev - 1)}
|
<span className="text-sm">
|
||||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
Page {currentPage} of {totalPages}
|
||||||
>
|
</span>
|
||||||
Previous
|
<button
|
||||||
</button>
|
disabled={currentPage === totalPages}
|
||||||
<span className="text-sm">
|
onClick={() => setCurrentPage((prev) => prev + 1)}
|
||||||
Page {currentPage} of {totalPages}
|
className="px-3 py-1 border rounded disabled:opacity-50"
|
||||||
</span>
|
>
|
||||||
<button
|
Next
|
||||||
disabled={currentPage === totalPages}
|
</button>
|
||||||
onClick={() => setCurrentPage((prev) => prev + 1)}
|
</div>
|
||||||
className="px-3 py-1 border rounded disabled:opacity-50"
|
</>
|
||||||
>
|
)}
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,40 +1,10 @@
|
|||||||
"use client";
|
|
||||||
import TokenTable from "../../components/TokenTable";
|
import TokenTable from "../../components/TokenTable";
|
||||||
import { useTheme } from "../../context/ThemeContext";
|
|
||||||
|
|
||||||
export default function MissedTokensPage() {
|
export default function MissedTokensPage() {
|
||||||
const { darkMode } = useTheme();
|
|
||||||
|
|
||||||
// Handle theme loading state (consistent with dashboard)
|
|
||||||
if (darkMode === undefined) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className={`animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto ${
|
|
||||||
darkMode === true ? 'border-gray-100' : 'border-gray-900'
|
|
||||||
}`}></div>
|
|
||||||
<p className={`mt-4 text-lg font-semibold ${
|
|
||||||
darkMode === true ? 'text-gray-100' : 'text-gray-900'
|
|
||||||
}`}>
|
|
||||||
Loading...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`min-h-screen p-6 transition-colors duration-300 ${
|
<div>
|
||||||
darkMode ? 'dark bg-gray-900' : 'bg-white'
|
<h1 className="text-2xl font-bold mb-4">Missed Tokens</h1>
|
||||||
}`}>
|
<TokenTable statusFilter="missed" />
|
||||||
<div className="max-w-7xl mx-auto">
|
|
||||||
<h1 className={`text-2xl font-bold mb-4 ${
|
|
||||||
darkMode ? 'text-gray-100' : 'text-gray-800'
|
|
||||||
}`}>
|
|
||||||
Missed Tokens
|
|
||||||
</h1>
|
|
||||||
<TokenTable statusFilter="missed" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -107,7 +107,7 @@ export default function SignUpPage() {
|
|||||||
{/* Left side - Form */}
|
{/* Left side - Form */}
|
||||||
<div className="lg:w-1/2 w-full flex flex-col items-center justify-center p-4">
|
<div className="lg:w-1/2 w-full flex flex-col items-center justify-center p-4">
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
{/* <div className="w-full max-w-md pt-5 mx-auto sm:py-10">
|
<div className="w-full max-w-md pt-5 mx-auto sm:py-10">
|
||||||
<Link
|
<Link
|
||||||
href="/dashboard"
|
href="/dashboard"
|
||||||
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||||
@ -130,7 +130,7 @@ export default function SignUpPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
Back to dashboard
|
Back to dashboard
|
||||||
</Link>
|
</Link>
|
||||||
</div> */}
|
</div>
|
||||||
|
|
||||||
<div className="mb-5 sm:mb-8">
|
<div className="mb-5 sm:mb-8">
|
||||||
<h1 className="mb-2 font-semibold text-gray-800 text-title-sm dark:text-white/90 sm:text-title-md">
|
<h1 className="mb-2 font-semibold text-gray-800 text-title-sm dark:text-white/90 sm:text-title-md">
|
||||||
|
Loading…
Reference in New Issue
Block a user