From 6bce31176236ced060f9a8e230533cc024748d3f Mon Sep 17 00:00:00 2001 From: Atul Gunjal Date: Wed, 16 Apr 2025 17:50:08 +0530 Subject: [PATCH] added roles admin,patient,staff --- src/app/context/AuthContext.js | 43 ++++-- src/app/dashboard/page.js | 19 ++- src/app/lib/appwrite.js | 24 ++++ src/app/pages/SingleBooked/page.js | 7 +- src/app/pages/SingleBooking/page.js | 28 ++-- src/app/pages/StaffBooking/page.js | 196 +++++++--------------------- src/app/pages/entries/page.js | 22 +++- 7 files changed, 162 insertions(+), 177 deletions(-) diff --git a/src/app/context/AuthContext.js b/src/app/context/AuthContext.js index ce38335..01881e5 100644 --- a/src/app/context/AuthContext.js +++ b/src/app/context/AuthContext.js @@ -1,13 +1,14 @@ -// src/app/context/AuthContext.js -"use client"; // Mark this as a client component +"use client"; import { createContext, useContext, useState, useEffect } from "react"; -import { account } from "../lib/appwrite"; // Import Appwrite account +import { account,databases,DATABASE_ID,USERS_COLLECTION_ID,Query } from "../lib/appwrite"; import { useRouter } from "next/navigation"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + const [userRole, setUserRole] = useState(""); const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); const router = useRouter(); @@ -15,14 +16,32 @@ export const AuthProvider = ({ children }) => { useEffect(() => { const checkAuth = async () => { try { - await account.get(); // Check if the user is authenticated + const session = await account.get(); // Get user session setIsAuthenticated(true); + + const userId = session.$id; // Appwrite auth user ID + + // ✅ Fetch custom user data from your collection + const userData = await databases.listDocuments( + DATABASE_ID, + USERS_COLLECTION_ID, + [Query.equal("userId", userId)] + ); + + const userDoc = userData.documents[0]; + + if (userDoc) { + setUser(userDoc); + setUserRole(userDoc.role || "user"); + } else { + console.warn("User document not found in DB."); + } } catch (error) { + console.error("Auth Error:", error); if (error.code === 401) { - // Session expired or invalid localStorage.removeItem("isLoggedIn"); setIsAuthenticated(false); - router.push("/signup"); // Redirect to sign-in page + router.push("/signup"); } } finally { setLoading(false); @@ -33,10 +52,18 @@ export const AuthProvider = ({ children }) => { }, [router]); return ( - + {children} ); }; -export const useAuth = () => useContext(AuthContext); \ No newline at end of file +export const useAuth = () => useContext(AuthContext); diff --git a/src/app/dashboard/page.js b/src/app/dashboard/page.js index 4c06d32..aad7cec 100644 --- a/src/app/dashboard/page.js +++ b/src/app/dashboard/page.js @@ -5,9 +5,11 @@ import { useAuth } from "../context/AuthContext"; import { useEffect } from "react"; import { useTheme } from "../context/ThemeContext"; + export default function DashboardPage() { + const router = useRouter(); - const { isAuthenticated, loading } = useAuth(); + const { isAuthenticated, loading, userRole } = useAuth(); const { darkMode } = useTheme(); useEffect(() => { @@ -38,7 +40,20 @@ export default function DashboardPage() {

Dashboard

- + {/* -------------user role-------------- */} +

+ User Profile +

+
+
+
+

John Doe

+

john@example.com

+

Role: {userRole}

{/* 👈 Added */} +
+
+{/* */} + {/* 2x2 Grid Layout */}
{/* Box 1 - User Profile */} diff --git a/src/app/lib/appwrite.js b/src/app/lib/appwrite.js index e9ab6ce..58b03fb 100644 --- a/src/app/lib/appwrite.js +++ b/src/app/lib/appwrite.js @@ -10,6 +10,7 @@ const databases = new Databases(client); const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID; const COLLECTION_ID = process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_ID; +const USERS_COLLECTION_ID = process.env.NEXT_PUBLIC_APPWRITE_USERS_COLLECTION_ID; // Initialize with proper schema async function initDatabase() { @@ -107,13 +108,36 @@ async function initDatabase() { return paddedToken; } +// 🔐 Get User Role from `users` collection +async function getUserRole() { + try { + const user = await account.get(); + const userId = user.$id; + const response = await databases.listDocuments( + DATABASE_ID, + USERS_COLLECTION_ID, + [Query.equal("userId", userId)] + ); + + if (response.total === 0) { + throw new Error("User not found in users collection."); + } + + return response.documents[0].role; + } catch (error) { + console.error("Error fetching role:", error.message); + return null; + } +} export { + getUserRole, client, account, databases, DATABASE_ID, COLLECTION_ID, + USERS_COLLECTION_ID, Query, ID, initDatabase, diff --git a/src/app/pages/SingleBooked/page.js b/src/app/pages/SingleBooked/page.js index ab8441e..eab9708 100644 --- a/src/app/pages/SingleBooked/page.js +++ b/src/app/pages/SingleBooked/page.js @@ -1,6 +1,6 @@ "use client"; import { useSearchParams } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { databases, ID, Query } from "../../lib/appwrite"; // Added Query import import { DATABASE_ID, COLLECTION_ID } from "../../lib/api"; import Header from '../../components/partials/header'; @@ -16,6 +16,7 @@ export default function SingleBooked() { const [token, setToken] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const bookingCreated = useRef(false); const generateTokenNumber = async (date) => { try { @@ -39,7 +40,10 @@ export default function SingleBooked() { }; const createBooking = async () => { + if (bookingCreated.current) return; // Prevent duplicate calls + try { + bookingCreated.current = true; // Mark as created setLoading(true); setError(null); @@ -63,6 +67,7 @@ export default function SingleBooked() { } catch (error) { console.error("Booking error:", error); setError(error.message); + bookingCreated.current = false; // Reset on error to allow retry } finally { setLoading(false); } diff --git a/src/app/pages/SingleBooking/page.js b/src/app/pages/SingleBooking/page.js index 254d959..3b68654 100644 --- a/src/app/pages/SingleBooking/page.js +++ b/src/app/pages/SingleBooking/page.js @@ -11,7 +11,6 @@ export default function SingleBooking() { const [darkMode, setDarkMode] = useState(false); const [loaded, setLoaded] = useState(false); const [name, setName] = useState(''); - const [appointmentDate, setAppointmentDate] = useState(''); const router = useRouter(); useEffect(() => { @@ -29,12 +28,16 @@ export default function SingleBooking() { const handleSubmit = (e) => { e.preventDefault(); - if (!name || !appointmentDate) { - alert('Please fill all the fields'); + if (!name) { + alert('Please enter your name'); return; } - // Navigate to confirmation page with name and appointmentDate as query params + // Get current date in YYYY-MM-DD format + const today = new Date(); + 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)}`); }; @@ -67,18 +70,6 @@ export default function SingleBooking() {

Book Appointment

-
- - setAppointmentDate(e.target.value)} - 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" - /> -
-
@@ -101,7 +93,7 @@ export default function SingleBooking() {
- Note: Token booking is available for the current date only (Today). + Note: Token booking is available for today's date only.
@@ -109,4 +101,4 @@ export default function SingleBooking() { ); -} +} \ No newline at end of file diff --git a/src/app/pages/StaffBooking/page.js b/src/app/pages/StaffBooking/page.js index 4ddaf87..4e0f876 100644 --- a/src/app/pages/StaffBooking/page.js +++ b/src/app/pages/StaffBooking/page.js @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { databases, Query } from "../../lib/appwrite"; +import { databases,Query } from "../../lib/appwrite"; import { DATABASE_ID, COLLECTION_ID } from "../../lib/api"; import Header from "../../components/partials/header"; import { useTheme } from "../../context/ThemeContext"; @@ -9,11 +9,9 @@ import { useTheme } from "../../context/ThemeContext"; export default function StaffBooking() { const { darkMode } = useTheme(); const router = useRouter(); - const [appointmentDate, setAppointmentDate] = useState(new Date().toISOString().split('T')[0]); - const [recentBookings, setRecentBookings] = useState([]); + const [appointmentDate] = useState(new Date().toISOString().split('T')[0]); const [stats, setStats] = useState({ totalTokens: 0, - avgWaitTime: 0, booked: 0, done: 0, missed: 0 @@ -21,23 +19,9 @@ export default function StaffBooking() { const [loading, setLoading] = useState(true); useEffect(() => { - const fetchData = async () => { + const fetchStats = async () => { try { const today = new Date().toISOString().split('T')[0]; - - // Fetch recent bookings (last 5) - const recentResponse = await databases.listDocuments( - DATABASE_ID, - COLLECTION_ID, - [ - Query.equal('date', today), - Query.orderDesc('$createdAt'), - Query.limit(5) - ] - ); - setRecentBookings(recentResponse.documents); - - // Fetch statistics const statsResponse = await databases.listDocuments( DATABASE_ID, COLLECTION_ID, @@ -49,46 +33,23 @@ export default function StaffBooking() { const doneTokens = allTokens.filter(t => t.status === 'done'); const missedTokens = allTokens.filter(t => t.status === 'missed'); - // Calculate average wait time (simplified) - const avgWaitTime = bookedTokens.length > 0 ? - Math.round((bookedTokens.length * 15) / bookedTokens.length) : 0; - setStats({ totalTokens: allTokens.length, - avgWaitTime, booked: bookedTokens.length, done: doneTokens.length, missed: missedTokens.length }); } catch (error) { - console.error("Error fetching data:", error); + console.error("Error fetching statistics:", error); } finally { setLoading(false); } }; - fetchData(); + fetchStats(); }, []); - const updateStatus = async (documentId, newStatus) => { - try { - await databases.updateDocument( - DATABASE_ID, - COLLECTION_ID, - documentId, - { status: newStatus } - ); - // Refresh data after update - const updated = recentBookings.map(booking => - booking.$id === documentId ? { ...booking, status: newStatus } : booking - ); - setRecentBookings(updated); - } catch (error) { - console.error("Update error:", error); - } - }; - if (loading) { return
@@ -109,15 +70,11 @@ export default function StaffBooking() {

Today's Overview

-
+

Total Tokens

{stats.totalTokens}

-
-

Avg Wait Time

-

{stats.avgWaitTime} mins

-

Pending

{stats.booked}

@@ -129,112 +86,59 @@ export default function StaffBooking() {
-
- {/* Booking Options */} -
-

- New Booking -

-
-
router.push('/pages/SingleBooking')} - className={`p-6 cursor-pointer rounded-lg border transition-all hover:shadow-md ${darkMode ? - 'bg-gray-800 border-gray-700 hover:border-blue-600 hover:bg-gray-700' : - 'bg-white border-gray-200 hover:border-blue-500 hover:bg-blue-50' - }`} - > -
-
- - - -
-
-

Single Booking

-

- Book one token for a patient -

-
+ {/* Booking Options */} +
+

+ New Booking +

+
+
router.push('/pages/SingleBooking')} + className={`p-6 cursor-pointer rounded-lg border transition-all hover:shadow-md ${darkMode ? + 'bg-gray-800 border-gray-700 hover:border-blue-600 hover:bg-gray-700' : + 'bg-white border-gray-200 hover:border-blue-500 hover:bg-blue-50' + }`} + > +
+
+ + +
-
- -
router.push('/pages/MultiBooking')} - className={`p-6 cursor-pointer rounded-lg border transition-all hover:shadow-md ${darkMode ? - 'bg-gray-800 border-gray-700 hover:border-blue-600 hover:bg-gray-700' : - 'bg-white border-gray-200 hover:border-blue-500 hover:bg-blue-50' - }`} - > -
-
- - - -
-
-

Multi Booking

-

- Book multiple tokens at once -

-
+
+

Single Booking

+

+ Book one token for a patient +

-
- {/* Recent Activity */} -
-

- Recent Activity -

-
- {recentBookings.length > 0 ? ( - recentBookings.map(booking => ( -
-
-
-

- {booking.patientName} -

-

- Token: {booking.tokenId} -

-
- - {booking.status} - -
- {booking.status === 'booked' && ( -
- - -
- )} -
- )) - ) : ( -

- No recent bookings found -

- )} +
router.push('/pages/MultiBooking')} + className={`p-6 cursor-pointer rounded-lg border transition-all hover:shadow-md ${darkMode ? + 'bg-gray-800 border-gray-700 hover:border-blue-600 hover:bg-gray-700' : + 'bg-white border-gray-200 hover:border-blue-500 hover:bg-blue-50' + }`} + > +
+
+ + + +
+
+

Multi Booking

+

+ Book multiple tokens at once +

+
+
-
+

Staff can book tokens for patients. Choose single booking for individual patients or multi-booking for group entries.

All tokens will be assigned for {new Date(appointmentDate).toLocaleDateString()}.

diff --git a/src/app/pages/entries/page.js b/src/app/pages/entries/page.js index bf81ca4..3342329 100644 --- a/src/app/pages/entries/page.js +++ b/src/app/pages/entries/page.js @@ -4,15 +4,17 @@ import { databases, Query } from "../../lib/appwrite"; import { DATABASE_ID, COLLECTION_ID } from "../../lib/api"; import Header from "../../components/partials/header"; import { useTheme } from "../../context/ThemeContext"; +import { useAuth } from "../../context/AuthContext"; export default function EntriesTable() { + const { userRole } = useAuth(); const { darkMode } = useTheme(); const [entries, setEntries] = useState([]); const [filteredEntries, setFilteredEntries] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [avgConsultTime, setAvgConsultTime] = useState(15); // Default 15 mins - const entriesPerPage = 10; + const entriesPerPage = 20; const [currentToken, setCurrentToken] = useState(null); const [previousToken, setPreviousToken] = useState(null); @@ -92,6 +94,8 @@ export default function EntriesTable() { }; useEffect(() => { + if (userRole === "patient") return; // Don't load data if patient + const loadData = async () => { try { const data = await getEntries(); @@ -109,7 +113,7 @@ export default function EntriesTable() { } }; loadData(); - }, []); + }, [userRole]); useEffect(() => { const filtered = entries.filter((entry) => { @@ -134,6 +138,20 @@ export default function EntriesTable() { if (error) return
Error: {error}
; + // Check user role and show permission message if patient + if (userRole === "patient") { + return ( +
+
+
+

Access Denied

+

You don't have permission to view this page.

+
+
+
+ ); + } + return (