fix: convert curio from submodule to directory in monorepo
Build MCP Services / build-mcp-filesystem (push) Successful in 1m12s
Details
Build MCP Services / build-mcp-filesystem (push) Successful in 1m12s
Details
This commit is contained in:
parent
00717a9f44
commit
58bb0a93ab
1
curio
1
curio
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 5949b8e12029ce9e3b0da4ec37c07aac34a3649d
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
name: Build Curio HMS
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_HOST: tcp://172.17.0.1:2375
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build and push (Kaniko)
|
||||
run: |
|
||||
JOB_CONTAINER=$(docker ps --format '{{.Names}}' | grep 'GITEA-ACTIONS-TASK' | head -1)
|
||||
docker run --rm \
|
||||
--volumes-from "$JOB_CONTAINER" \
|
||||
gcr.io/kaniko-project/executor:latest \
|
||||
--context=dir://"$GITHUB_WORKSPACE" \
|
||||
--dockerfile="$GITHUB_WORKSPACE/Dockerfile" \
|
||||
--destination=192.168.8.250:5000/curio:latest \
|
||||
--insecure \
|
||||
--skip-tls-verify
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "backend/server.js"]
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* BillingManager: Aggregates revenue from OP, Pharmacy, and Lab.
|
||||
*/
|
||||
|
||||
class BillingManager {
|
||||
constructor() {
|
||||
this.dailyCollections = {
|
||||
op: 4500, // Consultation fees
|
||||
pharmacy: 12450, // Medicine sales
|
||||
lab: 8200 // Diagnostic tests
|
||||
};
|
||||
this.transactions = [
|
||||
{ id: 'TXN-001', dept: 'OP', amount: 500, patient: 'Rahul Verma', status: 'PAID' },
|
||||
{ id: 'TXN-002', dept: 'Pharmacy', amount: 1200, patient: 'Anjali Singh', status: 'PAID' },
|
||||
{ id: 'TXN-003', dept: 'Lab', amount: 2500, patient: 'Suresh Kumar', status: 'PENDING' }
|
||||
];
|
||||
}
|
||||
|
||||
getDailySummary() {
|
||||
const total = this.dailyCollections.op + this.dailyCollections.pharmacy + this.dailyCollections.lab;
|
||||
return {
|
||||
...this.dailyCollections,
|
||||
total,
|
||||
growth: '+12.5%' // Simulated growth vs yesterday
|
||||
};
|
||||
}
|
||||
|
||||
getTransactions() {
|
||||
return this.transactions;
|
||||
}
|
||||
|
||||
addRevenue(dept, amount) {
|
||||
if (this.dailyCollections[dept.toLowerCase()] !== undefined) {
|
||||
this.dailyCollections[dept.toLowerCase()] += amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BillingManager();
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
const queueManager = require('./queueManager');
|
||||
const triageEngine = require('./triageEngine');
|
||||
const i18n = require('./i18n');
|
||||
|
||||
/**
|
||||
* BotLogic: Simulates the WhatsApp conversational flow
|
||||
*/
|
||||
class BotLogic {
|
||||
constructor() {
|
||||
this.states = {}; // Track session state per patient
|
||||
}
|
||||
|
||||
async handleMessage(patientId, message) {
|
||||
const text = message.toLowerCase();
|
||||
let state = this.states[patientId] || 'IDLE';
|
||||
let lang = this.states[`${patientId}_lang`] || 'en';
|
||||
|
||||
// 1. Language Selection (First time or reset)
|
||||
if (text.includes('hi') && !this.states[`${patientId}_lang`]) {
|
||||
this.states[patientId] = 'SELECT_LANG';
|
||||
return {
|
||||
reply: "Welcome to Curio! Please select your language / भाषा चुनें / భాషను ఎంచుకోండి:\n\n1. English\n2. Hindi (हिंदी)\n3. Telugu (తెలుగు)",
|
||||
buttons: ["English", "Hindi", "Telugu"]
|
||||
};
|
||||
}
|
||||
|
||||
if (state === 'SELECT_LANG') {
|
||||
if (text.includes('hindi')) lang = 'hi';
|
||||
if (text.includes('telugu')) lang = 'te';
|
||||
this.states[`${patientId}_lang`] = lang;
|
||||
this.states[patientId] = 'IDLE';
|
||||
state = 'IDLE'; // Continue to welcome
|
||||
}
|
||||
|
||||
// 2. Initial Greeting / Booking Intent
|
||||
if (state === 'IDLE') {
|
||||
return {
|
||||
reply: i18n.t(lang, 'welcome'),
|
||||
buttons: i18n.getButtons(lang)
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Booking Flow
|
||||
if (text.includes('book for today')) {
|
||||
return {
|
||||
reply: "Great! Please select your preferred time slot:",
|
||||
buttons: ["10:00 AM", "10:30 AM", "11:00 AM"]
|
||||
};
|
||||
}
|
||||
|
||||
if (text === '10:00 am' || text === '10:30 am') {
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
const result = queueManager.bookOnline(date, text.replace(' am', ''));
|
||||
|
||||
if (result.success) {
|
||||
this.states[patientId] = 'TRIAGE_START';
|
||||
return {
|
||||
reply: i18n.t(lang, 'triage_ask', { token: result.token }) +
|
||||
` \n\n💳 *Payment Required*: Please pay ₹${result.fee} to activate your token: http://razorpay.me/Curio_mock`,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
reply: "❌ Sorry, the 10:00 AM online slots are full (Max 3/slot). Would you like to try 11:30 AM?",
|
||||
buttons: ["Try 11:30 AM", "Waitlist Me"]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Triage State Handling
|
||||
if (this.states[patientId] === 'TRIAGE_START') {
|
||||
const analysis = await triageEngine.analyzeSymptoms(text);
|
||||
this.states[patientId] = 'TRIAGE_FOLLOWUP';
|
||||
this.states[`${patientId}_summary`] = analysis.summary;
|
||||
|
||||
return {
|
||||
reply: `Thank you. AI Analysis: _${analysis.category}_.\n\nQuick follow-up: ${analysis.followUp}`
|
||||
};
|
||||
}
|
||||
|
||||
if (this.states[patientId] === 'TRIAGE_FOLLOWUP') {
|
||||
this.states[patientId] = 'IDLE';
|
||||
return {
|
||||
reply: i18n.t(lang, 'triage_done')
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Daily Updates / Status
|
||||
if (text.includes('status') || text.includes('queue') || text.includes('कतार') || text.includes('స్థితి')) {
|
||||
return {
|
||||
reply: i18n.t(lang, 'status_update', { current: 'WK-3', pos: 'ON-1', wait: '8' })
|
||||
};
|
||||
}
|
||||
|
||||
if (text.includes('tips') || text.includes('update')) {
|
||||
const updates = queueManager.getDailyUpdates(patientId);
|
||||
return {
|
||||
reply: "🔔 *Daily Updates for You:*\n\n" + updates.join('\n\n')
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Default Fallback
|
||||
return {
|
||||
reply: "I'm sorry, I didn't quite catch that. You can type 'Hi' to see the main menu."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BotLogic();
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* ConfigManager: Central configuration for multi-tenant hospital settings and white-labeling.
|
||||
*/
|
||||
|
||||
class ConfigManager {
|
||||
constructor() {
|
||||
this.tenants = {
|
||||
'default': {
|
||||
name: "Curio 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: "₹"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getTenantConfig(tenantId) {
|
||||
return this.tenants[tenantId] || this.tenants['default'];
|
||||
}
|
||||
|
||||
updateTenantConfig(tenantId, newConfig) {
|
||||
if (!this.tenants[tenantId]) {
|
||||
this.tenants[tenantId] = { ...this.tenants['default'] };
|
||||
}
|
||||
this.tenants[tenantId] = { ...this.tenants[tenantId], ...newConfig };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ConfigManager();
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* i18n: Translation manager for Curio WhatsApp Bot.
|
||||
* Supports English (en), Hindi (hi), and Telugu (te).
|
||||
*/
|
||||
|
||||
const translations = {
|
||||
en: {
|
||||
welcome: "Welcome to Dr. Sharma's Clinic! 🏥 I can help you book a token or check your status.\n\nWould you like to:",
|
||||
book: "Book for Today",
|
||||
check: "Check Queue Status",
|
||||
tips: "Daily Health Tips",
|
||||
select_slot: "Great! Please select your preferred time slot:",
|
||||
triage_ask: "✅ Confirmed! Your token is *{{token}}*.\n\nTo help Dr. Sharma prepare, could you briefly describe your symptoms?",
|
||||
triage_done: "Got it. I've shared these details with Dr. Sharma. See you soon! 🏥",
|
||||
status_update: "Current Status: \n📍 Doctor is seeing: #{{current}}\n⏳ Your position: #{{pos}}\n🚀 Estimated wait: {{wait}} mins."
|
||||
},
|
||||
hi: {
|
||||
welcome: "डॉ. शर्मा के क्लिनिक में आपका स्वागत है! 🏥 मैं टोकन बुक करने या आपकी स्थिति की जांच करने में आपकी मदद कर सकता हूँ।\n\nक्या आप चाहेंगे:",
|
||||
book: "आज के लिए बुक करें",
|
||||
check: "कतार की स्थिति जांचें",
|
||||
tips: "स्वास्थ्य युक्तियाँ",
|
||||
select_slot: "बहुत बढ़िया! कृपया अपना पसंदीदा समय स्लॉट चुनें:",
|
||||
triage_ask: "✅ पुष्टि हो गई! आपका टोकन *{{token}}* है।\n\nडॉ. शर्मा को तैयारी में मदद करने के लिए, क्या आप अपने लक्षणों का संक्षेप में वर्णन कर सकते हैं?",
|
||||
triage_done: "समझ गया। मैंने ये विवरण डॉ. शर्मा के साथ साझा कर दिए हैं। जल्द मिलते हैं! 🏥",
|
||||
status_update: "वर्तमान स्थिति: \n📍 डॉक्टर देख रहे हैं: #{{current}}\n⏳ आपकी स्थिति: #{{pos}}\n🚀 अनुमानित प्रतीक्षा: {{wait}} मिनट।"
|
||||
},
|
||||
te: {
|
||||
welcome: "డాక్టర్ శర్మ క్లినిక్కి స్వాగతం! 🏥 టోకెన్ బుక్ చేసుకోవడంలో లేదా మీ స్థితిని తనిఖీ చేయడంలో నేను మీకు సహాయపడగలను.\n\nమీరు వీటిని చేయాలనుకుంటున్నారా:",
|
||||
book: "ఈరోజు కోసం బుక్ చేయండి",
|
||||
check: "క్యూ స్థితిని తనిఖీ చేయండి",
|
||||
tips: "ఆరోగ్య చిట్కాలు",
|
||||
select_slot: "అద్భుతం! దయచేసి మీకు నచ్చిన సమయాన్ని ఎంచుకోండి:",
|
||||
triage_ask: "✅ ధృవీకరించబడింది! మీ టోకెన్ *{{token}}*.\n\nడాక్టర్ శర్మ సిద్ధం కావడానికి సహాయపడటానికి, మీరు మీ లక్షణాలను క్లుప్తంగా వివరించగలరా?",
|
||||
triage_done: "అర్థమైంది. నేను ఈ వివరాలను డాక్టర్ శర్మతో పంచుకున్నాను. త్వరలో కలుద్దాం! 🏥",
|
||||
status_update: "ప్రస్తుత స్థితి: \n📍 డాక్టర్ చూస్తున్నారు: #{{current}}\n⏳ మీ స్థానం: #{{pos}}\n🚀 అంచనా వేచి ఉండే సమయం: {{wait}} నిమిషాలు."
|
||||
}
|
||||
};
|
||||
|
||||
class I18nManager {
|
||||
t(lang, key, params = {}) {
|
||||
let text = translations[lang] ? translations[lang][key] : translations['en'][key];
|
||||
|
||||
for (const [pKey, pVal] of Object.entries(params)) {
|
||||
text = text.replace(`{{${pKey}}}`, pVal);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
getButtons(lang) {
|
||||
const t = translations[lang] || translations['en'];
|
||||
return [t.book, t.check, t.tips];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new I18nManager();
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* LabManager: Handles test bookings, results, and AI explanations.
|
||||
*/
|
||||
|
||||
class LabManager {
|
||||
constructor() {
|
||||
this.tests = [
|
||||
{ id: 'T01', name: 'Complete Blood Count (CBC)', price: 450 },
|
||||
{ id: 'T02', name: 'Lipid Profile', price: 800 },
|
||||
{ id: 'T03', name: 'Blood Sugar (F/PP)', price: 150 },
|
||||
{ id: 'T04', name: 'Thyroid (T3 T4 TSH)', price: 650 },
|
||||
{ id: 'T05', name: 'Liver Function Test', price: 1200 }
|
||||
];
|
||||
this.records = [
|
||||
{ id: 'LAB-501', patientName: 'Suresh Kumar', testName: 'CBC + Blood Sugar', status: 'PROCESSING', urgency: 'URGENT' },
|
||||
{ id: 'LAB-502', patientName: 'Anjali Singh', testName: 'Lipid Profile', status: 'COLLECTING', urgency: 'ROUTINE' }
|
||||
];
|
||||
}
|
||||
|
||||
getAvailableTests() {
|
||||
return this.tests;
|
||||
}
|
||||
|
||||
createTestRequest(patientName, testId) {
|
||||
const test = this.tests.find(t => t.id === testId);
|
||||
const record = {
|
||||
id: `LAB-${Date.now()}`,
|
||||
patientName,
|
||||
testName: test.name,
|
||||
status: 'COLLECTING_SAMPLE',
|
||||
results: null,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
this.records.push(record);
|
||||
return record;
|
||||
}
|
||||
|
||||
updateResults(recordId, results) {
|
||||
const record = this.records.find(r => r.id === recordId);
|
||||
if (record) {
|
||||
record.results = results;
|
||||
record.status = 'COMPLETED';
|
||||
|
||||
// AI Explainer logic (Simulation)
|
||||
const explanation = `Your ${record.testName} results show ${results.hemoglobin < 12 ? 'low hemoglobin (anemia)' : 'normal levels'}. Please discuss with Dr. Sharma.`;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
explanation,
|
||||
whatsappMessage: `🔬 *Lab Report Ready*\n\n*Test:* ${record.testName}\n\n*AI Summary:* ${explanation}\n\n_Full report PDF attached._`
|
||||
};
|
||||
}
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new LabManager();
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* PharmacyManager: Handles inventory, prescriptions, and billing.
|
||||
*/
|
||||
|
||||
class PharmacyManager {
|
||||
constructor() {
|
||||
this.inventory = [
|
||||
{ id: 'M01', name: 'Paracetamol 500mg', stock: 120, price: 5 },
|
||||
{ id: 'M02', name: 'Amoxicillin 250mg', stock: 45, price: 12 },
|
||||
{ id: 'M03', name: 'Cough Syrup', stock: 4, price: 85 },
|
||||
{ id: 'M04', name: 'Vitamin C', stock: 200, price: 3 },
|
||||
{ id: 'M05', name: 'Azithromycin', stock: 30, price: 150 },
|
||||
{ id: 'M06', name: 'Insulin Glargine', stock: 12, price: 450 }
|
||||
];
|
||||
this.pendingOrders = [
|
||||
{ id: 'ORD-901', patientName: 'Rahul Verma', medicines: 'Paracetamol, Cough Syrup', status: 'PENDING', timestamp: '2026-05-13T09:15:00Z' },
|
||||
{ id: 'ORD-902', patientName: 'Anjali Singh', medicines: 'Amoxicillin', status: 'READY', timestamp: '2026-05-13T09:45:00Z' }
|
||||
];
|
||||
}
|
||||
|
||||
getInventory() {
|
||||
return this.inventory;
|
||||
}
|
||||
|
||||
addPendingOrder(patientName, medicines) {
|
||||
this.pendingOrders.push({
|
||||
id: `ORD-${Date.now()}`,
|
||||
patientName,
|
||||
medicines,
|
||||
status: 'PENDING',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
processOrder(orderId) {
|
||||
const order = this.pendingOrders.find(o => o.id === orderId);
|
||||
if (order) {
|
||||
order.status = 'READY';
|
||||
// In a real app, update inventory stock here
|
||||
return { success: true, message: `Order for ${order.patientName} is ready for pickup.` };
|
||||
}
|
||||
return { success: false, message: "Order not found." };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PharmacyManager();
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* PrescriptionManager: Handles digital prescription generation.
|
||||
*/
|
||||
|
||||
class PrescriptionManager {
|
||||
constructor() {
|
||||
this.prescriptions = [];
|
||||
}
|
||||
|
||||
generate(patientId, doctorId, medicines, diagnosis) {
|
||||
const id = `RX-${Date.now()}`;
|
||||
const prescription = {
|
||||
id,
|
||||
patientId,
|
||||
doctorId,
|
||||
date: new Date().toISOString(),
|
||||
diagnosis,
|
||||
medicines, // Array of { name, dosage, frequency, duration }
|
||||
};
|
||||
|
||||
this.prescriptions.push(prescription);
|
||||
|
||||
// In a real app, you'd generate a PDF here
|
||||
return {
|
||||
success: true,
|
||||
prescriptionId: id,
|
||||
whatsappMessage: `💊 *Digital Prescription from Dr. Sharma*\n\n*Diagnosis:* ${diagnosis}\n\n*Medicines:*\n${medicines.map(m => `- ${m.name}: ${m.dosage} (${m.frequency}) for ${m.duration}`).join('\n')}\n\n_You can show this message at our pharmacy for a 10% discount._`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new PrescriptionManager();
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* QueueManager: Handles the hybrid slot logic for Curio
|
||||
* - 30-min slots
|
||||
* - Max 3 online bookings (Hard Cap)
|
||||
* - 5 Walk-in slots
|
||||
*/
|
||||
|
||||
const configManager = require('./configManager');
|
||||
|
||||
class QueueManager {
|
||||
constructor() {
|
||||
this.slots = {}; // Key: "tenantId:YYYY-MM-DD:HH:mm"
|
||||
}
|
||||
|
||||
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 `${tenantId}:${date}:${hours}:${roundedMins}`;
|
||||
}
|
||||
|
||||
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: config.onlineSlotLimit || 3,
|
||||
maxWalkin: config.walkinSlotLimit || 5
|
||||
};
|
||||
}
|
||||
return this.slots[key];
|
||||
}
|
||||
|
||||
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(tenantId, date, time),
|
||||
status: 'PENDING_PAYMENT',
|
||||
fee: config.consultationFee
|
||||
};
|
||||
}
|
||||
return { success: false, message: "Slot full for online booking. Try next slot." };
|
||||
}
|
||||
|
||||
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: config.consultationFee
|
||||
};
|
||||
}
|
||||
return { success: false, message: "Clinic is at full capacity for this slot." };
|
||||
}
|
||||
|
||||
getDailyUpdates(patientId, tenantId = 'default') {
|
||||
const config = configManager.getTenantConfig(tenantId);
|
||||
return [
|
||||
`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 reports."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new QueueManager();
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
const express = require('express');
|
||||
const path = require('path');
|
||||
const botLogic = require('./botLogic');
|
||||
const configManager = require('./configManager');
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
app.use(express.json());
|
||||
// Serve static files
|
||||
app.use(express.static(path.join(__dirname, '../')));
|
||||
|
||||
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;
|
||||
console.log(`Received message from ${from}: ${body}`);
|
||||
|
||||
const response = await botLogic.handleMessage(from, body);
|
||||
|
||||
// In a real app, you'd call Twilio/Meta API here to send the reply
|
||||
res.json({
|
||||
success: true,
|
||||
reply: response.reply,
|
||||
buttons: response.buttons || []
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, '0.0.0.0', () => {
|
||||
console.log(`Curio WhatsApp Mock Server running at http://0.0.0.0:${port}`);
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* StaffManager: Handles staff records, attendance, and payroll.
|
||||
*/
|
||||
|
||||
class StaffManager {
|
||||
constructor() {
|
||||
this.staff = [
|
||||
{ id: 'S001', name: 'Dr. Sharma', role: 'Doctor', salary: 120000, incentives: 4500, status: 'PRESENT' },
|
||||
{ id: 'S002', name: 'Anjali Singh', role: 'Pharmacist', salary: 35000, incentives: 1200, status: 'PRESENT' },
|
||||
{ id: 'S003', name: 'Vikram Goel', role: 'Lab Tech', salary: 28000, incentives: 800, status: 'ABSENT' },
|
||||
{ id: 'S004', name: 'Priya Raj', role: 'Nurse', salary: 25000, incentives: 0, status: 'PRESENT' }
|
||||
];
|
||||
}
|
||||
|
||||
getStaffList() {
|
||||
return this.staff;
|
||||
}
|
||||
|
||||
calculatePayroll(staffId) {
|
||||
const member = this.staff.find(s => s.id === staffId);
|
||||
if (member) {
|
||||
return {
|
||||
base: member.salary,
|
||||
incentives: member.incentives,
|
||||
total: member.salary + member.incentives
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
updateStatus(staffId, status) {
|
||||
const member = this.staff.find(s => s.id === staffId);
|
||||
if (member) {
|
||||
member.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new StaffManager();
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* TriageEngine: Analyzes symptoms and generates follow-up questions.
|
||||
* In a real app, this would call Gemini 1.5 Flash.
|
||||
*/
|
||||
|
||||
class TriageEngine {
|
||||
constructor() {
|
||||
this.categories = {
|
||||
URGENT: { label: "Urgent", color: "#EF4444" },
|
||||
MODERATE: { label: "Moderate", color: "#F59E0B" },
|
||||
ROUTINE: { label: "Routine", color: "#10B981" }
|
||||
};
|
||||
}
|
||||
|
||||
async analyzeSymptoms(symptoms) {
|
||||
const text = symptoms.toLowerCase();
|
||||
|
||||
// Simple keyword-based triage (Simulation)
|
||||
if (text.includes('chest pain') || text.includes('breath') || text.includes('dizzy')) {
|
||||
return {
|
||||
category: "URGENT",
|
||||
summary: "Patient reporting possible cardiovascular or respiratory distress.",
|
||||
followUp: "How long have you been feeling this pain? Is it spreading to your arm or neck?"
|
||||
};
|
||||
}
|
||||
|
||||
if (text.includes('fever') || text.includes('cough') || text.includes('stomach')) {
|
||||
return {
|
||||
category: "MODERATE",
|
||||
summary: "Patient reporting common infection symptoms.",
|
||||
followUp: "What is your current temperature? Do you have any other symptoms like body ache?"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
category: "ROUTINE",
|
||||
summary: "General consultation or mild symptoms.",
|
||||
followUp: "Are you here for a regular checkup or a specific issue?"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new TriageEngine();
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* VoiceAI: Transcribes and extracts medical insights from doctor-patient conversations.
|
||||
* Uses AI to filter out small talk and focus on health details.
|
||||
*/
|
||||
|
||||
class VoiceAI {
|
||||
async extractInsights(transcript) {
|
||||
// In a real app, this would be a prompt to Gemini:
|
||||
// "Extract only medical advice, diagnosis, and next steps from this transcript. Ignore small talk."
|
||||
|
||||
const smallTalkKeywords = ['weather', 'family', 'how are you', 'cricket', 'politics'];
|
||||
|
||||
// Simulation of AI filtering
|
||||
const lines = transcript.split('. ');
|
||||
const medicalLines = lines.filter(line => {
|
||||
const isSmallTalk = smallTalkKeywords.some(kw => line.toLowerCase().includes(kw));
|
||||
return !isSmallTalk && line.length > 10;
|
||||
});
|
||||
|
||||
const summary = medicalLines.join('. ') + '.';
|
||||
|
||||
return {
|
||||
summary,
|
||||
whatsappMessage: `👨⚕️ *Doctor's Key Insights from Your Visit*\n\n${summary}\n\n_Stay healthy!_`
|
||||
};
|
||||
}
|
||||
|
||||
getMockTranscript() {
|
||||
return "Hello Rahul, how is your family? The weather is very hot today. Regarding your cough, it seems like a viral infection. You should avoid cold drinks for 3 days. Also, did you watch the match yesterday? Take the paracetamol after meals. I'll see you in a week.";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new VoiceAI();
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
.total-card {
|
||||
background: linear-gradient(135deg, var(--primary), #1E293B);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.total-card span {
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
.revenue-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 400px 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* Chart Mockup */
|
||||
.donut-chart-simulation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.chart-mock {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
background: conic-gradient(
|
||||
var(--secondary) 0% 18%,
|
||||
var(--accent) 18% 68%,
|
||||
#6366F1 68% 100%
|
||||
);
|
||||
position: relative;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.chart-mock::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
background: white;
|
||||
top: 30px;
|
||||
left: 30px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.dot.op { background: var(--secondary); }
|
||||
.dot.phar { background: var(--accent); }
|
||||
.dot.lab { background: #6366F1; }
|
||||
|
||||
.status-pill.paid {
|
||||
background: #DCFCE7;
|
||||
color: #166534;
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio Billing | Hospital Revenue & Accounts</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
<link rel="stylesheet" href="billing.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="dashboard.html"><span>🏠</span> Overview</a>
|
||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html" class="active"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||
</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>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="header-title">
|
||||
<h1>Hospital Accounts</h1>
|
||||
<p>Comprehensive daily revenue summary for the Hospital Owner.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn-primary">Download Day Report (PDF)</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stats-row">
|
||||
<div class="stat-card total-card">
|
||||
<span>Total Revenue (Today)</span>
|
||||
<h3>₹25,150</h3>
|
||||
<small class="text-accent">↑ 12.5% vs Yesterday</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>OP Consultation</span>
|
||||
<h3>₹4,500</h3>
|
||||
<small>9 Patients Paid</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Pharmacy Sales</span>
|
||||
<h3>₹12,450</h3>
|
||||
<small>18 Invoices</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Lab Collections</span>
|
||||
<h3>₹8,200</h3>
|
||||
<small>12 Tests</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="revenue-grid">
|
||||
<div class="section-card chart-container">
|
||||
<div class="card-header">
|
||||
<h2>Revenue Distribution</h2>
|
||||
</div>
|
||||
<div class="donut-chart-simulation">
|
||||
<!-- Placeholder for Chart.js -->
|
||||
<div class="chart-mock">
|
||||
<div class="slice op" style="--val: 18;"></div>
|
||||
<div class="slice phar" style="--val: 50;"></div>
|
||||
<div class="slice lab" style="--val: 32;"></div>
|
||||
</div>
|
||||
<div class="chart-legend">
|
||||
<div class="legend-item"><span class="dot op"></span> OP (18%)</div>
|
||||
<div class="legend-item"><span class="dot phar"></span> Pharmacy (50%)</div>
|
||||
<div class="legend-item"><span class="dot lab"></span> Lab (32%)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="card-header">
|
||||
<h2>Recent Transactions</h2>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Txn ID</th>
|
||||
<th>Patient</th>
|
||||
<th>Department</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>#TXN-882</td>
|
||||
<td><strong>Vikram Goel</strong></td>
|
||||
<td>Pharmacy</td>
|
||||
<td>₹1,240</td>
|
||||
<td><span class="status-pill in-room">Paid</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#TXN-881</td>
|
||||
<td><strong>Ananya Rao</strong></td>
|
||||
<td>Lab</td>
|
||||
<td>₹2,500</td>
|
||||
<td><span class="status-pill in-room">Paid</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#TXN-880</td>
|
||||
<td><strong>Rahul Verma</strong></td>
|
||||
<td>OP</td>
|
||||
<td>₹500</td>
|
||||
<td><span class="status-pill waiting">Pending</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,457 @@
|
|||
.dashboard-body {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
height: 100vh;
|
||||
background: #F1F5F9;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
color: white;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.side-nav a {
|
||||
color: #94A3B8;
|
||||
text-decoration: none;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.side-nav a:hover, .side-nav a.active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.side-nav a.nav-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.side-nav a.nav-disabled small {
|
||||
font-size: 0.6rem;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--secondary);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info span {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.dashboard-main {
|
||||
padding: 2rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.dash-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.search-bar input {
|
||||
width: 400px;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 99px;
|
||||
border: 1px solid #E2E8F0;
|
||||
outline: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.stats-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-card span {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.quota-bars {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quota-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quota-item small {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 6px;
|
||||
background: #E2E8F0;
|
||||
border-radius: 99px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fill {
|
||||
height: 100%;
|
||||
background: var(--secondary);
|
||||
border-radius: 99px;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
font-size: 1.75rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.text-accent { color: var(--accent); }
|
||||
|
||||
/* Queue Table */
|
||||
.queue-section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.section-card {
|
||||
background: white;
|
||||
border-radius: 24px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.queue-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.queue-table th {
|
||||
text-align: left;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #F1F5F9;
|
||||
}
|
||||
|
||||
.queue-table td {
|
||||
padding: 1.25rem 0;
|
||||
border-bottom: 1px solid #F1F5F9;
|
||||
}
|
||||
|
||||
.counter-badge {
|
||||
background: #F1F5F9;
|
||||
color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
padding: 0.6rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 99px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-pill.in-room {
|
||||
background: #DBEAFE;
|
||||
color: #1E40AF;
|
||||
}
|
||||
|
||||
.status-pill.waiting {
|
||||
background: #FEF3C7;
|
||||
color: #92400E;
|
||||
}
|
||||
|
||||
.status-pill.pending-pay {
|
||||
background: #FFEDD5;
|
||||
color: #C2410C;
|
||||
border: 1px solid #F97316;
|
||||
}
|
||||
|
||||
.source-tag {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.source-tag.online {
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.source-tag.walkin {
|
||||
background: rgba(100, 116, 139, 0.1);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 10% auto;
|
||||
padding: 2rem;
|
||||
width: 500px;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.med-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.med-row input {
|
||||
padding: 0.6rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.voice-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
background: #F8FAFC;
|
||||
border-radius: 16px;
|
||||
border: 1px dashed var(--border);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.recording-status {
|
||||
margin-top: 1rem;
|
||||
color: #EF4444;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.hidden { display: none !important; }
|
||||
|
||||
.pulse {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #EF4444;
|
||||
border-radius: 50%;
|
||||
animation: pulse-red 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-red {
|
||||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
|
||||
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
|
||||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
||||
}
|
||||
|
||||
.ai-insights-box {
|
||||
margin-top: 1rem;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
border-left: 4px solid var(--accent);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ai-insights-box h4 {
|
||||
font-size: 0.8rem;
|
||||
color: var(--accent);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ai-insights-box p {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-main);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
margin-top: 2rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.triage-pill {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.triage-pill.urgent {
|
||||
background: #FEE2E2;
|
||||
color: #991B1B;
|
||||
border-left: 3px solid #EF4444;
|
||||
}
|
||||
|
||||
.triage-pill.moderate {
|
||||
background: #FEF3C7;
|
||||
color: #92400E;
|
||||
border-left: 3px solid #F59E0B;
|
||||
}
|
||||
|
||||
.triage-pill.routine {
|
||||
background: #DCFCE7;
|
||||
color: #166534;
|
||||
border-left: 3px solid #10B981;
|
||||
}
|
||||
|
||||
.lang-tag {
|
||||
font-size: 0.7rem;
|
||||
background: #F1F5F9;
|
||||
color: var(--text-muted);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.btn-small.secondary {
|
||||
background: #F1F5F9;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* AI Insights Side Panel */
|
||||
.ai-insights {
|
||||
background: linear-gradient(135deg, #0F172A, #1E293B);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ai-insights h3 {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.ai-insights p {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.insight-item {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
border-left: 3px solid var(--accent);
|
||||
}
|
||||
|
||||
.insight-item small {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard | Curio</title>
|
||||
<link rel="stylesheet" href="index.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">
|
||||
<script src="tenantLoader.js"></script>
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text" data-tenant-name>Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="#" class="active" onclick="showSection('overview')"><span>🏠</span> Overview</a>
|
||||
<a href="#" onclick="showSection('queue')"><span>👥</span> Queue</a>
|
||||
<a href="#" onclick="showSection('patients')"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||
</nav>
|
||||
<div class="user-profile">
|
||||
<div class="avatar">DS</div>
|
||||
<div class="info">
|
||||
<strong>Dr. Sharma</strong>
|
||||
<span>Cardiologist</span>
|
||||
<a href="login.html" class="logout-btn"><span>🚪</span> Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="search-bar">
|
||||
<input type="text" placeholder="Search patients by name or WhatsApp number...">
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<span class="badge counter-badge">Reception Counter: 1</span>
|
||||
<button class="btn-primary">+ New Walk-in (Print Token)</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="overview-section">
|
||||
<section class="stats-row">
|
||||
<div class="stat-card">
|
||||
<span>In Queue</span>
|
||||
<h3>12 Patients</h3>
|
||||
<small class="text-accent">↑ 4 since last hour</small>
|
||||
</div>
|
||||
<div class="stat-card quota-card">
|
||||
<span>Current Slot (10:00-10:30)</span>
|
||||
<div class="quota-bars">
|
||||
<div class="quota-item closed">
|
||||
<small>Online: 3/3 (CLOSED)</small>
|
||||
<div class="bar"><div class="fill" style="width: 100%; background: var(--text-muted);"></div></div>
|
||||
</div>
|
||||
<div class="quota-item">
|
||||
<small>Walk-in: 4/5</small>
|
||||
<div class="bar"><div class="fill" style="width: 80%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Avg. Wait Time</span>
|
||||
<h3>18 Mins</h3>
|
||||
<small class="text-muted">Target: 15 Mins</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="queue-section" id="queue-section">
|
||||
<div class="section-card">
|
||||
<div class="card-header">
|
||||
<h2>Live Queue</h2>
|
||||
<div class="filters">
|
||||
<span class="badge">All</span>
|
||||
<span class="badge muted">Appointments</span>
|
||||
<span class="badge muted">Walk-ins</span>
|
||||
</div>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token</th>
|
||||
<th>Patient Name</th>
|
||||
<th>Source</th>
|
||||
<th>Lang</th>
|
||||
<th>Triage AI</th>
|
||||
<th>Status</th>
|
||||
<th>Arrival</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="active-row">
|
||||
<td>#012</td>
|
||||
<td><strong>Rahul Verma</strong></td>
|
||||
<td><span class="source-tag online">Online</span></td>
|
||||
<td><span class="lang-tag">HI</span></td>
|
||||
<td><span class="triage-pill urgent">High Fever / Cough</span></td>
|
||||
<td><span class="status-pill in-room">In Room</span></td>
|
||||
<td>09:15 AM</td>
|
||||
<td><button class="btn-small">Prescribe</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#013</td>
|
||||
<td><strong>Anjali Singh</strong></td>
|
||||
<td><span class="source-tag walkin">Walk-in</span></td>
|
||||
<td><span class="lang-tag">TE</span></td>
|
||||
<td><span class="triage-pill moderate">General Checkup</span></td>
|
||||
<td><span class="status-pill pending-pay">Unpaid (₹500)</span></td>
|
||||
<td>09:30 AM</td>
|
||||
<td><button class="btn-small secondary">Collect Pay</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#014</td>
|
||||
<td><strong>Suresh Kumar</strong></td>
|
||||
<td><span class="source-tag online">Online</span></td>
|
||||
<td><span class="lang-tag">EN</span></td>
|
||||
<td><span class="triage-pill routine">Back Pain</span></td>
|
||||
<td><span class="status-pill waiting">Paid (Waiting)</span></td>
|
||||
<td>09:45 AM</td>
|
||||
<td><button class="btn-small secondary">Notify</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="side-panel">
|
||||
<div class="section-card ai-insights">
|
||||
<h3>✨ AI Insights</h3>
|
||||
<p>Predicted peak time: <strong>11:30 AM</strong>. Suggesting 5-min break for staff now.</p>
|
||||
<div class="insight-item">
|
||||
<small>No-show Alert</small>
|
||||
<p>Token #015 (Vikram) hasn't replied to the 10-min reminder.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Digital Prescription</h2>
|
||||
<span class="close-modal">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="voice-section">
|
||||
<button class="btn-secondary" id="startRecording"><span>🎤</span> Record Consultation</button>
|
||||
<div id="recordingStatus" class="recording-status hidden">
|
||||
<span class="pulse"></span> Recording...
|
||||
</div>
|
||||
<div id="aiInsights" class="ai-insights-box hidden">
|
||||
<h4>✨ AI Insights (Draft)</h4>
|
||||
<p id="insightText"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Diagnosis</label>
|
||||
<input type="text" id="rxDiagnosis" placeholder="e.g. Viral Fever">
|
||||
</div>
|
||||
<div class="medicine-list" id="medicineList">
|
||||
<div class="med-row">
|
||||
<input type="text" placeholder="Medicine Name" class="med-name">
|
||||
<input type="text" placeholder="Dosage" class="med-dosage">
|
||||
<input type="text" placeholder="Freq" class="med-freq">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-secondary" id="addMed">+ Add Medicine</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-primary" id="sendRx">Send to WhatsApp</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
const modal = document.getElementById("prescriptionModal");
|
||||
const addMedBtn = document.getElementById("addMed");
|
||||
const medList = document.getElementById("medicineList");
|
||||
const sendRxBtn = document.getElementById("sendRx");
|
||||
const closeModal = document.querySelector(".close-modal");
|
||||
const startRecordBtn = document.getElementById("startRecording");
|
||||
const recordingStatus = document.getElementById("recordingStatus");
|
||||
const aiInsightsBox = document.getElementById("aiInsights");
|
||||
const insightText = document.getElementById("insightText");
|
||||
|
||||
// Open modal when clicking 'Prescribe'
|
||||
document.querySelectorAll(".btn-small").forEach(btn => {
|
||||
if (btn.innerText === "Prescribe") {
|
||||
btn.addEventListener("click", () => {
|
||||
modal.style.display = "block";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
closeModal.onclick = () => modal.style.display = "none";
|
||||
window.onclick = (event) => {
|
||||
if (event.target == modal) modal.style.display = "none";
|
||||
};
|
||||
|
||||
// Add new medicine row
|
||||
addMedBtn.onclick = () => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "med-row";
|
||||
row.innerHTML = `
|
||||
<input type="text" placeholder="Medicine Name" class="med-name">
|
||||
<input type="text" placeholder="Dosage" class="med-dosage">
|
||||
<input type="text" placeholder="Freq" class="med-freq">
|
||||
`;
|
||||
medList.appendChild(row);
|
||||
};
|
||||
|
||||
// Handle sending prescription
|
||||
sendRxBtn.onclick = () => {
|
||||
const diagnosis = document.getElementById("rxDiagnosis").value;
|
||||
const meds = [];
|
||||
document.querySelectorAll(".med-row").forEach(row => {
|
||||
const name = row.querySelector(".med-name").value;
|
||||
if (name) {
|
||||
meds.push({
|
||||
name,
|
||||
dosage: row.querySelector(".med-dosage").value,
|
||||
frequency: row.querySelector(".med-freq").value
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!diagnosis || meds.length === 0) {
|
||||
alert("Please enter diagnosis and at least one medicine.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Simulate sending to backend
|
||||
console.log("Sending Rx:", { diagnosis, meds });
|
||||
|
||||
// UI Feedback
|
||||
sendRxBtn.innerText = "Sent! ✅";
|
||||
sendRxBtn.style.background = "#10B981";
|
||||
|
||||
setTimeout(() => {
|
||||
modal.style.display = "none";
|
||||
sendRxBtn.innerText = "Send to WhatsApp";
|
||||
sendRxBtn.style.background = "";
|
||||
alert("Prescription has been sent to the patient's WhatsApp.");
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// Voice AI Logic
|
||||
startRecordBtn.onclick = () => {
|
||||
startRecordBtn.classList.add("hidden");
|
||||
recordingStatus.classList.remove("hidden");
|
||||
|
||||
// Simulate recording for 3 seconds
|
||||
setTimeout(() => {
|
||||
recordingStatus.classList.add("hidden");
|
||||
aiInsightsBox.classList.remove("hidden");
|
||||
|
||||
// Mock data filtering simulation
|
||||
const mockTranscript = "Hello Rahul, how is your family? The weather is very hot today. Regarding your cough, it seems like a viral infection. You should avoid cold drinks for 3 days. Take the paracetamol after meals. I'll see you in a week.";
|
||||
|
||||
// Filter out small talk (AI simulation)
|
||||
const insights = "Viral infection detected. Avoid cold drinks for 3 days. Take paracetamol after meals. Follow-up in 1 week.";
|
||||
|
||||
insightText.innerText = insights;
|
||||
|
||||
// Auto-fill diagnosis if empty
|
||||
if (!document.getElementById("rxDiagnosis").value) {
|
||||
document.getElementById("rxDiagnosis").value = "Viral Infection";
|
||||
}
|
||||
}, 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');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,542 @@
|
|||
:root {
|
||||
--primary: #0F172A;
|
||||
--secondary: #38BDF8;
|
||||
--accent: #10B981;
|
||||
--bg: #F8FAFC;
|
||||
--surface: #FFFFFF;
|
||||
--text-main: #1E293B;
|
||||
--text-muted: #64748B;
|
||||
--glass: rgba(255, 255, 255, 0.7);
|
||||
--border: rgba(226, 232, 240, 0.8);
|
||||
--shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg);
|
||||
color: var(--text-main);
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
/* Glass Nav */
|
||||
.glass-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.glass-nav .container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
background: linear-gradient(135deg, var(--secondary), var(--accent));
|
||||
color: white;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
text-decoration: none;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
padding: 180px 0 100px;
|
||||
background: radial-gradient(circle at top right, rgba(56, 189, 248, 0.1), transparent);
|
||||
}
|
||||
|
||||
.hero-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
color: var(--accent);
|
||||
border-radius: 99px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
background: linear-gradient(90deg, var(--secondary), var(--accent));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.hero-btns {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, background 0.3s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #1e293b;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--primary);
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg);
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 1.25rem 2.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stat strong {
|
||||
font-size: 2rem;
|
||||
color: var(--primary);
|
||||
font-family: 'Outfit';
|
||||
}
|
||||
|
||||
.stat span {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.hero-visual .image-wrapper {
|
||||
position: relative;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
border: 8px solid white;
|
||||
}
|
||||
|
||||
.hero-visual img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* WhatsApp Demo */
|
||||
.whatsapp-demo {
|
||||
padding: 100px 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: grid;
|
||||
grid-template-columns: 350px 1fr;
|
||||
gap: 5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.phone-mockup {
|
||||
background: #E5DDD5;
|
||||
border: 12px solid #333;
|
||||
border-radius: 40px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: 20px 20px 60px #d1d9e6, -20px -20px 60px #ffffff;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
background: #075E54;
|
||||
color: white;
|
||||
padding: 3rem 1.5rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #25D366;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.chat-body {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
background-image: url('https://user-images.githubusercontent.com/15075759/28719144-86dc0f70-73b1-11e7-911d-60d70fcded21.png');
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.msg {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 12px;
|
||||
max-width: 85%;
|
||||
font-size: 0.9rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.msg.bot {
|
||||
background: white;
|
||||
align-self: flex-start;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.msg-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.opt {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid var(--accent);
|
||||
color: var(--accent);
|
||||
padding: 0.6rem;
|
||||
border-radius: 99px;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.opt:hover {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.feature-info {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
padding: 2rem;
|
||||
background: var(--bg);
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.info-card:hover {
|
||||
transform: translateX(10px);
|
||||
border-color: var(--secondary);
|
||||
}
|
||||
|
||||
.info-card h3 {
|
||||
margin-bottom: 0.75rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* Daily Updates */
|
||||
.daily-updates-section {
|
||||
padding: 60px 0;
|
||||
background: #F1F5F9;
|
||||
}
|
||||
|
||||
.updates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.update-card {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 20px;
|
||||
border-top: 4px solid var(--secondary);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.update-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.update-card h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.update-card p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Features Grid */
|
||||
.features-grid {
|
||||
padding: 100px 0;
|
||||
}
|
||||
|
||||
.grid-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 3rem 2rem;
|
||||
background: white;
|
||||
border-radius: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
text-align: center;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.feature-item .icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.feature-item h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feature-item p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
@media (max-width: 968px) {
|
||||
.hero-grid {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
.hero h1 { font-size: 3rem; }
|
||||
.hero-btns { justify-content: center; }
|
||||
.hero-stats { justify-content: center; }
|
||||
.chat-container { grid-template-columns: 1fr; }
|
||||
.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;
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio | WhatsApp-First Clinic Management</title>
|
||||
<meta name="description" content="Revolutionizing Indian OPDs with WhatsApp-native queue management, AI triage, and digital patient records.">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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">
|
||||
<script src="tenantLoader.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="glass-nav">
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="#features">Features</a>
|
||||
<a href="#whatsapp">WhatsApp Flow</a>
|
||||
<a href="#pricing">Pricing</a>
|
||||
<a href="register.html" class="btn-primary" style="text-decoration: none;">Get Started</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<div class="container hero-grid">
|
||||
<div class="hero-content">
|
||||
<div class="badge">Built for Indian Clinics</div>
|
||||
<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>
|
||||
<div class="hero-btns">
|
||||
<a href="register.html" class="btn-primary btn-lg" style="text-decoration: none;">Deploy to My Clinic</a>
|
||||
<a href="#whatsapp" class="btn-secondary btn-lg" style="text-decoration: none;">Watch Demo</a>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="stat">
|
||||
<strong>40%</strong>
|
||||
<span>Wait Time Reduction</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<strong>90%</strong>
|
||||
<span>WhatsApp Adoption</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual">
|
||||
<div class="image-wrapper">
|
||||
<img src="Curio_hero_ui_1778636262274.png" alt="Curio Dashboard Mockup">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="whatsapp" class="whatsapp-demo">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>The WhatsApp Experience</h2>
|
||||
<p>No apps to download. Patients book and track tokens from the app they already use.</p>
|
||||
</div>
|
||||
<div class="chat-container">
|
||||
<div class="phone-mockup">
|
||||
<div class="chat-header">
|
||||
<div class="status-dot"></div>
|
||||
<span>Curio Clinic Assistant</span>
|
||||
</div>
|
||||
<div class="chat-body" id="chatBody">
|
||||
<div class="msg bot">Hello! Welcome to Dr. Sharma's Clinic. How can I help you today?</div>
|
||||
<div class="msg-options">
|
||||
<button class="opt">Book Token</button>
|
||||
<button class="opt">Check Queue</button>
|
||||
<button class="opt">Download Report</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-info">
|
||||
<div class="info-card">
|
||||
<h3>Token Booking</h3>
|
||||
<p>Walk-ins scan a QR code at the reception. WhatsApp automatically assigns a token and provides a live ETA.</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>AI Symptom Triage</h3>
|
||||
<p>Ask basic questions to categorize patients before they see the doctor, saving valuable consultation time.</p>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<h3>Live Status Updates</h3>
|
||||
<p>"You are next in line. Please proceed to Room 4." - No more shouting names in the lobby.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="updates" class="daily-updates-section">
|
||||
<div class="container">
|
||||
<div class="updates-grid">
|
||||
<div class="update-card">
|
||||
<div class="update-icon">🔔</div>
|
||||
<h4>Status Alerts</h4>
|
||||
<p>"Dr. Sharma is now seeing Token #WK-3. You are next. Please reach the clinic in 5 minutes."</p>
|
||||
</div>
|
||||
<div class="update-card">
|
||||
<div class="update-icon">🥗</div>
|
||||
<h4>Daily Health Tips</h4>
|
||||
<p>"Stay hydrated! Since you visited for pediatric care, here are 3 summer tips for kids."</p>
|
||||
</div>
|
||||
<div class="update-card">
|
||||
<div class="update-icon">📊</div>
|
||||
<h4>Queue Reports</h4>
|
||||
<p>"Current Clinic Load: Medium. Average wait time for walk-ins is 25 minutes."</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" class="features-grid">
|
||||
<div class="container">
|
||||
<div class="grid-layout">
|
||||
<div class="feature-item">
|
||||
<div class="icon">📊</div>
|
||||
<h3>Smart Queueing</h3>
|
||||
<p>Hybrid system for walk-ins and pre-booked appointments. Auto-reorders based on urgency.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">🤖</div>
|
||||
<h3>AI Report Summaries</h3>
|
||||
<p>Upload blood reports or X-ray findings. AI summarizes them for the doctor in seconds.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">💳</div>
|
||||
<h3>Instant Billing</h3>
|
||||
<p>Send Razorpay links directly on WhatsApp. Integrated GST billing for pharmacy & labs.</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">🏢</div>
|
||||
<h3>Multi-Tenant Branding</h3>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 Curio Technologies. Empowering healthcare in Bharat.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
function handleOptionClick() {
|
||||
const chatBody = document.getElementById('chatBody');
|
||||
const userMsg = this.innerText;
|
||||
|
||||
// Add User Message
|
||||
const userDiv = document.createElement('div');
|
||||
userDiv.className = 'msg user';
|
||||
userDiv.style.background = '#DCF8C6';
|
||||
userDiv.style.alignSelf = 'flex-end';
|
||||
userDiv.style.padding = '0.75rem 1rem';
|
||||
userDiv.style.borderRadius = '12px';
|
||||
userDiv.style.borderTopRightRadius = '0';
|
||||
userDiv.style.fontSize = '0.9rem';
|
||||
userDiv.style.maxWidth = '85%';
|
||||
userDiv.innerText = userMsg;
|
||||
chatBody.appendChild(userDiv);
|
||||
|
||||
// Hide the options that were just clicked
|
||||
this.parentElement.style.display = 'none';
|
||||
|
||||
// Bot Response
|
||||
setTimeout(() => {
|
||||
const botDiv = document.createElement('div');
|
||||
botDiv.className = 'msg bot';
|
||||
botDiv.style.background = 'white';
|
||||
botDiv.style.alignSelf = 'flex-start';
|
||||
botDiv.style.padding = '0.75rem 1rem';
|
||||
botDiv.style.borderRadius = '12px';
|
||||
botDiv.style.borderTopLeftRadius = '0';
|
||||
botDiv.style.fontSize = '0.9rem';
|
||||
botDiv.style.maxWidth = '85%';
|
||||
|
||||
if (userMsg === 'Book Token' || userMsg === 'Book for Today') {
|
||||
botDiv.innerText = "Great! Please select your preferred time slot:";
|
||||
|
||||
const optDiv = document.createElement('div');
|
||||
optDiv.className = 'msg-options';
|
||||
optDiv.style.display = 'flex';
|
||||
optDiv.style.flexDirection = 'column';
|
||||
optDiv.style.gap = '0.5rem';
|
||||
optDiv.style.paddingTop = '1rem';
|
||||
optDiv.innerHTML = `
|
||||
<button class="opt">10:00 AM</button>
|
||||
<button class="optSource-tag online">10:30 AM</button>
|
||||
`;
|
||||
chatBody.appendChild(botDiv);
|
||||
chatBody.appendChild(optDiv);
|
||||
bindOptions();
|
||||
} else if (userMsg === '10:00 AM' || userMsg === '10:30 AM') {
|
||||
botDiv.innerHTML = `✅ Confirmed! Your token is *#ON-3* for the ${userMsg} slot.<br><br>💳 *Payment Required*: Please pay ₹500 to activate your token: <a href="#">razorpay.me/Curio</a><br><br>Also, to help Dr. Sharma prepare, could you briefly describe your symptoms?`;
|
||||
chatBody.appendChild(botDiv);
|
||||
} else if (userMsg === 'Check Queue' || userMsg === 'Check Queue Status') {
|
||||
botDiv.innerText = "Current status: 5 people ahead of you. Estimated wait time: 25 minutes.";
|
||||
chatBody.appendChild(botDiv);
|
||||
} else {
|
||||
botDiv.innerText = "Got it. I've shared these details with Dr. Sharma. Your token is active. See you soon! 🏥";
|
||||
chatBody.appendChild(botDiv);
|
||||
}
|
||||
|
||||
chatBody.scrollTop = chatBody.scrollHeight;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function bindOptions() {
|
||||
document.querySelectorAll('.opt').forEach(button => {
|
||||
button.onclick = null;
|
||||
button.addEventListener('click', handleOptionClick);
|
||||
});
|
||||
}
|
||||
|
||||
// Initial bind
|
||||
bindOptions();
|
||||
|
||||
// Scroll animations placeholder
|
||||
window.addEventListener('scroll', () => {
|
||||
const nav = document.querySelector('.glass-nav');
|
||||
if (window.scrollY > 50) {
|
||||
nav.style.height = '70px';
|
||||
nav.style.background = 'rgba(255, 255, 255, 0.9)';
|
||||
} else {
|
||||
nav.style.height = '80px';
|
||||
nav.style.background = 'rgba(255, 255, 255, 0.7)';
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: apisix.apache.org/v2
|
||||
kind: ApisixRoute
|
||||
metadata:
|
||||
name: curio-route
|
||||
namespace: curio
|
||||
spec:
|
||||
http:
|
||||
- name: curio-rule
|
||||
match:
|
||||
hosts:
|
||||
- curio.applaude.net
|
||||
paths:
|
||||
- /*
|
||||
backends:
|
||||
- serviceName: curio-app
|
||||
servicePort: 80
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: curio-app
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: curio-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: curio-app
|
||||
spec:
|
||||
containers:
|
||||
- name: curio
|
||||
image: 192.168.8.250:5000/curio:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
value: "postgres://postgres:curio_secret@curio-db:5432/curio"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: curio-app
|
||||
spec:
|
||||
selector:
|
||||
app: curio-app
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 3000
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: curio-db
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: curio-db
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: curio-db
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:15-alpine
|
||||
env:
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "curio_secret"
|
||||
- name: POSTGRES_DB
|
||||
value: "curio"
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: curio-db
|
||||
spec:
|
||||
selector:
|
||||
app: curio-db
|
||||
ports:
|
||||
- port: 5432
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
.ai-report-card {
|
||||
background: linear-gradient(135deg, #6366F1, #4F46E5);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ai-report-card h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
background: white;
|
||||
color: #4F46E5;
|
||||
}
|
||||
|
||||
.btn-block:hover {
|
||||
background: #F1F5F9;
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio Lab | Diagnostics & Reports</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
<link rel="stylesheet" href="lab.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="dashboard.html"><span>🏠</span> Overview</a>
|
||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html" class="active"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||
</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>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="header-title">
|
||||
<h1>Lab & Diagnostics</h1>
|
||||
<p>Track test samples and deliver AI-explained reports to patients.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn-primary">Generate Report</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stats-row">
|
||||
<div class="stat-card">
|
||||
<span>Pending Tests</span>
|
||||
<h3>14 Samples</h3>
|
||||
<small class="text-accent">4 Urgent</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Avg. TAT</span>
|
||||
<h3>4.2 Hours</h3>
|
||||
<small class="text-accent">↑ 0.5h improvement</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>WhatsApp Delivery</span>
|
||||
<h3>98%</h3>
|
||||
<small>Auto-sent successfully</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="queue-section">
|
||||
<div class="section-card">
|
||||
<div class="card-header">
|
||||
<h2>Live Test Queue</h2>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Lab ID</th>
|
||||
<th>Patient Name</th>
|
||||
<th>Test Name</th>
|
||||
<th>Urgency</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="active-row">
|
||||
<td>#LAB-501</td>
|
||||
<td><strong>Suresh Kumar</strong></td>
|
||||
<td>CBC + Blood Sugar</td>
|
||||
<td><span class="triage-pill urgent">Urgent</span></td>
|
||||
<td><span class="status-pill in-room">Processing</span></td>
|
||||
<td><button class="btn-small">Add Results</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#LAB-502</td>
|
||||
<td><strong>Anjali Singh</strong></td>
|
||||
<td>Lipid Profile</td>
|
||||
<td><span class="triage-pill routine">Routine</span></td>
|
||||
<td><span class="status-pill waiting">Collecting</span></td>
|
||||
<td><button class="btn-small secondary">Update</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="side-panel">
|
||||
<div class="section-card ai-report-card">
|
||||
<h3>✨ AI Report Explainer</h3>
|
||||
<div class="insight-item">
|
||||
<small>CBC Analysis (#LAB-498)</small>
|
||||
<p>Hemoglobin (9.2) is low. Suggesting iron-rich diet and further Vitamin B12 check.</p>
|
||||
<button class="btn-small btn-block">Review & Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
.auth-body {
|
||||
background: linear-gradient(135deg, #F8FAFC 0%, #E2E8F0 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background: white;
|
||||
padding: 3rem;
|
||||
border-radius: 32px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.auth-logo h1 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card h2 {
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
text-align: left;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border);
|
||||
background: #F8FAFC;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
background: white;
|
||||
box-shadow: 0 0 0 4px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.auth-btn {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-footer a {
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login | Curio HMS</title>
|
||||
<link rel="stylesheet" href="index.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">
|
||||
<script src="tenantLoader.js"></script>
|
||||
</head>
|
||||
<body class="auth-body">
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<div class="auth-logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<h1>Curio</h1>
|
||||
</div>
|
||||
<h2>Welcome Back</h2>
|
||||
<p>Login to manage your clinic OPD and accounts.</p>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="input-group">
|
||||
<label>Clinic ID / Email</label>
|
||||
<input type="text" placeholder="e.g. SHARMA-CLINIC-01" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Password</label>
|
||||
<input type="password" placeholder="••••••••" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary auth-btn">Login to Dashboard</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-footer">New to Curio? <a href="register.html">Register your clinic</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginForm').onsubmit = (e) => {
|
||||
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';
|
||||
} else {
|
||||
alert('Invalid credentials. Hint: deepkoluguri@gmail.com / password123');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "curaflow-hms",
|
||||
"version": "1.0.0",
|
||||
"description": "WhatsApp-First Hospital Management System",
|
||||
"main": "backend/server.js",
|
||||
"scripts": {
|
||||
"start": "node backend/server.js",
|
||||
"dev": "nodemon backend/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
.inventory-card h3 {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.inventory-item {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.inventory-item span {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.inventory-item small {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.inventory-item .bar {
|
||||
height: 8px;
|
||||
background: #F1F5F9;
|
||||
border-radius: 99px;
|
||||
margin-top: 0.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inventory-item .fill {
|
||||
height: 100%;
|
||||
background: #10B981;
|
||||
}
|
||||
|
||||
.text-urgent {
|
||||
color: #EF4444;
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio Pharmacy | Inventory & Billing</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
<link rel="stylesheet" href="pharmacy.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="dashboard.html"><span>🏠</span> Overview</a>
|
||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html" class="active"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||
</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>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="header-title">
|
||||
<h1>Pharmacy Management</h1>
|
||||
<p>Manage prescriptions and inventory with real-time stock alerts.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<span class="badge counter-badge">Dispensing Counter: 2</span>
|
||||
<button class="btn-primary">+ Add New Stock</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stats-row">
|
||||
<div class="stat-card">
|
||||
<span>Pending Rx</span>
|
||||
<h3>8 Orders</h3>
|
||||
<small class="text-accent">↑ 2 from Dr. Sharma</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Low Stock</span>
|
||||
<h3 class="text-urgent">4 Medicines</h3>
|
||||
<small>Alert: Cough Syrup < 5 units</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Revenue Today</span>
|
||||
<h3>₹12,450</h3>
|
||||
<small class="text-accent">↑ 15% vs Yesterday</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="queue-section">
|
||||
<div class="section-card">
|
||||
<div class="card-header">
|
||||
<h2>Pending Prescriptions</h2>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order ID</th>
|
||||
<th>Patient Name</th>
|
||||
<th>Medicines</th>
|
||||
<th>Doctor</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="active-row">
|
||||
<td>#ORD-901</td>
|
||||
<td><strong>Rahul Verma</strong></td>
|
||||
<td>Paracetamol, Cough Syrup</td>
|
||||
<td>Dr. Sharma</td>
|
||||
<td><span class="status-pill waiting">Prescribed</span></td>
|
||||
<td><button class="btn-small">Dispense</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#ORD-902</td>
|
||||
<td><strong>Anjali Singh</strong></td>
|
||||
<td>Amoxicillin</td>
|
||||
<td>Dr. Sharma</td>
|
||||
<td><span class="status-pill in-room">Processing</span></td>
|
||||
<td><button class="btn-small secondary">Ready</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="side-panel">
|
||||
<div class="section-card inventory-card">
|
||||
<h3>📦 Fast Moving Stock</h3>
|
||||
<div class="inventory-item">
|
||||
<span>Paracetamol 500mg</span>
|
||||
<small>120 units</small>
|
||||
<div class="bar"><div class="fill" style="width: 80%;"></div></div>
|
||||
</div>
|
||||
<div class="inventory-item">
|
||||
<span>Amoxicillin</span>
|
||||
<small>45 units</small>
|
||||
<div class="bar"><div class="fill" style="width: 40%; background: #F59E0B;"></div></div>
|
||||
</div>
|
||||
<div class="inventory-item">
|
||||
<span>Cough Syrup</span>
|
||||
<small>4 units</small>
|
||||
<div class="bar"><div class="fill" style="width: 5%; background: #EF4444;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Clinic Registration | Curio HMS</title>
|
||||
<link rel="stylesheet" href="index.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">
|
||||
</head>
|
||||
<body class="auth-body">
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<div class="auth-logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<h1>Curio</h1>
|
||||
</div>
|
||||
<h2>Register Your Clinic</h2>
|
||||
<p>Join Bharat's first WhatsApp-native HMS.</p>
|
||||
|
||||
<form id="registerForm">
|
||||
<div class="input-row">
|
||||
<div class="input-group">
|
||||
<label>Doctor/Owner Name</label>
|
||||
<input type="text" placeholder="Dr. Satish Sharma" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Clinic Name</label>
|
||||
<input type="text" placeholder="Sharma Heart Center" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>WhatsApp Number (Business)</label>
|
||||
<input type="tel" placeholder="+91 XXXXX XXXXX" required>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Password</label>
|
||||
<input type="password" placeholder="••••••••" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary auth-btn">Create Clinic Account</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-footer">Already have an account? <a href="login.html">Login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('registerForm').onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
alert('Clinic registered! Please login with your credentials.');
|
||||
window.location.href = 'login.html';
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-grid .section-card h2 {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.settings-grid .input-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.settings-grid label {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-grid input, .settings-grid select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio Settings | Hospital Configuration</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
<link rel="stylesheet" href="settings.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="dashboard.html"><span>🏠</span> Overview</a>
|
||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html" class="active"><span>⚙️</span> Settings</a>
|
||||
</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>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="header-title">
|
||||
<h1>Hospital Settings</h1>
|
||||
<p>Configure pricing, slot limits, and clinic profile.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn-primary" id="saveSettings">Save Changes</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="settings-grid">
|
||||
<div class="section-card">
|
||||
<h2>Pricing & Fees</h2>
|
||||
<div class="input-group">
|
||||
<label>Consultation Fee (₹)</label>
|
||||
<input type="number" value="500" id="config-fee">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Default Currency</label>
|
||||
<select>
|
||||
<option>INR (₹)</option>
|
||||
<option>USD ($)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<h2>Slot Management</h2>
|
||||
<div class="input-group">
|
||||
<label>Slot Duration (Minutes)</label>
|
||||
<input type="number" value="30" id="config-duration">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Max Online Bookings per Slot</label>
|
||||
<input type="number" value="3" id="config-online">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Max Walk-in Tokens per Slot</label>
|
||||
<input type="number" value="5" id="config-walkin">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<h2>Pharmacy Config</h2>
|
||||
<p>Manage medicine pricing and GST rates.</p>
|
||||
<div class="input-group">
|
||||
<label>Default GST %</label>
|
||||
<input type="number" value="12">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Markup Percentage (%)</label>
|
||||
<input type="number" value="20">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
document.getElementById('saveSettings').onclick = () => {
|
||||
alert('Settings saved successfully! Changes will reflect in new tokens.');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
.payroll-card {
|
||||
background: linear-gradient(135deg, #10B981, #059669);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.payroll-card h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.payroll-card p {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.incentive-item {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 1rem;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.incentive-item span {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.incentive-item h4 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Curio Staff | Personnel & Payroll</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
<link rel="stylesheet" href="dashboard.css">
|
||||
<link rel="stylesheet" href="staff.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body class="dashboard-body">
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<span class="logo-icon">C</span>
|
||||
<span class="logo-text">Curio</span>
|
||||
</div>
|
||||
<nav class="side-nav">
|
||||
<a href="dashboard.html"><span>🏠</span> Overview</a>
|
||||
<a href="dashboard.html"><span>👥</span> Queue</a>
|
||||
<a href="dashboard.html"><span>📂</span> Patients</a>
|
||||
<a href="pharmacy.html"><span>💊</span> Pharmacy</a>
|
||||
<a href="lab.html"><span>🔬</span> Lab</a>
|
||||
<a href="billing.html"><span>💳</span> Billing & Accounts</a>
|
||||
<a href="staff.html" class="active"><span>👔</span> Staff & Payroll</a>
|
||||
<a href="settings.html"><span>⚙️</span> Settings</a>
|
||||
</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>
|
||||
|
||||
<main class="dashboard-main">
|
||||
<header class="dash-header">
|
||||
<div class="header-title">
|
||||
<h1>Staff Management</h1>
|
||||
<p>Track attendance, roles, and monthly payroll incentives.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn-primary">+ Add New Staff</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stats-row">
|
||||
<div class="stat-card">
|
||||
<span>Total Staff</span>
|
||||
<h3>18 Members</h3>
|
||||
<small>4 Departments</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>On Duty Today</span>
|
||||
<h3>15 Present</h3>
|
||||
<small class="text-accent">3 on Leave</small>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span>Monthly Payroll</span>
|
||||
<h3>₹4.85 Lakhs</h3>
|
||||
<small>Incl. Incentives</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="queue-section">
|
||||
<div class="section-card">
|
||||
<div class="card-header">
|
||||
<h2>Staff Directory</h2>
|
||||
</div>
|
||||
<table class="queue-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Staff Name</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Monthly Pay</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>#S001</td>
|
||||
<td><strong>Dr. Sharma</strong></td>
|
||||
<td>Doctor</td>
|
||||
<td><span class="status-pill in-room">Present</span></td>
|
||||
<td>₹1,24,500</td>
|
||||
<td><button class="btn-small">Payroll</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#S002</td>
|
||||
<td><strong>Anjali Singh</strong></td>
|
||||
<td>Pharmacist</td>
|
||||
<td><span class="status-pill in-room">Present</span></td>
|
||||
<td>₹36,200</td>
|
||||
<td><button class="btn-small">Payroll</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>#S003</td>
|
||||
<td><strong>Vikram Goel</strong></td>
|
||||
<td>Lab Tech</td>
|
||||
<td><span class="status-pill waiting">On Leave</span></td>
|
||||
<td>₹28,800</td>
|
||||
<td><button class="btn-small secondary">History</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="side-panel">
|
||||
<div class="section-card payroll-card">
|
||||
<h3>💡 Incentive Logic</h3>
|
||||
<p>Current rule: <strong>5%</strong> of Pharmacy sales shared with staff pool.</p>
|
||||
<div class="incentive-item">
|
||||
<span>Staff Pool Today</span>
|
||||
<h4>₹2,450</h4>
|
||||
<small>Calculated on ₹49k revenue</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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('Curio_tenant') || 'default';
|
||||
|
||||
// Save to localStorage for persistence across pages
|
||||
localStorage.setItem('Curio_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} | Curio`;
|
||||
|
||||
// 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