diff --git a/backend/db.js b/backend/db.js index c73bd1c..c2d2503 100644 --- a/backend/db.js +++ b/backend/db.js @@ -97,6 +97,32 @@ CREATE TABLE IF NOT EXISTS prescriptions ( created_at TIMESTAMPTZ DEFAULT now() ); +CREATE TABLE IF NOT EXISTS patients ( + id SERIAL PRIMARY KEY, + tenant_id TEXT REFERENCES tenants(id) ON DELETE CASCADE, + display_id TEXT NOT NULL, + name TEXT NOT NULL, + whatsapp_number TEXT NOT NULL, + age INTEGER, + gender TEXT, + created_at TIMESTAMPTZ DEFAULT now(), + UNIQUE(tenant_id, whatsapp_number) +); + +CREATE TABLE IF NOT EXISTS visits ( + id SERIAL PRIMARY KEY, + tenant_id TEXT REFERENCES tenants(id) ON DELETE CASCADE, + patient_id INTEGER REFERENCES patients(id) ON DELETE CASCADE, + token_number TEXT NOT NULL, + status TEXT DEFAULT 'waiting' CHECK (status IN ('waiting', 'in-room', 'completed', 'cancelled')), + type TEXT DEFAULT 'walk-in' CHECK (type IN ('walk-in', 'online')), + triage_category TEXT DEFAULT 'Routine', + payment_status TEXT DEFAULT 'unpaid' CHECK (payment_status IN ('unpaid', 'paid')), + fee INTEGER DEFAULT 0, + notes TEXT, + created_at TIMESTAMPTZ DEFAULT now() +); + CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_pending_status ON pending_registrations(status); @@ -139,6 +165,7 @@ async function seedDemoUsers() { const hash = await bcrypt.hash(defaultPassword, 10); const demoUsers = [ { email: 'doctor@curio.app', name: 'Dr. Sharma', role: 'DOCTOR', tenant_id: 'default' }, + { email: 'reception@curio.app', name: 'Front Desk', role: 'RECEPTIONIST', tenant_id: 'default' }, { email: 'pharmacy@curio.app', name: 'Pharmacist', role: 'PHARMACIST', tenant_id: 'default' }, ]; diff --git a/backend/server.js b/backend/server.js index e85087c..09fa40a 100644 --- a/backend/server.js +++ b/backend/server.js @@ -499,6 +499,91 @@ app.patch('/api/clinic/agents/:type', requireAuth('OWNER'), async (req, res) => } }); +// ── CLINIC OPERATIONS (QUEUE & PATIENTS) ────────────────────────────────────── + +/** + * GET /api/clinic/queue + * Fetch the live queue for the tenant + */ +app.get('/api/clinic/queue', requireAuth(), async (req, res) => { + try { + const tenantId = req.user.tenantId; + const result = await query( + `SELECT v.*, p.name as patient_name, p.whatsapp_number, p.age, p.gender, p.display_id + FROM visits v + JOIN patients p ON v.patient_id = p.id + WHERE v.tenant_id = $1 AND v.status IN ('waiting', 'in-room') + ORDER BY v.created_at ASC`, + [tenantId] + ); + res.json(result.rows); + } catch (err) { + console.error('[Clinic Queue]', err); + res.status(500).json({ error: 'Failed to fetch queue' }); + } +}); + +/** + * POST /api/clinic/walk-in + * Register a new walk-in patient and add to queue + */ +app.post('/api/clinic/walk-in', requireAuth(), async (req, res) => { + const { name, whatsappNumber, age, gender, fee } = req.body; + const tenantId = req.user.tenantId; + + if (!name || !whatsappNumber) return res.status(400).json({ error: 'Name and WhatsApp number are required' }); + + try { + await query('BEGIN'); + + // Find or create patient + let patientResult = await query( + 'SELECT id FROM patients WHERE tenant_id = $1 AND whatsapp_number = $2', + [tenantId, whatsappNumber] + ); + + let patientId; + if (patientResult.rows.length > 0) { + patientId = patientResult.rows[0].id; + } else { + // Generate display ID + const countRes = await query('SELECT count(*) FROM patients WHERE tenant_id = $1', [tenantId]); + const nextNum = parseInt(countRes.rows[0].count) + 1001; + const displayId = `CF-${nextNum}`; + + const insertPatient = await query( + `INSERT INTO patients (tenant_id, display_id, name, whatsapp_number, age, gender) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`, + [tenantId, displayId, name, whatsappNumber, age || null, gender || null] + ); + patientId = insertPatient.rows[0].id; + } + + // Generate Token Number (simple count-based for the day) + const todayStr = new Date().toISOString().split('T')[0]; + const tokenRes = await query( + `SELECT count(*) FROM visits WHERE tenant_id = $1 AND DATE(created_at) = $2`, + [tenantId, todayStr] + ); + const tokenNum = parseInt(tokenRes.rows[0].count) + 1; + const tokenStr = `#${tokenNum.toString().padStart(3, '0')}`; + + // Insert Visit + const insertVisit = await query( + `INSERT INTO visits (tenant_id, patient_id, token_number, type, status, fee) + VALUES ($1, $2, $3, 'walk-in', 'waiting', $4) RETURNING *`, + [tenantId, patientId, tokenStr, fee || 0] + ); + + await query('COMMIT'); + res.json({ success: true, visit: insertVisit.rows[0], token: tokenStr }); + } catch (err) { + await query('ROLLBACK'); + console.error('[Clinic Walk-in]', err); + res.status(500).json({ error: 'Failed to register walk-in' }); + } +}); + // ── CLINIC OWNER ROUTES ─────────────────────────────────────────────────────── /** diff --git a/dashboard.html b/dashboard.html index 399f2e0..af6c6b7 100644 --- a/dashboard.html +++ b/dashboard.html @@ -57,7 +57,7 @@ Reception Counter: 1 - + @@ -125,21 +125,8 @@