);
-}
+}
\ 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) => (
+
+
+
+ Patient {booking.id} Name:
+
+ 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'}`}
+ />
+
+
+
+ Token:
+
+
+ {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
-
- Date:
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ Staff Multi-Booking
+
+
+
+ Date:
+
setAppointmentDate(e.target.value)}
+ className={`p-1 border rounded ${darkMode ? 'bg-gray-800 text-white border-gray-700' : 'bg-white'}`}
/>
-
+
-
-
- {[1, 2, 3, 4, 5].map((item) => (
-
-
- Enter Name
-
+
-
-
- Note: Token booking is available for current date only i.e.
- Today
-
+
+
Staff can book multiple tokens at once. All tokens will be assigned for the same date.
+
Minimum 1 patient required. Maximum 20 patients per batch.
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
-
- Date:
-
-
-
-
-
-
-
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
-
- Date:
+
- {/* ===== 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
-
- Date:
- setAppointmentDate(e.target.value)}
- className="ml-2"
- />
-
-
-
-
-
-

+
+ {/* 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
;
+
+ 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.
+ ) : (
+ <>
+
+
+
+ Token |
+ Name |
+ Booked By |
+ Status |
+ Wait Time |
+ Actions |
+
+
+
+ {currentEntries.map((entry) => (
+
+ {entry.tokenNumber} |
+ {entry.patientName} |
+
+
+ {entry.bookedBy}
+
+ |
+
+
+ {entry.status}
+
+ |
+
+ {entry.status === "booked" ?
+ `~${calculateWaitTime(entry.tokenNumber)} mins` :
+ '-'}
+ |
+
+ {entry.status === "booked" && (
+ <>
+
+
+ >
+ )}
+ |
+
+ ))}
+
+
-
-
-
Previous Token
-
J-210
-
-
-
-
-
-
-
-
-{/* ------------------------------------------------------------------- */}
-
-
-
-
-
-
- Token |
- Name |
- Options |
- Status |
-
-
-
- {entries.map((entry, index) => (
-
- {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