new updates
Build CuraFlow HMS / build-and-deploy (push) Successful in 44s
Details
Build CuraFlow HMS / build-and-deploy (push) Successful in 44s
Details
This commit is contained in:
parent
c4d751dba0
commit
e5e79d55ce
|
|
@ -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 {
|
class ConfigManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.config = {
|
this.tenants = {
|
||||||
|
'default': {
|
||||||
|
name: "CuraFlow Clinic",
|
||||||
|
logo: "C",
|
||||||
|
primaryColor: "#0F172A",
|
||||||
|
secondaryColor: "#38BDF8",
|
||||||
consultationFee: 500,
|
consultationFee: 500,
|
||||||
slotDuration: 30, // minutes
|
|
||||||
onlineSlotLimit: 3,
|
|
||||||
walkinSlotLimit: 5,
|
|
||||||
hospitalName: "Dr. Sharma's Clinic",
|
|
||||||
currency: "₹"
|
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) {
|
getTenantConfig(tenantId) {
|
||||||
return this.config[key];
|
return this.tenants[tenantId] || this.tenants['default'];
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
updateTenantConfig(tenantId, newConfig) {
|
||||||
this.config[key] = value;
|
if (!this.tenants[tenantId]) {
|
||||||
console.log(`Config updated: ${key} = ${value}`);
|
this.tenants[tenantId] = { ...this.tenants['default'] };
|
||||||
}
|
}
|
||||||
|
this.tenants[tenantId] = { ...this.tenants[tenantId], ...newConfig };
|
||||||
getAll() {
|
|
||||||
return this.config;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,65 +9,68 @@ const configManager = require('./configManager');
|
||||||
|
|
||||||
class QueueManager {
|
class QueueManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.slots = {}; // Key: "YYYY-MM-DD:HH:mm"
|
this.slots = {}; // Key: "tenantId:YYYY-MM-DD:HH:mm"
|
||||||
}
|
}
|
||||||
|
|
||||||
getSlotKey(date, time) {
|
getSlotKey(tenantId, date, time) {
|
||||||
// Round time to nearest slot duration
|
const config = configManager.getTenantConfig(tenantId);
|
||||||
const duration = configManager.get('slotDuration');
|
const duration = config.slotDuration || 30;
|
||||||
const [hours, minutes] = time.split(':');
|
const [hours, minutes] = time.split(':');
|
||||||
const roundedMins = parseInt(minutes) < duration ? '00' : duration;
|
const roundedMins = parseInt(minutes) < duration ? '00' : duration;
|
||||||
return `${date}:${hours}:${roundedMins}`;
|
return `${tenantId}:${date}:${hours}:${roundedMins}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSlotStatus(date, time) {
|
getSlotStatus(tenantId, date, time) {
|
||||||
const key = this.getSlotKey(date, time);
|
const key = this.getSlotKey(tenantId, date, time);
|
||||||
if (!this.slots[key]) {
|
if (!this.slots[key]) {
|
||||||
|
const config = configManager.getTenantConfig(tenantId);
|
||||||
this.slots[key] = {
|
this.slots[key] = {
|
||||||
online: 0,
|
online: 0,
|
||||||
walkin: 0,
|
walkin: 0,
|
||||||
maxOnline: configManager.get('onlineSlotLimit'),
|
maxOnline: config.onlineSlotLimit || 3,
|
||||||
maxWalkin: configManager.get('walkinSlotLimit')
|
maxWalkin: config.walkinSlotLimit || 5
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return this.slots[key];
|
return this.slots[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
bookOnline(date, time) {
|
bookOnline(date, time, tenantId = 'default') {
|
||||||
const status = this.getSlotStatus(date, time);
|
const status = this.getSlotStatus(tenantId, date, time);
|
||||||
|
const config = configManager.getTenantConfig(tenantId);
|
||||||
if (status.online < status.maxOnline) {
|
if (status.online < status.maxOnline) {
|
||||||
status.online++;
|
status.online++;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
token: `ON-${status.online}`,
|
token: `ON-${status.online}`,
|
||||||
slot: this.getSlotKey(date, time),
|
slot: this.getSlotKey(tenantId, date, time),
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
fee: configManager.get('consultationFee')
|
fee: config.consultationFee
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { success: false, message: "Slot full for online booking. Try next slot." };
|
return { success: false, message: "Slot full for online booking. Try next slot." };
|
||||||
}
|
}
|
||||||
|
|
||||||
bookWalkin(date, time) {
|
bookWalkin(date, time, tenantId = 'default') {
|
||||||
const status = this.getSlotStatus(date, time);
|
const status = this.getSlotStatus(tenantId, date, time);
|
||||||
|
const config = configManager.getTenantConfig(tenantId);
|
||||||
if (status.walkin < status.maxWalkin) {
|
if (status.walkin < status.maxWalkin) {
|
||||||
status.walkin++;
|
status.walkin++;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
token: `WK-${status.walkin}`,
|
token: `WK-${status.walkin}`,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
fee: configManager.get('consultationFee')
|
fee: config.consultationFee
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { success: false, message: "Clinic is at full capacity for this slot." };
|
return { success: false, message: "Clinic is at full capacity for this slot." };
|
||||||
}
|
}
|
||||||
|
|
||||||
getDailyUpdates(patientId) {
|
getDailyUpdates(patientId, tenantId = 'default') {
|
||||||
// Mock data for daily updates
|
const config = configManager.getTenantConfig(tenantId);
|
||||||
return [
|
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.",
|
"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."
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const botLogic = require('./botLogic');
|
const botLogic = require('./botLogic');
|
||||||
|
const configManager = require('./configManager');
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
|
|
||||||
|
|
@ -12,6 +13,12 @@ app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, '../login.html'));
|
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
|
// Mock WhatsApp Webhook
|
||||||
app.post('/whatsapp/webhook', async (req, res) => {
|
app.post('/whatsapp/webhook', async (req, res) => {
|
||||||
const { from, body } = req.body;
|
const { from, body } = req.body;
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
|
@ -25,6 +25,14 @@
|
||||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="user-profile">
|
||||||
|
<div class="avatar">BA</div>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Billing Admin</strong>
|
||||||
|
<span>Staff</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,22 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>CuraFlow Dashboard | Dr. Sharma's Clinic</title>
|
<title>Dashboard | CuraFlow</title>
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
<link rel="stylesheet" href="dashboard.css">
|
<link rel="stylesheet" href="dashboard.css">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||||
|
<script src="tenantLoader.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="dashboard-body">
|
<body class="dashboard-body">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<span class="logo-icon">C</span>
|
<span class="logo-icon">C</span>
|
||||||
<span class="logo-text">CuraFlow</span>
|
<span class="logo-text" data-tenant-name>CuraFlow</span>
|
||||||
</div>
|
</div>
|
||||||
<nav class="side-nav">
|
<nav class="side-nav">
|
||||||
<a href="dashboard.html" class="active"><span>🏠</span> Overview</a>
|
<a href="#" class="active" onclick="showSection('overview')"><span>🏠</span> Overview</a>
|
||||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
<a href="#" onclick="showSection('queue')"><span>👥</span> Queue</a>
|
||||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
<a href="#" onclick="showSection('patients')"><span>📂</span> Patients</a>
|
||||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||||
|
|
@ -29,6 +30,7 @@
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<strong>Dr. Sharma</strong>
|
<strong>Dr. Sharma</strong>
|
||||||
<span>Cardiologist</span>
|
<span>Cardiologist</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
@ -44,6 +46,7 @@
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div id="overview-section">
|
||||||
<section class="stats-row">
|
<section class="stats-row">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<span>In Queue</span>
|
<span>In Queue</span>
|
||||||
|
|
@ -70,7 +73,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="queue-section">
|
<section class="queue-section" id="queue-section">
|
||||||
<div class="section-card">
|
<div class="section-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2>Live Queue</h2>
|
<h2>Live Queue</h2>
|
||||||
|
|
@ -139,6 +142,46 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="patients-section" class="hidden">
|
||||||
|
<section class="section-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2>Patient Records</h2>
|
||||||
|
<button class="btn-primary">+ Add New Patient</button>
|
||||||
|
</div>
|
||||||
|
<table class="queue-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Patient ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>WhatsApp</th>
|
||||||
|
<th>Last Visit</th>
|
||||||
|
<th>Visits</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>CF-1001</td>
|
||||||
|
<td><strong>Rahul Verma</strong></td>
|
||||||
|
<td>+91 98765 43210</td>
|
||||||
|
<td>12 May 2026</td>
|
||||||
|
<td>5</td>
|
||||||
|
<td><button class="btn-small secondary">View History</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>CF-1002</td>
|
||||||
|
<td><strong>Anjali Singh</strong></td>
|
||||||
|
<td>+91 98234 56789</td>
|
||||||
|
<td>Today</td>
|
||||||
|
<td>2</td>
|
||||||
|
<td><button class="btn-small secondary">View History</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
<div id="prescriptionModal" class="modal">
|
<div id="prescriptionModal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|
|
||||||
25
dashboard.js
25
dashboard.js
|
|
@ -93,3 +93,28 @@ startRecordBtn.onclick = () => {
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Section Switching Logic
|
||||||
|
window.showSection = (sectionId) => {
|
||||||
|
// Hide all sections
|
||||||
|
document.getElementById('overview-section').classList.add('hidden');
|
||||||
|
document.getElementById('patients-section').classList.add('hidden');
|
||||||
|
|
||||||
|
// Show target section
|
||||||
|
if (sectionId === 'overview') {
|
||||||
|
document.getElementById('overview-section').classList.remove('hidden');
|
||||||
|
} else if (sectionId === 'queue') {
|
||||||
|
document.getElementById('overview-section').classList.remove('hidden');
|
||||||
|
document.getElementById('queue-section').scrollIntoView({ behavior: 'smooth' });
|
||||||
|
} else if (sectionId === 'patients') {
|
||||||
|
document.getElementById('patients-section').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update active state in nav
|
||||||
|
document.querySelectorAll('.side-nav a').forEach(link => {
|
||||||
|
link.classList.remove('active');
|
||||||
|
if (link.innerText.toLowerCase().includes(sectionId)) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
||||||
108
index.css
108
index.css
|
|
@ -432,3 +432,111 @@ footer {
|
||||||
.chat-container { grid-template-columns: 1fr; }
|
.chat-container { grid-template-columns: 1fr; }
|
||||||
.phone-mockup { margin: 0 auto; }
|
.phone-mockup { margin: 0 auto; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Pricing Section */
|
||||||
|
.pricing-section {
|
||||||
|
padding: 100px 0;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-top: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card {
|
||||||
|
background: white;
|
||||||
|
padding: 3rem;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.featured {
|
||||||
|
border-color: var(--secondary);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card.featured::after {
|
||||||
|
content: "Most Popular";
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: -35px;
|
||||||
|
background: var(--secondary);
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.5rem 3rem;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-card h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price span {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features {
|
||||||
|
list-style: none;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-features li::before {
|
||||||
|
content: "✓";
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logout & Shared Nav Utilities */
|
||||||
|
.logout-btn {
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
color: #F87171;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
57
index.html
57
index.html
|
|
@ -9,6 +9,7 @@
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<script src="tenantLoader.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="glass-nav">
|
<nav class="glass-nav">
|
||||||
|
|
@ -21,7 +22,7 @@
|
||||||
<a href="#features">Features</a>
|
<a href="#features">Features</a>
|
||||||
<a href="#whatsapp">WhatsApp Flow</a>
|
<a href="#whatsapp">WhatsApp Flow</a>
|
||||||
<a href="#pricing">Pricing</a>
|
<a href="#pricing">Pricing</a>
|
||||||
<button class="btn-primary">Get Started</button>
|
<a href="register.html" class="btn-primary" style="text-decoration: none;">Get Started</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -33,8 +34,8 @@
|
||||||
<h1>Chaotic OPDs are a thing of the <span class="text-gradient">past.</span></h1>
|
<h1>Chaotic OPDs are a thing of the <span class="text-gradient">past.</span></h1>
|
||||||
<p>Manage tokens, appointments, and patient records entirely through WhatsApp. Reduce waiting room crowds and automate admin work with AI-powered triage.</p>
|
<p>Manage tokens, appointments, and patient records entirely through WhatsApp. Reduce waiting room crowds and automate admin work with AI-powered triage.</p>
|
||||||
<div class="hero-btns">
|
<div class="hero-btns">
|
||||||
<button class="btn-primary btn-lg">Deploy to My Clinic</button>
|
<a href="register.html" class="btn-primary btn-lg" style="text-decoration: none;">Deploy to My Clinic</a>
|
||||||
<button class="btn-secondary btn-lg">Watch Demo</button>
|
<a href="#whatsapp" class="btn-secondary btn-lg" style="text-decoration: none;">Watch Demo</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-stats">
|
<div class="hero-stats">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
|
|
@ -135,9 +136,53 @@
|
||||||
<p>Send Razorpay links directly on WhatsApp. Integrated GST billing for pharmacy & labs.</p>
|
<p>Send Razorpay links directly on WhatsApp. Integrated GST billing for pharmacy & labs.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-item">
|
<div class="feature-item">
|
||||||
<div class="icon">📜</div>
|
<div class="icon">🏢</div>
|
||||||
<h3>Digital Prescriptions</h3>
|
<h3>Multi-Tenant Branding</h3>
|
||||||
<p>Doctors can generate prescriptions that are instantly sent to the patient's WhatsApp.</p>
|
<p>Enterprise ready. Full white-label support for hospital chains with custom logos, themes, and domains.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="pricing" class="pricing-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Simple, Transparent Pricing</h2>
|
||||||
|
<p>Choose the plan that fits your clinic's volume.</p>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-grid">
|
||||||
|
<div class="pricing-card">
|
||||||
|
<h3>Basic</h3>
|
||||||
|
<div class="price">₹1,499<span>/mo</span></div>
|
||||||
|
<ul class="pricing-features">
|
||||||
|
<li>Up to 500 tokens/mo</li>
|
||||||
|
<li>Basic WhatsApp Queue</li>
|
||||||
|
<li>Digital Prescriptions</li>
|
||||||
|
<li>Single Doctor Support</li>
|
||||||
|
</ul>
|
||||||
|
<a href="register.html" class="btn-secondary" style="display: block; text-decoration: none;">Choose Basic</a>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-card featured">
|
||||||
|
<h3>Pro</h3>
|
||||||
|
<div class="price">₹3,999<span>/mo</span></div>
|
||||||
|
<ul class="pricing-features">
|
||||||
|
<li>Unlimited Tokens</li>
|
||||||
|
<li>AI Triage & Summaries</li>
|
||||||
|
<li>Pharmacy & Lab Integration</li>
|
||||||
|
<li>Multi-Doctor Support</li>
|
||||||
|
</ul>
|
||||||
|
<a href="register.html" class="btn-primary" style="display: block; text-decoration: none;">Get Started Pro</a>
|
||||||
|
</div>
|
||||||
|
<div class="pricing-card">
|
||||||
|
<h3>Enterprise</h3>
|
||||||
|
<div class="price">Custom</div>
|
||||||
|
<ul class="pricing-features">
|
||||||
|
<li>Multi-Clinic Chain</li>
|
||||||
|
<li>Custom AI Training</li>
|
||||||
|
<li>White-label WhatsApp Bot</li>
|
||||||
|
<li>Dedicated Account Manager</li>
|
||||||
|
</ul>
|
||||||
|
<a href="register.html" class="btn-secondary" style="display: block; text-decoration: none;">Contact Sales</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
8
lab.html
8
lab.html
|
|
@ -25,6 +25,14 @@
|
||||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="user-profile">
|
||||||
|
<div class="avatar">LT</div>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Lab Tech</strong>
|
||||||
|
<span>Staff</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
<link rel="stylesheet" href="login.css">
|
<link rel="stylesheet" href="login.css">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||||
|
<script src="tenantLoader.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="auth-body">
|
<body class="auth-body">
|
||||||
<div class="auth-container">
|
<div class="auth-container">
|
||||||
|
|
@ -37,7 +38,14 @@
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('loginForm').onsubmit = (e) => {
|
document.getElementById('loginForm').onsubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const email = e.target.querySelector('input[type="text"]').value;
|
||||||
|
const password = e.target.querySelector('input[type="password"]').value;
|
||||||
|
|
||||||
|
if (email === 'deepkoluguri@gmail.com' && password === 'password123') {
|
||||||
window.location.href = 'dashboard.html';
|
window.location.href = 'dashboard.html';
|
||||||
|
} else {
|
||||||
|
alert('Invalid credentials. Hint: deepkoluguri@gmail.com / password123');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@
|
||||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="user-profile">
|
||||||
|
<div class="avatar">PH</div>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Pharmacy Head</strong>
|
||||||
|
<span>Staff</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@
|
||||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||||
<a href="settings.html" class="active"><span>⚙️</span> Settings</a>
|
<a href="settings.html" class="active"><span>⚙️</span> Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="user-profile">
|
||||||
|
<div class="avatar">AD</div>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Admin</strong>
|
||||||
|
<span>Settings</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@
|
||||||
<a href="staff.html" class="active"><span>👔</span> Staff & Payroll</a>
|
<a href="staff.html" class="active"><span>👔</span> Staff & Payroll</a>
|
||||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div class="user-profile">
|
||||||
|
<div class="avatar">SA</div>
|
||||||
|
<div class="info">
|
||||||
|
<strong>Staff Admin</strong>
|
||||||
|
<span>Staff</span>
|
||||||
|
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
Loading…
Reference in New Issue