""" Web UI Lambda. Serves a simple HTML dashboard for viewing work orders and comments. Accessed via Lambda Function URL. """ import os import boto3 dynamodb = boto3.resource("dynamodb") WORK_ORDERS_TABLE = os.environ.get("WORK_ORDERS_TABLE", "WorkOrders") COMMENTS_TABLE = os.environ.get("COMMENTS_TABLE", "WorkOrderComments") def get_work_orders(): table = dynamodb.Table(WORK_ORDERS_TABLE) response = table.scan() items = response.get("Items", []) # Sort by updated_at descending items.sort(key=lambda x: x.get("updated_at", ""), reverse=True) return items def get_comments(work_order_id): table = dynamodb.Table(COMMENTS_TABLE) response = table.query( KeyConditionExpression="work_order_id = :woid", ExpressionAttributeValues={":woid": work_order_id}, ) items = response.get("Items", []) items.sort(key=lambda x: x.get("created_at", ""), reverse=True) return items def render_badge(value, color_map): color = color_map.get(value, "#9ca3af") label = value.replace("_", " ").title() return f'{label}' STATUS_COLORS = { "new": "#3b82f6", "assigned": "#8b5cf6", "in_progress": "#f59e0b", "on_hold": "#6b7280", "completed": "#10b981", "cancelled": "#ef4444", "unknown": "#9ca3af", } RECORD_TYPE_COLORS = { "new_work_order": "#3b82f6", "comment": "#8b5cf6", "update": "#f59e0b", "cancellation": "#ef4444", "unknown": "#9ca3af", } def render_work_order_detail(wo, events): events_html = "" if events: for e in events: record_type = e.get("record_type", "unknown") commenter = e.get("commenter", "") created = e.get("created_at", "") text = e.get("text", "") badge = render_badge(record_type, RECORD_TYPE_COLORS) commenter_str = ( f"{commenter} — " if commenter else "" ) border_colors = { "new_work_order": "#3b82f6", "comment": "#8b5cf6", "update": "#f59e0b", "cancellation": "#ef4444", } border = border_colors.get(record_type, "#94a3b8") events_html += f"""
{badge} {commenter_str}{created}
{"
" + text + "
" if text else ""}
""" else: events_html = '

No events yet.

' wo_id = wo.get("work_order_id", "") fields = [ ("Description", wo.get("description")), ("Status", render_badge(wo.get("wo_status", "unknown"), STATUS_COLORS)), ( "Record Type", render_badge(wo.get("record_type", "unknown"), RECORD_TYPE_COLORS), ), ("Site Code", wo.get("site_code")), ("Building", wo.get("building")), ("Address", wo.get("address")), ("Severity", wo.get("severity")), ("Priority", wo.get("priority")), ("Due Date", wo.get("due_date")), ("Date Reported", wo.get("date_reported")), ("Scheduled Start", wo.get("scheduled_start")), ("Assigned To", wo.get("assigned_to")), ("Created", wo.get("created_at")), ("Last Updated", wo.get("updated_at")), ] details_html = "" for label, value in fields: if value: details_html += f"""
{label}
{value}
""" return f""" WO {wo_id} - Sea Haven
← All Work Orders

Work Order {wo_id}

{details_html}

Events ({len(events)})

{events_html}
""" def render_work_orders_list(work_orders): rows = "" for wo in work_orders: wo_id = wo.get("work_order_id", "") desc = wo.get("description", "") site = wo.get("site_code", "") status = wo.get("wo_status", "unknown") record_type = wo.get("record_type", "unknown") due = wo.get("due_date", "") updated = wo.get("updated_at", "")[:16] rows += f""" {wo_id} {desc} {site} {render_badge(record_type, RECORD_TYPE_COLORS)} {render_badge(status, STATUS_COLORS)} {due} {updated} """ return f""" Work Orders - Sea Haven

Work Orders

{len(work_orders)} total
{rows if rows else ''}
WO # Description Site Last Action Status Due Date Updated
No work orders yet.
""" def handler(event, context): path = event.get("rawPath", "/") qs = event.get("queryStringParameters") or {} if path == "/wo" and "id" in qs: wo_id = qs["id"] # Get work order table = dynamodb.Table(WORK_ORDERS_TABLE) result = table.get_item(Key={"work_order_id": wo_id}) wo = result.get("Item") if not wo: return { "statusCode": 404, "headers": {"Content-Type": "text/html"}, "body": "

Work order not found

", } events = get_comments(wo_id) html = render_work_order_detail(wo, events) else: work_orders = get_work_orders() html = render_work_orders_list(work_orders) return { "statusCode": 200, "headers": {"Content-Type": "text/html"}, "body": html, }