diff --git a/backend/configManager.js b/backend/configManager.js index 4103afa..be1463e 100644 --- a/backend/configManager.js +++ b/backend/configManager.js @@ -1,30 +1,46 @@ /** - * ConfigManager: Central configuration for hospital-wide pricing and slots. + * ConfigManager: Central configuration for multi-tenant hospital settings and white-labeling. */ class ConfigManager { constructor() { - this.config = { - consultationFee: 500, - slotDuration: 30, // minutes - onlineSlotLimit: 3, - walkinSlotLimit: 5, - hospitalName: "Dr. Sharma's Clinic", - currency: "₹" + this.tenants = { + 'default': { + name: "CuraFlow Clinic", + logo: "C", + primaryColor: "#0F172A", + secondaryColor: "#38BDF8", + consultationFee: 500, + currency: "₹" + }, + 'sharma-clinic': { + name: "Dr. Sharma's Cardiology", + logo: "S", + primaryColor: "#1E3A8A", // Deep Blue + secondaryColor: "#60A5FA", + consultationFee: 800, + currency: "₹" + }, + 'apollo-hospitals': { + name: "Apollo Multispecialty", + logo: "A", + primaryColor: "#065F46", // Dark Green + secondaryColor: "#34D399", + consultationFee: 1200, + currency: "₹" + } }; } - get(key) { - return this.config[key]; + getTenantConfig(tenantId) { + return this.tenants[tenantId] || this.tenants['default']; } - set(key, value) { - this.config[key] = value; - console.log(`Config updated: ${key} = ${value}`); - } - - getAll() { - return this.config; + updateTenantConfig(tenantId, newConfig) { + if (!this.tenants[tenantId]) { + this.tenants[tenantId] = { ...this.tenants['default'] }; + } + this.tenants[tenantId] = { ...this.tenants[tenantId], ...newConfig }; } } diff --git a/backend/queueManager.js b/backend/queueManager.js index 0c65c9c..e38e71d 100644 --- a/backend/queueManager.js +++ b/backend/queueManager.js @@ -9,65 +9,68 @@ const configManager = require('./configManager'); class QueueManager { constructor() { - this.slots = {}; // Key: "YYYY-MM-DD:HH:mm" + this.slots = {}; // Key: "tenantId:YYYY-MM-DD:HH:mm" } - getSlotKey(date, time) { - // Round time to nearest slot duration - const duration = configManager.get('slotDuration'); + getSlotKey(tenantId, date, time) { + const config = configManager.getTenantConfig(tenantId); + const duration = config.slotDuration || 30; const [hours, minutes] = time.split(':'); const roundedMins = parseInt(minutes) < duration ? '00' : duration; - return `${date}:${hours}:${roundedMins}`; + return `${tenantId}:${date}:${hours}:${roundedMins}`; } - getSlotStatus(date, time) { - const key = this.getSlotKey(date, time); + getSlotStatus(tenantId, date, time) { + const key = this.getSlotKey(tenantId, date, time); if (!this.slots[key]) { + const config = configManager.getTenantConfig(tenantId); this.slots[key] = { online: 0, walkin: 0, - maxOnline: configManager.get('onlineSlotLimit'), - maxWalkin: configManager.get('walkinSlotLimit') + maxOnline: config.onlineSlotLimit || 3, + maxWalkin: config.walkinSlotLimit || 5 }; } return this.slots[key]; } - bookOnline(date, time) { - const status = this.getSlotStatus(date, time); + bookOnline(date, time, tenantId = 'default') { + const status = this.getSlotStatus(tenantId, date, time); + const config = configManager.getTenantConfig(tenantId); if (status.online < status.maxOnline) { status.online++; return { success: true, token: `ON-${status.online}`, - slot: this.getSlotKey(date, time), + slot: this.getSlotKey(tenantId, date, time), status: 'PENDING_PAYMENT', - fee: configManager.get('consultationFee') + fee: config.consultationFee }; } return { success: false, message: "Slot full for online booking. Try next slot." }; } - bookWalkin(date, time) { - const status = this.getSlotStatus(date, time); + bookWalkin(date, time, tenantId = 'default') { + const status = this.getSlotStatus(tenantId, date, time); + const config = configManager.getTenantConfig(tenantId); if (status.walkin < status.maxWalkin) { status.walkin++; return { success: true, token: `WK-${status.walkin}`, status: 'PENDING_PAYMENT', - fee: configManager.get('consultationFee') + fee: config.consultationFee }; } return { success: false, message: "Clinic is at full capacity for this slot." }; } - getDailyUpdates(patientId) { - // Mock data for daily updates + getDailyUpdates(patientId, tenantId = 'default') { + const config = configManager.getTenantConfig(tenantId); return [ - "Good morning! Your appointment with Dr. Sharma is at 10:30 AM today.", + `Good morning! Your appointment with ${config.name} is scheduled for today.`, "Queue Update: The clinic is running 10 mins behind schedule. Please plan accordingly.", - "Reminder: Don't forget to bring your previous blood reports." + "Reminder: Don't forget to bring your previous reports." ]; } } diff --git a/backend/server.js b/backend/server.js index 7f131f5..f511cd9 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,7 @@ const express = require('express'); const path = require('path'); const botLogic = require('./botLogic'); +const configManager = require('./configManager'); const app = express(); const port = 3000; @@ -12,6 +13,12 @@ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../login.html')); }); +// Multi-tenant Config API +app.get('/api/config/:tenantId', (req, res) => { + const config = configManager.getTenantConfig(req.params.tenantId); + res.json(config); +}); + // Mock WhatsApp Webhook app.post('/whatsapp/webhook', async (req, res) => { const { from, body } = req.body; diff --git a/backend_tests.js b/backend_tests.js new file mode 100644 index 0000000..7027732 --- /dev/null +++ b/backend_tests.js @@ -0,0 +1,44 @@ +const queueManager = require('./backend/queueManager'); +const botLogic = require('./backend/botLogic'); +const configManager = require('./backend/configManager'); + +console.log("=== STARTING BACKEND UNIT TESTS ==="); + +// 1. Test QueueManager - Online Booking Limits +console.log("\nTesting QueueManager: Online Booking Limits"); +const date = "2026-05-13"; +const time = "10:00"; + +for (let i = 0; i < 4; i++) { + const res = queueManager.bookOnline(date, time); + console.log(`Booking ${i+1}:`, res.success ? `Success - ${res.token}` : `Failed - ${res.message}`); +} + +// 2. Test QueueManager - Walkin Booking +console.log("\nTesting QueueManager: Walk-in Booking"); +const walkRes = queueManager.bookWalkin(date, time); +console.log("Walkin result:", walkRes.token); + +// 3. Test Bot Logic - WhatsApp Simulation +console.log("\nTesting BotLogic: WhatsApp Interactions"); +async function testBot() { + const msg1 = await botLogic.handleMessage("9876543210", "Hi"); + console.log("User: Hi -> Bot:", msg1.reply); + + const msg2 = await botLogic.handleMessage("9876543210", "Book Token"); + console.log("User: Book Token -> Bot:", msg2.reply); + console.log("Buttons:", msg2.buttons); +} + +testBot(); + +// 4. Test ConfigManager - Multi-tenancy +console.log("\nTesting ConfigManager: Multi-tenancy"); +const sharmaConfig = configManager.getTenantConfig('sharma-clinic'); +console.log("Sharma Clinic Name:", sharmaConfig.name); + +// 5. Test Multi-tenant Booking +console.log("\nTesting Multi-tenant Booking (Sharma Clinic)"); +const sharmaRes = queueManager.bookOnline(date, time, 'sharma-clinic'); +console.log("Sharma Booking Result:", sharmaRes.success ? `Success - ${sharmaRes.token} (Fee: ${sharmaRes.fee})` : "Failed"); + diff --git a/billing.html b/billing.html index 5f0ae36..723d0f1 100644 --- a/billing.html +++ b/billing.html @@ -25,6 +25,14 @@ 👔 Staff & Payroll ⚙️ Settings +
+
BA
+
+ Billing Admin + Staff + 🚪 Logout +
+
diff --git a/dashboard.html b/dashboard.html index a3fe7f8..a246a1a 100644 --- a/dashboard.html +++ b/dashboard.html @@ -3,21 +3,22 @@ - CuraFlow Dashboard | Dr. Sharma's Clinic + Dashboard | CuraFlow + @@ -44,101 +46,142 @@ -
-
- In Queue -

12 Patients

- ↑ 4 since last hour -
-
- Current Slot (10:00-10:30) -
-
- Online: 3/3 (CLOSED) -
-
-
- Walk-in: 4/5 -
+
+
+
+ In Queue +

12 Patients

+ ↑ 4 since last hour +
+
+ Current Slot (10:00-10:30) +
+
+ Online: 3/3 (CLOSED) +
+
+
+ Walk-in: 4/5 +
+
-
-
- Avg. Wait Time -

18 Mins

- Target: 15 Mins -
-
+
+ Avg. Wait Time +

18 Mins

+ Target: 15 Mins +
+ -
-
-
-

Live Queue

-
- All - Appointments - Walk-ins +
+
+
+

Live Queue

+
+ All + Appointments + Walk-ins +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TokenPatient NameSourceLangTriage AIStatusArrivalActions
#012Rahul VermaOnlineHIHigh Fever / CoughIn Room09:15 AM
#013Anjali SinghWalk-inTEGeneral CheckupUnpaid (₹500)09:30 AM
#014Suresh KumarOnlineENBack PainPaid (Waiting)09:45 AM
+
+ +
+
+

✨ AI Insights

+

Predicted peak time: 11:30 AM. Suggesting 5-min break for staff now.

+
+ No-show Alert +

Token #015 (Vikram) hasn't replied to the 10-min reminder.

+
+
+
+
+
+ + - -
-
-

✨ AI Insights

-

Predicted peak time: 11:30 AM. Suggesting 5-min break for staff now.

-
- No-show Alert -

Token #015 (Vikram) hasn't replied to the 10-min reminder.

-
-
-
-
+ + + + +
+
+
+

Simple, Transparent Pricing

+

Choose the plan that fits your clinic's volume.

+
+
+
+

Basic

+
₹1,499/mo
+
    +
  • Up to 500 tokens/mo
  • +
  • Basic WhatsApp Queue
  • +
  • Digital Prescriptions
  • +
  • Single Doctor Support
  • +
+ Choose Basic +
+ +
+

Enterprise

+
Custom
+
    +
  • Multi-Clinic Chain
  • +
  • Custom AI Training
  • +
  • White-label WhatsApp Bot
  • +
  • Dedicated Account Manager
  • +
+ Contact Sales
diff --git a/lab.html b/lab.html index 2abc16a..62f1c55 100644 --- a/lab.html +++ b/lab.html @@ -25,6 +25,14 @@ 👔 Staff & Payroll ⚙️ Settings +
diff --git a/login.html b/login.html index cbdcad7..ad1be63 100644 --- a/login.html +++ b/login.html @@ -7,6 +7,7 @@ +
@@ -37,7 +38,14 @@ diff --git a/pharmacy.html b/pharmacy.html index 12f347f..f6e10b5 100644 --- a/pharmacy.html +++ b/pharmacy.html @@ -25,6 +25,14 @@ 👔 Staff & Payroll ⚙️ Settings +
diff --git a/settings.html b/settings.html index 257b137..27e12bb 100644 --- a/settings.html +++ b/settings.html @@ -25,6 +25,14 @@ 👔 Staff & Payroll ⚙️ Settings +
diff --git a/staff.html b/staff.html index edb1bca..f2d235d 100644 --- a/staff.html +++ b/staff.html @@ -25,6 +25,14 @@ 👔 Staff & Payroll ⚙️ Settings +
diff --git a/tenantLoader.js b/tenantLoader.js new file mode 100644 index 0000000..ecc28af --- /dev/null +++ b/tenantLoader.js @@ -0,0 +1,47 @@ +/** + * tenantLoader.js: Dynamically applies branding and multi-tenant settings. + */ + +async function loadTenantBranding() { + // Determine tenant from URL (e.g., ?tenant=sharma-clinic) or localStorage + const urlParams = new URLSearchParams(window.location.search); + let tenantId = urlParams.get('tenant') || localStorage.getItem('curaflow_tenant') || 'default'; + + // Save to localStorage for persistence across pages + localStorage.setItem('curaflow_tenant', tenantId); + + try { + const response = await fetch(`/api/config/${tenantId}`); + const config = await response.json(); + + applyBranding(config); + } catch (error) { + console.error("Failed to load tenant branding:", error); + } +} + +function applyBranding(config) { + // 1. Apply CSS Variables for dynamic coloring + document.documentElement.style.setProperty('--primary', config.primaryColor); + document.documentElement.style.setProperty('--secondary', config.secondaryColor); + + // 2. Update UI Elements (Logo, Name) + const logoIcons = document.querySelectorAll('.logo-icon'); + const logoTexts = document.querySelectorAll('.logo-text, .header-title h1, .auth-logo h1'); + const hospitalNames = document.querySelectorAll('.hospital-name, .logo-text'); + + logoIcons.forEach(icon => icon.innerText = config.logo); + + // Update Document Title + document.title = `${config.name} | CuraFlow`; + + // Update instances of hospital name in text + document.querySelectorAll('[data-tenant-name]').forEach(el => { + el.innerText = config.name; + }); + + console.log(`Branding applied for: ${config.name}`); +} + +// Auto-load on script include +document.addEventListener('DOMContentLoaded', loadTenantBranding);