Spaces:
Sleeping
Sleeping
Add connection deletion actions
Browse files- app.py +36 -12
- database.py +17 -8
- templates/hospital.html +25 -1
- templates/patient.html +26 -2
app.py
CHANGED
|
@@ -560,22 +560,46 @@ def get_hospital_connections():
|
|
| 560 |
return jsonify({"connections": db.get_hospital_connections(session["hospital_id"])})
|
| 561 |
|
| 562 |
|
| 563 |
-
@app.route("/api/hospital/connections/<cid>/status", methods=["PUT"])
|
| 564 |
-
def update_hospital_connection_status(cid):
|
| 565 |
-
err = _hospital_required()
|
| 566 |
-
if err:
|
| 567 |
-
return err
|
| 568 |
data = request.get_json(force=True) or {}
|
| 569 |
status = data.get("status", "")
|
| 570 |
if status not in ("pending", "accepted", "rejected", "completed"):
|
| 571 |
return jsonify({"error": "Invalid status"}), 400
|
| 572 |
-
db.update_connection_status(cid, status)
|
| 573 |
-
return jsonify({"success": True})
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
|
| 580 |
@app.route("/api/patient/connections/<cid>/messages", methods=["GET"])
|
| 581 |
def get_patient_connection_messages(cid):
|
|
|
|
| 560 |
return jsonify({"connections": db.get_hospital_connections(session["hospital_id"])})
|
| 561 |
|
| 562 |
|
| 563 |
+
@app.route("/api/hospital/connections/<cid>/status", methods=["PUT"])
|
| 564 |
+
def update_hospital_connection_status(cid):
|
| 565 |
+
err = _hospital_required()
|
| 566 |
+
if err:
|
| 567 |
+
return err
|
| 568 |
data = request.get_json(force=True) or {}
|
| 569 |
status = data.get("status", "")
|
| 570 |
if status not in ("pending", "accepted", "rejected", "completed"):
|
| 571 |
return jsonify({"error": "Invalid status"}), 400
|
| 572 |
+
db.update_connection_status(cid, status)
|
| 573 |
+
return jsonify({"success": True})
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
@app.route("/api/patient/connections/<cid>", methods=["DELETE"])
|
| 577 |
+
def delete_patient_connection(cid):
|
| 578 |
+
err = _patient_required()
|
| 579 |
+
if err:
|
| 580 |
+
return err
|
| 581 |
+
conn = db.get_connection(cid)
|
| 582 |
+
if not conn or conn["patient_id"] != session["patient_id"]:
|
| 583 |
+
return jsonify({"error": "Not found"}), 404
|
| 584 |
+
db.delete_connection(cid)
|
| 585 |
+
return jsonify({"success": True})
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
@app.route("/api/hospital/connections/<cid>", methods=["DELETE"])
|
| 589 |
+
def delete_hospital_connection(cid):
|
| 590 |
+
err = _hospital_required()
|
| 591 |
+
if err:
|
| 592 |
+
return err
|
| 593 |
+
conn = db.get_connection(cid)
|
| 594 |
+
if not conn or conn["hospital_id"] != session["hospital_id"]:
|
| 595 |
+
return jsonify({"error": "Not found"}), 404
|
| 596 |
+
db.delete_connection(cid)
|
| 597 |
+
return jsonify({"success": True})
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
# ---------------------------------------------------------------------------
|
| 601 |
+
# Connection Messages
|
| 602 |
+
# ---------------------------------------------------------------------------
|
| 603 |
|
| 604 |
@app.route("/api/patient/connections/<cid>/messages", methods=["GET"])
|
| 605 |
def get_patient_connection_messages(cid):
|
database.py
CHANGED
|
@@ -559,14 +559,23 @@ def get_hospital_connections(hospital_id: str) -> list:
|
|
| 559 |
return result
|
| 560 |
|
| 561 |
|
| 562 |
-
def update_connection_status(cid: str, status: str):
|
| 563 |
-
with _conn() as c:
|
| 564 |
-
c.execute("UPDATE connections SET status=? WHERE id=?", (status, cid))
|
| 565 |
-
c.commit()
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
def
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
"""
|
| 571 |
Returns patients who are open_to_trials=1, optionally filtered by condition.
|
| 572 |
When include_connected=False (default), excludes patients already connected to this hospital.
|
|
|
|
| 559 |
return result
|
| 560 |
|
| 561 |
|
| 562 |
+
def update_connection_status(cid: str, status: str):
|
| 563 |
+
with _conn() as c:
|
| 564 |
+
c.execute("UPDATE connections SET status=? WHERE id=?", (status, cid))
|
| 565 |
+
c.commit()
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def delete_connection(cid: str) -> bool:
|
| 569 |
+
"""Delete a connection and its message history."""
|
| 570 |
+
with _conn() as c:
|
| 571 |
+
c.execute("DELETE FROM connection_messages WHERE connection_id=?", (cid,))
|
| 572 |
+
cur = c.execute("DELETE FROM connections WHERE id=?", (cid,))
|
| 573 |
+
c.commit()
|
| 574 |
+
return cur.rowcount > 0
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
def get_open_patients_for_hospital(hospital_id: str, condition_filter: str = "",
|
| 578 |
+
include_connected: bool = False) -> list:
|
| 579 |
"""
|
| 580 |
Returns patients who are open_to_trials=1, optionally filtered by condition.
|
| 581 |
When include_connected=False (default), excludes patients already connected to this hospital.
|
templates/hospital.html
CHANGED
|
@@ -163,7 +163,7 @@ body{background:#0d1117;}
|
|
| 163 |
<table class="table table-dark table-hover table-sm align-middle">
|
| 164 |
<thead><tr>
|
| 165 |
<th>Patient</th><th>Age/Gender</th><th>Conditions</th>
|
| 166 |
-
<th>Trial</th><th>Initiated</th><th>Status</th><th>Message</th><th>Action</th><th>
|
| 167 |
</tr></thead>
|
| 168 |
<tbody id="cTbody"></tbody>
|
| 169 |
</table>
|
|
@@ -264,6 +264,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 264 |
if (!btn) return;
|
| 265 |
if (btn.dataset.st) updateStatus(btn.dataset.cid, btn.dataset.st);
|
| 266 |
else if ('chat' in btn.dataset) openMsgModal(btn.dataset.cid, btn.dataset.chat);
|
|
|
|
| 267 |
});
|
| 268 |
|
| 269 |
loadPatients();
|
|
@@ -451,6 +452,15 @@ async function loadConnections() {
|
|
| 451 |
`<button class="btn btn-sm btn-outline-info py-0 px-2" style="font-size:.72rem"
|
| 452 |
data-cid="${escA(c.id)}" data-chat="${escA(pname)}">π¬ Chat</button>`,
|
| 453 |
].map(v=>`<td>${v}</td>`).join('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
tbody.appendChild(tr);
|
| 455 |
});
|
| 456 |
} catch(e) {}
|
|
@@ -477,6 +487,20 @@ async function updateStatus(cid, status) {
|
|
| 477 |
}
|
| 478 |
|
| 479 |
// ββ MY TRIALS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
async function loadTrials() {
|
| 481 |
show('tLoading'); hide('tEmpty'); document.getElementById('tGrid').innerHTML = '';
|
| 482 |
try {
|
|
|
|
| 163 |
<table class="table table-dark table-hover table-sm align-middle">
|
| 164 |
<thead><tr>
|
| 165 |
<th>Patient</th><th>Age/Gender</th><th>Conditions</th>
|
| 166 |
+
<th>Trial</th><th>Initiated</th><th>Status</th><th>Message</th><th>Status Action</th><th>Actions</th>
|
| 167 |
</tr></thead>
|
| 168 |
<tbody id="cTbody"></tbody>
|
| 169 |
</table>
|
|
|
|
| 264 |
if (!btn) return;
|
| 265 |
if (btn.dataset.st) updateStatus(btn.dataset.cid, btn.dataset.st);
|
| 266 |
else if ('chat' in btn.dataset) openMsgModal(btn.dataset.cid, btn.dataset.chat);
|
| 267 |
+
else if ('del' in btn.dataset) deleteConnection(btn.dataset.cid, btn.dataset.del);
|
| 268 |
});
|
| 269 |
|
| 270 |
loadPatients();
|
|
|
|
| 452 |
`<button class="btn btn-sm btn-outline-info py-0 px-2" style="font-size:.72rem"
|
| 453 |
data-cid="${escA(c.id)}" data-chat="${escA(pname)}">π¬ Chat</button>`,
|
| 454 |
].map(v=>`<td>${v}</td>`).join('');
|
| 455 |
+
const actionsCell = tr.lastElementChild;
|
| 456 |
+
const delBtn = document.createElement('button');
|
| 457 |
+
delBtn.type = 'button';
|
| 458 |
+
delBtn.className = 'btn btn-sm btn-outline-danger py-0 px-2 ms-1';
|
| 459 |
+
delBtn.style.fontSize = '.72rem';
|
| 460 |
+
delBtn.textContent = 'Delete';
|
| 461 |
+
delBtn.dataset.cid = c.id;
|
| 462 |
+
delBtn.dataset.del = pname;
|
| 463 |
+
actionsCell.appendChild(delBtn);
|
| 464 |
tbody.appendChild(tr);
|
| 465 |
});
|
| 466 |
} catch(e) {}
|
|
|
|
| 487 |
}
|
| 488 |
|
| 489 |
// ββ MY TRIALS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 490 |
+
async function deleteConnection(cid, patientName) {
|
| 491 |
+
const ok = confirm(`Remove connection with ${patientName || 'this patient'}? This will also remove the chat thread.`);
|
| 492 |
+
if (!ok) return;
|
| 493 |
+
try {
|
| 494 |
+
const r = await fetch('/api/hospital/connections/'+encodeURIComponent(cid), {method:'DELETE'});
|
| 495 |
+
const d = await r.json();
|
| 496 |
+
if (!r.ok) { alert(d.error || 'Delete failed'); return; }
|
| 497 |
+
await loadConnections();
|
| 498 |
+
loadInbox();
|
| 499 |
+
} catch(e) {
|
| 500 |
+
alert('Network error');
|
| 501 |
+
}
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
async function loadTrials() {
|
| 505 |
show('tLoading'); hide('tEmpty'); document.getElementById('tGrid').innerHTML = '';
|
| 506 |
try {
|
templates/patient.html
CHANGED
|
@@ -192,7 +192,7 @@ body{background:#0d1117;}
|
|
| 192 |
<table class="table table-dark table-hover table-sm align-middle">
|
| 193 |
<thead><tr>
|
| 194 |
<th>Hospital</th><th>Trial</th><th>Initiated By</th>
|
| 195 |
-
<th>Status</th><th>Message</th><th>Date</th><th>
|
| 196 |
</tr></thead>
|
| 197 |
<tbody id="connsTbody"></tbody>
|
| 198 |
</table>
|
|
@@ -678,12 +678,36 @@ async function loadConnections() {
|
|
| 678 |
`<button class="btn btn-sm btn-outline-info py-0 px-2" style="font-size:.72rem"
|
| 679 |
onclick="openMsgModal('${escA(c.id)}','${escA(hname)}')">π¬ Chat</button>`,
|
| 680 |
].map(v=>`<td>${v}</td>`).join('');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
tbody.appendChild(tr);
|
| 682 |
});
|
| 683 |
} catch(e) {}
|
| 684 |
}
|
| 685 |
|
| 686 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
function renderDocList() {
|
| 688 |
const wrap = document.getElementById('docList');
|
| 689 |
wrap.innerHTML = '';
|
|
|
|
| 192 |
<table class="table table-dark table-hover table-sm align-middle">
|
| 193 |
<thead><tr>
|
| 194 |
<th>Hospital</th><th>Trial</th><th>Initiated By</th>
|
| 195 |
+
<th>Status</th><th>Message</th><th>Date</th><th>Actions</th>
|
| 196 |
</tr></thead>
|
| 197 |
<tbody id="connsTbody"></tbody>
|
| 198 |
</table>
|
|
|
|
| 678 |
`<button class="btn btn-sm btn-outline-info py-0 px-2" style="font-size:.72rem"
|
| 679 |
onclick="openMsgModal('${escA(c.id)}','${escA(hname)}')">π¬ Chat</button>`,
|
| 680 |
].map(v=>`<td>${v}</td>`).join('');
|
| 681 |
+
const actionsCell = tr.lastElementChild;
|
| 682 |
+
const delBtn = document.createElement('button');
|
| 683 |
+
delBtn.type = 'button';
|
| 684 |
+
delBtn.className = 'btn btn-sm btn-outline-danger py-0 px-2 ms-1';
|
| 685 |
+
delBtn.style.fontSize = '.72rem';
|
| 686 |
+
delBtn.textContent = 'Delete';
|
| 687 |
+
delBtn.addEventListener('click', () => deleteConnection(c.id, hname));
|
| 688 |
+
actionsCell.appendChild(delBtn);
|
| 689 |
tbody.appendChild(tr);
|
| 690 |
});
|
| 691 |
} catch(e) {}
|
| 692 |
}
|
| 693 |
|
| 694 |
+
// Connection removal
|
| 695 |
+
// Remove the selected hospital connection and associated chat thread.
|
| 696 |
+
async function deleteConnection(cid, hospitalName) {
|
| 697 |
+
const ok = confirm(`Remove connection with ${hospitalName || 'this hospital'}? This will also remove the chat thread.`);
|
| 698 |
+
if (!ok) return;
|
| 699 |
+
try {
|
| 700 |
+
const r = await fetch('/api/patient/connections/'+encodeURIComponent(cid), {method:'DELETE'});
|
| 701 |
+
const d = await r.json();
|
| 702 |
+
if (!r.ok) { alert(d.error || 'Delete failed'); return; }
|
| 703 |
+
await loadConnections();
|
| 704 |
+
loadInbox();
|
| 705 |
+
} catch(e) {
|
| 706 |
+
alert('Network error');
|
| 707 |
+
}
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
// Documents
|
| 711 |
function renderDocList() {
|
| 712 |
const wrap = document.getElementById('docList');
|
| 713 |
wrap.innerHTML = '';
|