Files
step3/scripts/ui/ccs_player.gd
T
2026-07-15 10:17:53 -05:00

112 lines
7.4 KiB
GDScript

extends VBoxContainer
const Style := preload("res://scripts/ui/ui_style.gd")
var main: Control
var heading: Label
var clock: Label
var summary: RichTextLabel
var chart: RichTextLabel
var orders: VBoxContainer
var search: LineEdit
var search_results: VBoxContainer
var actions: VBoxContainer
var status: Label
var timer: Timer
var end_button: Button
var time_row: HBoxContainer
func _ready() -> void:
set_anchors_preset(Control.PRESET_FULL_RECT); add_theme_constant_override("separation", 8)
var top := HBoxContainer.new(); add_child(top)
heading = Label.new(); heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL; Style.set_label(heading, 25, Style.TEXT); top.add_child(heading)
clock = Label.new(); Style.set_label(clock, 18, Style.AMBER); top.add_child(clock)
var columns := HSplitContainer.new(); columns.size_flags_vertical = Control.SIZE_EXPAND_FILL; columns.split_offset = 850; add_child(columns)
var chart_panel := VBoxContainer.new(); chart_panel.custom_minimum_size.x = 500; columns.add_child(chart_panel)
summary = RichTextLabel.new(); summary.bbcode_enabled = true; summary.custom_minimum_size.y = 160; chart_panel.add_child(summary)
chart = RichTextLabel.new(); chart.bbcode_enabled = true; chart.size_flags_vertical = Control.SIZE_EXPAND_FILL; chart_panel.add_child(chart)
var clerk_scroll := ScrollContainer.new(); clerk_scroll.custom_minimum_size.x = 430; columns.add_child(clerk_scroll)
var clerk := VBoxContainer.new(); clerk.size_flags_horizontal = Control.SIZE_EXPAND_FILL; clerk.add_theme_constant_override("separation", 8); clerk_scroll.add_child(clerk)
var order_title := Label.new(); order_title.text = "Order Clerk"; Style.set_label(order_title, 21, Style.CYAN); clerk.add_child(order_title)
search = LineEdit.new(); search.placeholder_text = "Search tests, treatments, and procedures"; search.text_submitted.connect(func(_value): CCSService.search_orders(search.text)); clerk.add_child(search)
var find := Button.new(); find.text = "Search"; find.pressed.connect(func(): CCSService.search_orders(search.text)); clerk.add_child(find)
search_results = VBoxContainer.new(); clerk.add_child(search_results)
var active_title := Label.new(); active_title.text = "Active orders"; Style.set_label(active_title, 18, Style.TEXT); clerk.add_child(active_title)
orders = VBoxContainer.new(); clerk.add_child(orders)
var exam_title := Label.new(); exam_title.text = "Physical examination"; Style.set_label(exam_title, 18, Style.TEXT); clerk.add_child(exam_title)
actions = VBoxContainer.new(); clerk.add_child(actions)
time_row = HBoxContainer.new(); clerk.add_child(time_row)
for minutes in [1, 10, 60, 1440]:
var button := Button.new(); button.text = "+%s" % ("1 day" if minutes == 1440 else "%d min" % minutes); button.pressed.connect(CCSService.send_command.bind("advance", "", minutes)); time_row.add_child(button)
var reassess := Button.new(); reassess.text = "Interval history"; reassess.pressed.connect(CCSService.send_command.bind("interval_history", "", 0)); clerk.add_child(reassess)
end_button = Button.new(); end_button.text = "End case"; Style.style_chip(end_button, Style.AMBER); end_button.pressed.connect(_end_case); clerk.add_child(end_button)
var leave := Button.new(); leave.text = "Leave case"; Style.style_chip(leave, Style.RED); leave.pressed.connect(_leave); clerk.add_child(leave)
status = Label.new(); Style.set_label(status, 14, Style.RED); add_child(status)
timer = Timer.new(); timer.wait_time = 1; timer.timeout.connect(_tick); add_child(timer)
CCSService.attempt_changed.connect(_render)
CCSService.orders_changed.connect(_render_search)
CCSService.request_failed.connect(func(message: String): status.text = message)
func on_show(payload: Variant = null) -> void:
status.text = ""
if typeof(payload) == TYPE_STRING and not str(payload).is_empty(): CCSService.load_attempt(str(payload))
_render(); timer.start()
func on_hide() -> void: timer.stop()
func _render() -> void:
var attempt: Dictionary = CCSService.attempt
if attempt.is_empty(): return
if str(attempt.get("state", "")) == "completed": main.show_screen("ccs_review", attempt); return
heading.text = "%s | %s" % [attempt.get("caseTitle", "CCS case"), str(attempt.get("location", "")).capitalize()]
summary.text = "[b]%s[/b]\n%s\n\n[b]Vitals[/b] %s" % [attempt.get("patientSummary", ""), attempt.get("reasonForEncounter", ""), _vitals_text(attempt.get("vitals", {}))]
chart.text = "[b]History[/b]\n%s\n\n[b]Appearance[/b]\n%s\n\n[b]Chart[/b]\n%s" % [attempt.get("history", ""), attempt.get("appearance", ""), _chart_text(attempt.get("chart", []))]
_clear(orders)
for code in attempt.get("activeOrderCodes", []):
var row := HBoxContainer.new(); orders.add_child(row)
var label := Label.new(); label.text = str(code); label.size_flags_horizontal = Control.SIZE_EXPAND_FILL; row.add_child(label)
var cancel := Button.new(); cancel.text = "Cancel"; cancel.pressed.connect(CCSService.send_command.bind("cancel_order", str(code), 0)); row.add_child(cancel)
_clear(actions)
for exam in attempt.get("availableExams", []):
var button := Button.new(); button.text = str(exam.get("name", exam.get("code", "Exam"))); button.pressed.connect(CCSService.send_command.bind("exam", str(exam.get("code", "")), 0)); actions.add_child(button)
for location in attempt.get("allowedLocations", []):
var button := Button.new(); button.text = "Move to %s" % str(location).capitalize(); button.pressed.connect(CCSService.send_command.bind("location", str(location), 0)); actions.add_child(button)
_tick()
end_button.text = "Submit final orders" if str(attempt.get("state", "")) == "final_orders" else "End case"
time_row.visible = str(attempt.get("state", "")) == "active"
if str(attempt.get("state", "")) == "final_orders": status.text = "Case-end instructions: finalize or cancel orders, then submit."
func _render_search() -> void:
_clear(search_results)
for order in CCSService.orders:
var button := Button.new(); button.text = "%s (%s)" % [order.get("canonicalName", ""), order.get("category", "")]; button.alignment = HORIZONTAL_ALIGNMENT_LEFT
button.pressed.connect(_place_order.bind(str(order.get("code", "")))); search_results.add_child(button)
func _place_order(code: String) -> void:
CCSService.send_command("order", code); search.text = ""; CCSService.orders = []; _render_search()
func _tick() -> void:
var deadline := CCSService.deadline_unix()
if deadline <= 0: clock.text = "Practice | Case time %d min" % int(CCSService.attempt.get("simulatedMinute", 0)); return
var remaining := maxi(0, int(ceil(deadline - Time.get_unix_time_from_system())))
clock.text = "%02d:%02d | Case time %d min" % [floori(float(remaining) / 60.0), remaining % 60, int(CCSService.attempt.get("simulatedMinute", 0))]
if remaining == 0 and not CCSService.is_loading: CCSService.load_attempt(str(CCSService.attempt.get("id", "")))
func _leave() -> void:
CCSService.abandon(); main.show_screen("ccs_hub")
func _end_case() -> void:
CCSService.send_command("exit_case" if str(CCSService.attempt.get("state", "")) == "final_orders" else "end_case")
func _clear(parent: Node) -> void:
for child in parent.get_children(): child.queue_free()
func _vitals_text(vitals: Dictionary) -> String:
var parts: Array[String] = []
for key in vitals: parts.append("%s: %s" % [str(key), str(vitals[key])])
return " | ".join(parts)
func _chart_text(entries: Array) -> String:
var lines: Array[String] = []
for entry in entries: lines.append("T+%d [%s] %s" % [entry.get("simulatedMinute", 0), entry.get("category", "note"), entry.get("text", "")])
return "\n\n".join(lines)