From eaff6a0b8a5f61b7c8c8aa257a052e60c1f2e1f3 Mon Sep 17 00:00:00 2001 From: Atul Gunjal Date: Wed, 16 Apr 2025 10:38:20 +0530 Subject: [PATCH] new booking implementation --- src/app/components/partials/header.jsx | 23 +- src/app/dashboard/page.js | 2 +- src/app/lib/api.js | 55 ++++ src/app/lib/appwrite.js | 125 +++++++- src/app/lib/config.js | 11 + src/app/page.js | 2 +- src/app/pages/MultiBooked/page.js | 249 +++++++++++----- src/app/pages/MultiBooking/page.js | 185 +++++++++--- src/app/pages/SingleBooked/page.js | 165 +++++++---- src/app/pages/SingleBooking/page.js | 139 +++------ src/app/pages/StaffBooking/page.js | 327 ++++++++++++++------- src/app/pages/entries/page.js | 377 ++++++++++++++++--------- 12 files changed, 1143 insertions(+), 517 deletions(-) create mode 100644 src/app/lib/api.js create mode 100644 src/app/lib/config.js diff --git a/src/app/components/partials/header.jsx b/src/app/components/partials/header.jsx index 3e9ac08..8fb977d 100644 --- a/src/app/components/partials/header.jsx +++ b/src/app/components/partials/header.jsx @@ -1,6 +1,9 @@ -"use client"; // Add this if you're using the App Router and need client-side interactivity +"use client"; + +export default function Header({ currentToken, previousToken, nextToken, missedTokens }) { + // Extract missed tokens from props or calculate them + const missedTokensList = missedTokens || "J-134, J-159"; // Default or from props -export default function Header() { return (
@@ -28,7 +31,7 @@ export default function Header() {

Missed Token

- J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159 + {missedTokensList}

); -} +} \ No newline at end of file diff --git a/src/app/dashboard/page.js b/src/app/dashboard/page.js index b5774d4..4c06d32 100644 --- a/src/app/dashboard/page.js +++ b/src/app/dashboard/page.js @@ -20,7 +20,7 @@ export default function DashboardPage() { return (
-
+

Loading...

diff --git a/src/app/lib/api.js b/src/app/lib/api.js new file mode 100644 index 0000000..f43a723 --- /dev/null +++ b/src/app/lib/api.js @@ -0,0 +1,55 @@ +// lib/api.js +import { databases, Query } from "./appwrite"; +import { DATABASE_ID, COLLECTION_ID } from "./config"; + +export async function getTodayTokens() { + const today = new Date().toISOString().split('T')[0]; + return databases.listDocuments( + DATABASE_ID, + COLLECTION_ID, + [ + Query.equal('date', today), + Query.orderAsc('tokenId') + ] + ); +} + +export async function getPatientTokens(patientId, date) { + return databases.listDocuments( + DATABASE_ID, + COLLECTION_ID, + [ + Query.equal('patientId', patientId), + Query.equal('date', date), + Query.notEqual('status', 'done') + ] + ); +} + +export async function createToken(document) { + return databases.createDocument( + DATABASE_ID, + COLLECTION_ID, + ID.unique(), + document + ); +} + +export async function updateTokenStatus(documentId, status) { + return databases.updateDocument( + DATABASE_ID, + COLLECTION_ID, + documentId, + { status } + ); +} + +export async function getSettings() { + return databases.getDocument( + DATABASE_ID, + 'settings', + 'current_settings' // Single document ID + ); +} + +export { DATABASE_ID, COLLECTION_ID }; \ No newline at end of file diff --git a/src/app/lib/appwrite.js b/src/app/lib/appwrite.js index 62110ca..e9ab6ce 100644 --- a/src/app/lib/appwrite.js +++ b/src/app/lib/appwrite.js @@ -1,14 +1,121 @@ -import { Client, Account, Databases, ID } from "appwrite"; +import { Client, Account, Databases, Query, ID } from "appwrite"; -// Initialize Appwrite Client -const client = new Client(); -client - .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) // Use environment variable - .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID); // Use environment variable +const client = new Client() + .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT) + .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID); -// Services const account = new Account(client); const databases = new Databases(client); -// Export everything, including `ID` -export { account, databases, ID }; \ No newline at end of file + +const DATABASE_ID = process.env.NEXT_PUBLIC_APPWRITE_DATABASE_ID; +const COLLECTION_ID = process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_ID; + +// Initialize with proper schema +async function initDatabase() { + try { + // Main tokens collection + await databases.createCollection( + DATABASE_ID, + COLLECTION_ID, + "Token Management" + ); + + // Add attributes matching new schema + await databases.createStringAttribute( + DATABASE_ID, + COLLECTION_ID, + "tokenId", + 20, + true + ); + + await databases.createDatetimeAttribute( + DATABASE_ID, + COLLECTION_ID, + "date", + true + ); + + await databases.createStringAttribute( + DATABASE_ID, + COLLECTION_ID, + "patientName", + 100, + true + ); + + await databases.createStringAttribute( + DATABASE_ID, + COLLECTION_ID, + "patientId", + 50, + false + ); + + await databases.createEnumAttribute( + DATABASE_ID, + COLLECTION_ID, + "bookedBy", + ["self", "staff"] + ); + + await databases.createEnumAttribute( + DATABASE_ID, + COLLECTION_ID, + "status", + ["booked", "done", "missed"] + ); + + // Settings collection + await databases.createCollection( + DATABASE_ID, + 'settings', + "System Settings" + ); + + await databases.createIntegerAttribute( + DATABASE_ID, + 'settings', + "avgConsultationTime", + true + ); + + } catch (error) { + console.error("Initialization error:", error); + if (!error.message.includes('already exists')) { + throw error; + } + } +} + +// Date-based token generator +// lib/appwrite.js + async function generateTokenNumber(date) { + // Step 1: Get all documents for the selected date + const response = await databases.listDocuments( + DATABASE_ID, + COLLECTION_ID, + [Query.equal("date", date)] + ); + + // Step 2: Get the next token count + const nextNumber = response.total + 1; + + // Step 3: Pad the number with zeros (e.g., 1 => 001) + const paddedToken = String(nextNumber).padStart(3, '0'); + + return paddedToken; +} + +export { + client, + account, + databases, + DATABASE_ID, + COLLECTION_ID, + Query, + ID, + initDatabase, + generateTokenNumber, +}; \ No newline at end of file diff --git a/src/app/lib/config.js b/src/app/lib/config.js new file mode 100644 index 0000000..68ac91d --- /dev/null +++ b/src/app/lib/config.js @@ -0,0 +1,11 @@ +// lib/config.js +// export const DATABASE_ID = "entries_db"; +// export const COLLECTION_ID = "entries"; +// export const TOKEN_COLLECTION_ID = "token_sequences"; + + +// lib/config.js +export const DATABASE_ID = "67e1452b00016444b37f"; +export const COLLECTION_ID = "67fe4029000f7e0a7b92"; +export const SETTINGS_COLLECTION_ID = "settings"; +export const AVG_CONSULTATION_KEY = "avg_consultation"; \ No newline at end of file diff --git a/src/app/page.js b/src/app/page.js index 7b95f43..67ceff5 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -26,7 +26,7 @@ export default function Home() { {/* Spinner */}
{/* Loading Text */} -

+

Loading...

diff --git a/src/app/pages/MultiBooked/page.js b/src/app/pages/MultiBooked/page.js index 8816927..53ba4c1 100644 --- a/src/app/pages/MultiBooked/page.js +++ b/src/app/pages/MultiBooked/page.js @@ -1,79 +1,190 @@ -import Head from 'next/head'; +"use client"; +import { useState, useEffect } from "react"; +import { databases, ID, Query } from "../../lib/appwrite"; +import { DATABASE_ID, COLLECTION_ID } from "../../lib/api"; import Header from "../../components/partials/header"; +import { useTheme } from "../../context/ThemeContext"; + export default function MultiBooked() { + const { darkMode } = useTheme(); + const [appointmentDate, setAppointmentDate] = useState(new Date().toISOString().split('T')[0]); + const [bookings, setBookings] = useState([ + { id: 1, name: "", tokenNumber: "" }, + { id: 2, name: "", tokenNumber: "" }, + { id: 3, name: "", tokenNumber: "" }, + { id: 4, name: "", tokenNumber: "" }, + { id: 5, name: "", tokenNumber: "" } + ]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const bookingData = sessionStorage.getItem('multiBookingData'); + if (bookingData) { + const { date, patients } = JSON.parse(bookingData); + setAppointmentDate(date); + setBookings(patients.map(p => ({ ...p, tokenNumber: "" }))); + sessionStorage.removeItem('multiBookingData'); + } + }, []); + +// ------------------create entries-------------------------------- +const createEntries = async () => { + setLoading(true); + setError(null); + + try { + const validBookings = bookings.filter(b => b.name.trim() !== ""); + + if (validBookings.length === 0) { + throw new Error("Please enter at least one patient name"); + } + + // Step 1: Get the highest token number + const response = await databases.listDocuments( + DATABASE_ID, + COLLECTION_ID, + [Query.orderDesc('tokenNumber'), Query.limit(1)] + ); + + let lastTokenNum = response.documents[0] + ? parseInt(response.documents[0].tokenNumber) + : 0; + + // Step 2: Prepare all token numbers first + const tokenAssignments = validBookings.map((_, index) => { + return (lastTokenNum + index + 1).toString().padStart(3, '0'); + }); + + // Step 3: Create documents and update state + const updatedBookings = [...bookings]; + let bookingIndex = 0; + + for (let i = 0; i < bookings.length; i++) { + if (bookings[i].name.trim() !== "") { + await databases.createDocument( + DATABASE_ID, + COLLECTION_ID, + ID.unique(), + { + tokenNumber: tokenAssignments[bookingIndex], + patientName: bookings[i].name, + date: appointmentDate, + bookedBy: "staff", + status: "booked", + patientId: ID.unique(), + } + ); + + updatedBookings[i].tokenNumber = tokenAssignments[bookingIndex]; + bookingIndex++; + } + } + + setBookings(updatedBookings); + alert(`${validBookings.length} tokens created successfully!`); + } catch (error) { + console.error("Creation error:", error); + setError(error.message); + } finally { + setLoading(false); + } +}; + + const handleNameChange = (id, value) => { + setBookings(bookings.map(b => + b.id === id ? { ...b, name: value } : b + )); + }; + return ( - <> - - eCommerce Dashboard | TailAdmin - Tailwind CSS Admin Dashboard Template - - - - +
+
+
-
- {/* Content Area */} -
- {/* Header would be included here */} -
+
+
+ {error && ( +
+ {error} +
+ )} - {/* Main Content */} -
-
-
-

Generate Appointment Code

-
-
+ +
+
+ {bookings.map((booking) => ( +
+
+ + handleNameChange(booking.id, e.target.value)} + placeholder="Enter patient name" + className={`w-full p-2 border rounded ${darkMode ? 'bg-gray-800 text-white border-gray-700' : 'bg-white'}`} + /> +
+
+ +
+ {booking.tokenNumber || "---"} +
+
+
+ ))} +
+ +
+ + + + +
+
+ +
+

Staff can book multiple tokens at once. All tokens will be assigned for the same date.

+
+
+
- +
); -} \ No newline at end of file +} diff --git a/src/app/pages/MultiBooking/page.js b/src/app/pages/MultiBooking/page.js index a522f59..23e3972 100644 --- a/src/app/pages/MultiBooking/page.js +++ b/src/app/pages/MultiBooking/page.js @@ -1,76 +1,173 @@ "use client"; -import Head from 'next/head'; import { useState } from 'react'; +import { useRouter } from 'next/navigation'; import Header from "../../components/partials/header"; +import { useTheme } from "../../context/ThemeContext"; -export default function BookAppointment() { - const [darkMode, setDarkMode] = useState(false); +export default function MultiBooking() { + const { darkMode } = useTheme(); + const router = useRouter(); + const [appointmentDate, setAppointmentDate] = useState(new Date().toISOString().split('T')[0]); + const [patients, setPatients] = useState( + Array(5).fill().map((_, i) => ({ id: i+1, name: "" })) + ); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const handleNameChange = (id, value) => { + setPatients(patients.map(p => + p.id === id ? { ...p, name: value } : p + )); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setError(null); + + try { + // Filter out empty names and validate + const validPatients = patients.filter(p => p.name.trim() !== ""); + if (validPatients.length === 0) { + throw new Error("Please enter at least one patient name"); + } + + // Store in session to use in MultiBooked page + sessionStorage.setItem('multiBookingData', JSON.stringify({ + date: appointmentDate, + patients: validPatients + })); + + router.push('/pages/MultiBooked'); + } catch (error) { + setError(error.message); + } finally { + setLoading(false); + } + }; + + const addMorePatients = () => { + const newId = patients.length > 0 ? + Math.max(...patients.map(p => p.id)) + 1 : 1; + setPatients([...patients, { id: newId, name: "" }]); + }; return ( -
- - eCommerce Dashboard | TailAdmin - Tailwind CSS Admin Dashboard Template - - - - - +
{/* Content Area */}
{/* Sticky Header */}
-
+
{/* Main Content */}
-
{/* Added pt-20 to account for sticky header */} -
-

Book Appointment

-
diff --git a/src/app/pages/SingleBooked/page.js b/src/app/pages/SingleBooked/page.js index bf93b4c..ab8441e 100644 --- a/src/app/pages/SingleBooked/page.js +++ b/src/app/pages/SingleBooked/page.js @@ -1,62 +1,123 @@ +"use client"; +import { useSearchParams } from 'next/navigation'; +import { useEffect, useState } 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'; -import Head from 'next/head'; import Link from 'next/link'; export default function SingleBooked() { + const searchParams = useSearchParams(); + const nameParam = searchParams.get('name'); + const dateParam = searchParams.get('date'); + + const [appointmentDate] = useState(dateParam || new Date().toISOString().split('T')[0]); + const [name] = useState(nameParam || ''); + const [token, setToken] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const generateTokenNumber = async (date) => { + try { + // Get the highest token number for the date + const response = await databases.listDocuments( + DATABASE_ID, + COLLECTION_ID, + [ + Query.equal('date', date), + Query.orderDesc('tokenNumber'), + Query.limit(1) + ] + ); + + const lastToken = response.documents[0]?.tokenNumber || "000"; + return (parseInt(lastToken) + 1).toString().padStart(3, '0'); + } catch (error) { + console.error("Token generation error:", error); + return "001"; // Fallback token number + } + }; + + const createBooking = async () => { + try { + setLoading(true); + setError(null); + + const generatedToken = await generateTokenNumber(appointmentDate); + + await databases.createDocument( + DATABASE_ID, + COLLECTION_ID, + ID.unique(), + { + tokenNumber: generatedToken, + date: appointmentDate, + patientName: name, + status: "booked", + bookedBy: "staff", + patientId: ID.unique(), + } + ); + + setToken(generatedToken); + } catch (error) { + console.error("Booking error:", error); + setError(error.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (name && appointmentDate) { + createBooking(); + } + }, [name, appointmentDate]); + return ( - <> - -
- {/* Sidebar would go here */} - -
- {/* Small Device Overlay would go here */} - - {/* Header would go here */} -
+
+
+
-
- -
-
-
-

Generate Appointment Code

- -
- -
-
-

35

-

John Doe

-
-
- - - - - - -
-
- -
-

Note: Token booking is available for current date only i.e. Today

-
-
-
+ +
+
+ {error && ( +
+ {error} +
+ )} + +
+

Staff Booking

+

{name || 'Staff Booking'}

+ +

Token Number:

+

+ {loading ? 'Generating...' : token || '---'} +

+
+ +
+ + + + + + +
+ +

+ Staff token booking system +

+
+
- +
); } \ No newline at end of file diff --git a/src/app/pages/SingleBooking/page.js b/src/app/pages/SingleBooking/page.js index cc9b722..254d959 100644 --- a/src/app/pages/SingleBooking/page.js +++ b/src/app/pages/SingleBooking/page.js @@ -1,21 +1,20 @@ 'use client'; import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; import Head from 'next/head'; -import Link from 'next/link'; -import Preloader from "../../components/partials/preloaders"; -// import Sidebar from '@/app/partials/sidebar'; -import Overlay from "../../components/partials/overlay"; -import Header from "../../components/partials/header"; +import Preloader from '../../components/partials/preloaders'; +import Overlay from '../../components/partials/overlay'; +import Header from '../../components/partials/header'; 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(() => { - // Load dark mode preference from localStorage const savedDarkMode = JSON.parse(localStorage.getItem('darkMode')); if (savedDarkMode !== null) { setDarkMode(savedDarkMode); @@ -24,13 +23,19 @@ export default function SingleBooking() { }, []); useEffect(() => { - // Save dark mode preference to localStorage when it changes localStorage.setItem('darkMode', JSON.stringify(darkMode)); }, [darkMode]); const handleSubmit = (e) => { e.preventDefault(); - // Handle form submission logic here + + if (!name || !appointmentDate) { + alert('Please fill all the fields'); + return; + } + + // Navigate to confirmation page with name and appointmentDate as query params + router.push(`/pages/SingleBooked?name=${encodeURIComponent(name)}&date=${encodeURIComponent(appointmentDate)}`); }; if (!loaded) { @@ -40,7 +45,7 @@ export default function SingleBooking() { return (
- eCommerce Dashboard | TailAdmin - Tailwind CSS Admin Dashboard Template + Book Appointment - {/* ===== Page Wrapper Start ===== */}
- {/* ===== Sidebar Start ===== */} - {/* */} - {/* ===== Sidebar End ===== */} - - {/* ===== Content Area Start ===== */}
- {/* Small Device Overlay Start */} - {/* Small Device Overlay End */} - {/* ===== Header Start ===== */} - -
-
-
- {/* ===== Header End ===== */} +
+
+
- {/* ===== Main Content Start ===== */}
-
-
-

Book Appointment

-
- {/* ===== Main Content End ===== */}
- {/* ===== Content Area End ===== */}
- {/* ===== Page Wrapper End ===== */}
); -} \ No newline at end of file +} diff --git a/src/app/pages/StaffBooking/page.js b/src/app/pages/StaffBooking/page.js index e6eaa3c..4ddaf87 100644 --- a/src/app/pages/StaffBooking/page.js +++ b/src/app/pages/StaffBooking/page.js @@ -1,133 +1,246 @@ -'use client'; // Required for client-side interactivity -import Image from 'next/image'; +'use client'; import { useState, useEffect } from 'react'; -import Head from 'next/head'; -import Link from 'next/link'; - -// Import components (you'll need to create these) -import Preloader from "../../components/partials/preloaders"; -// import Sidebar from '@/app/partials/sidebar'; -import Overlay from "../../components/partials/overlay"; +import { useRouter } from 'next/navigation'; +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"; -export default function BookingPage() { - const [darkMode, setDarkMode] = useState(false); - const [loaded, setLoaded] = useState(false); - const [appointmentDate, setAppointmentDate] = useState(''); +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 [stats, setStats] = useState({ + totalTokens: 0, + avgWaitTime: 0, + booked: 0, + done: 0, + missed: 0 + }); + const [loading, setLoading] = useState(true); useEffect(() => { - // Load dark mode preference from localStorage - const savedDarkMode = JSON.parse(localStorage.getItem('darkMode')); - if (savedDarkMode !== null) { - setDarkMode(savedDarkMode); - } - setLoaded(true); + const fetchData = 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, + [Query.equal('date', today)] + ); + + const allTokens = statsResponse.documents; + const bookedTokens = allTokens.filter(t => t.status === 'booked'); + 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); + } finally { + setLoading(false); + } + }; + + fetchData(); }, []); - useEffect(() => { - // Save dark mode preference to localStorage when it changes - localStorage.setItem('darkMode', JSON.stringify(darkMode)); - }, [darkMode]); + 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 (!loaded) { - return ; + if (loading) { + return
+
+
; } return ( -
- - eCommerce Dashboard | TailAdmin - Tailwind CSS Admin Dashboard Template - - - - - - {/* ===== Page Wrapper Start ===== */} +
- {/* ===== Sidebar Start ===== */} - {/* */} - {/* ===== Sidebar End ===== */} - - {/* ===== Content Area Start ===== */}
- {/* Small Device Overlay Start */} - - {/* Small Device Overlay End */} - - {/* ===== Header Start ===== */} -
+
- {/* ===== Header End ===== */} - {/* ===== Main Content Start ===== */} -
-
-
-

Book Appointment

- -
- -
-
- Appointment booking illustration +
+ {/* Statistics Overview */} +
+

+ Today's Overview +

+
+
+

Total Tokens

+

{stats.totalTokens}

-
- - - - - - +
+

Avg Wait Time

+

{stats.avgWaitTime} mins

+
+
+

Pending

+

{stats.booked}

+
+
+

Completed

+

{stats.done}

-
- -
-

Note: Token booking is available for current date only i.e. Today

+ +
+ {/* 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 +

+
+
+
+ +
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 +

+
+
+
+
+
+ + {/* Recent Activity */} +
+

+ Recent Activity +

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

+ {booking.patientName} +

+

+ Token: {booking.tokenId} +

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

+ No recent bookings found +

+ )} +
+
+
+ +
+

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()}.

+
- {/* ===== Main Content End ===== */}
- {/* ===== Content Area End ===== */}
- {/* ===== Page Wrapper End ===== */}
); } \ No newline at end of file diff --git a/src/app/pages/entries/page.js b/src/app/pages/entries/page.js index 01e8c66..bf81ca4 100644 --- a/src/app/pages/entries/page.js +++ b/src/app/pages/entries/page.js @@ -1,139 +1,256 @@ "use client"; -import React from "react"; +import { useEffect, useState } from "react"; +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"; -const EntriesTable = () => { - // Removed theme-related code - const entries = [ - { - token: 1, - name: "John Doe", - status: "Done", - statusColor: "text-green-500 bg-green-50", - }, - { - token: 2, - name: "Jane Smith", - status: "In-Queue", - statusColor: "text-amber-500 bg-amber-50", - }, - { - token: 3, - name: "Alice Johnson", - status: "Cancelled", - statusColor: "text-red-500 bg-red-50", - }, - { - token: 4, - name: "Bob Williams", - status: "Done", - statusColor: "text-green-500 bg-green-50", - }, - ]; +export default function EntriesTable() { + 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 [currentToken, setCurrentToken] = useState(null); + const [previousToken, setPreviousToken] = useState(null); + const [nextToken, setNextToken] = useState(null); + const [missedTokens, setMissedTokens] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Calculate waiting time based on position in queue + const calculateWaitTime = (tokenNumber) => { + const bookedEntries = entries.filter(e => e.status === "booked"); + const position = bookedEntries.findIndex(e => e.tokenNumber === tokenNumber); + return position >= 0 ? (position * avgConsultTime) : 0; + }; + + 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; + } + }; + + 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"); + }; + + const updateStatus = async (entryId, newStatus) => { + try { + setLoading(true); + await databases.updateDocument( + DATABASE_ID, + COLLECTION_ID, + entryId, + { status: newStatus } + ); + const data = await getEntries(); + setEntries(data); + setFilteredEntries(data); + updateTokenInfo(data); + } catch (error) { + console.error("Update error:", error); + setError(error.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + const loadData = async () => { + try { + const data = await getEntries(); + setEntries(data); + setFilteredEntries(data); + updateTokenInfo(data); + + // Load settings (avg consultation time) + // const settings = await getSettings(); + // setAvgConsultTime(settings?.avgConsultationTime || 15); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + loadData(); + }, []); + + useEffect(() => { + const filtered = entries.filter((entry) => { + return ( + entry.patientName.toLowerCase().includes(searchQuery.toLowerCase()) || + entry.tokenNumber.toString().includes(searchQuery) + ); + }); + setFilteredEntries(filtered); + setCurrentPage(1); + }, [searchQuery, entries]); + + const indexOfLastEntry = currentPage * entriesPerPage; + const indexOfFirstEntry = indexOfLastEntry - entriesPerPage; + const currentEntries = filteredEntries.slice(indexOfFirstEntry, indexOfLastEntry); + const totalPages = Math.ceil(filteredEntries.length / entriesPerPage); + + if (loading) return
+
+

Loading...

+
; + + if (error) return
Error: {error}
; return ( -
- {/* ----------------------------------------- */} -
-
-
-
- {[1, 2, 3, 4].map((item) => ( -
-

Total Patients

-
-

345

-

- +25% vs Last Week -

-
- -
- ))} -
+
+
+
+
+

Today's Entries

-
-

Missed Token

-

- J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159, J-134, J-159 -

- -
-
+ setSearchQuery(e.target.value)} + /> -
-
-

Current Token

-

J-369

-
+ {filteredEntries.length === 0 ? ( +
No entries found for today.
+ ) : ( + <> + + + + + + + + + + + + + {currentEntries.map((entry) => ( + + + + + + + + + ))} + +
TokenNameBooked ByStatusWait TimeActions
{entry.tokenNumber}{entry.patientName} + + {entry.bookedBy} + + + + {entry.status} + + + {entry.status === "booked" ? + `~${calculateWaitTime(entry.tokenNumber)} mins` : + '-'} + + {entry.status === "booked" && ( + <> + + + + )} +
-
-
-

Previous Token

-

J-210

-
-
-

Next Token

-

J-309

-
-
- - -
-
-
-{/* ------------------------------------------------------------------- */} - - -
- - - - - - - - - - - {entries.map((entry, index) => ( - - - - - - - - ))} - -
TokenNameOptionsStatus
{entry.token}{entry.name} -
- - -
-
- - {entry.status} - -
-
+ {/* Pagination */} +
+ + + Page {currentPage} of {totalPages} + + +
+ + )}
); -}; - -export default EntriesTable; \ No newline at end of file +} \ No newline at end of file