Add HTML templates for medical report generator with navigation, upload, edit, and preview functionalities

- Created base template with navigation and layout structure
- Implemented upload.html for patient data and file uploads
- Developed edit.html for editing calculated metrics
- Added preview.html for displaying generated report previews
- Enhanced user experience with Tailwind CSS styling
This commit is contained in:
bolade
2025-11-17 17:15:44 +01:00
parent 4f97691ff9
commit 83f50882e2
14 changed files with 1726 additions and 1770 deletions
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Medical Report Generator{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
</style>
{% block extra_head %}{% endblock %}
</head>
<body class="bg-gray-50 min-h-screen">
<nav class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<h1 class="text-xl font-bold text-gray-900">ISHP Report Generator</h1>
</div>
<div class="flex items-center space-x-4">
<a href="/" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">Upload</a>
{% if session.get('report_path') %}
<a href="/preview" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">Preview</a>
<a href="/edit" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">Edit Metrics</a>
{% endif %}
</div>
</div>
</div>
</nav>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
{% block content %}{% endblock %}
</main>
{% block extra_scripts %}{% endblock %}
</body>
</html>
+197
View File
@@ -0,0 +1,197 @@
{% extends "base.html" %}
{% block title %}Edit Metrics - Report Generator{% endblock %}
{% block content %}
<div class="px-4 py-6 sm:px-0">
{% if not session.get('metrics') %}
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<p class="text-yellow-800">No metrics found. Please <a href="/" class="underline">generate a report</a> first.</p>
</div>
{% else %}
{% if error %}
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<p class="text-red-800">{{ error }}</p>
</div>
{% endif %}
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">Edit Calculated Metrics</h2>
<a href="/preview" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
Back to Preview
</a>
</div>
<form action="/edit" method="post" class="space-y-8">
<!-- Pnoe Metrics Section -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Pnoe Metrics</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="vo2_max" class="block text-sm font-medium text-gray-700">VO2 Max (ml/min)</label>
<input type="number" step="0.01" name="vo2_max" id="vo2_max"
value="{{ '%.2f'|format(session.metrics.pnoe['vo2_max']) if session.metrics.pnoe.get('vo2_max') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="vo2_max_per_kg" class="block text-sm font-medium text-gray-700">VO2 Max per kg (ml/min/kg)</label>
<input type="number" step="0.01" name="vo2_max_per_kg" id="vo2_max_per_kg"
value="{{ '%.2f'|format(session.metrics.pnoe['vo2_max_per_kg']) if session.metrics.pnoe.get('vo2_max_per_kg') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="peak_vt" class="block text-sm font-medium text-gray-700">Peak VT (L)</label>
<input type="number" step="0.01" name="peak_vt" id="peak_vt"
value="{{ '%.2f'|format(session.metrics.pnoe['peak_vt']) if session.metrics.pnoe.get('peak_vt') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="peak_vt_hr" class="block text-sm font-medium text-gray-700">Peak VT HR (bpm)</label>
<input type="number" step="1" name="peak_vt_hr" id="peak_vt_hr"
value="{{ '%.0f'|format(session.metrics.pnoe['peak_vt_hr']) if session.metrics.pnoe.get('peak_vt_hr') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fat_max_value" class="block text-sm font-medium text-gray-700">Fat Max Value (kcal/min)</label>
<input type="number" step="0.01" name="fat_max_value" id="fat_max_value"
value="{{ '%.2f'|format(session.metrics.pnoe['fat_max_value']) if session.metrics.pnoe.get('fat_max_value') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fat_max_hr" class="block text-sm font-medium text-gray-700">Fat Max HR (bpm)</label>
<input type="number" step="1" name="fat_max_hr" id="fat_max_hr"
value="{{ '%.0f'|format(session.metrics.pnoe['fat_max_hr']) if session.metrics.pnoe.get('fat_max_hr') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
</div>
</div>
<!-- VT1 Section -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">VT1 Threshold</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
<div>
<label for="vt1_hr" class="block text-sm font-medium text-gray-700">Heart Rate (bpm)</label>
<input type="number" step="1" name="vt1_hr" id="vt1_hr"
value="{{ '%.0f'|format(session.metrics.pnoe['vt1']['HeartRate']) if session.metrics.pnoe.get('vt1') and session.metrics.pnoe['vt1'].get('HeartRate') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="vt1_speed" class="block text-sm font-medium text-gray-700">Speed (mph)</label>
<input type="number" step="0.01" name="vt1_speed" id="vt1_speed"
value="{{ '%.2f'|format(session.metrics.pnoe['vt1']['Speed']) if session.metrics.pnoe.get('vt1') and session.metrics.pnoe['vt1'].get('Speed') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="vt1_time" class="block text-sm font-medium text-gray-700">Time (sec)</label>
<input type="number" step="1" name="vt1_time" id="vt1_time"
value="{{ '%.0f'|format(session.metrics.pnoe['vt1']['Time']) if session.metrics.pnoe.get('vt1') and session.metrics.pnoe['vt1'].get('Time') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
</div>
</div>
<!-- VT2 Section -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">VT2 Threshold</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
<div>
<label for="vt2_hr" class="block text-sm font-medium text-gray-700">Heart Rate (bpm)</label>
<input type="number" step="1" name="vt2_hr" id="vt2_hr"
value="{{ '%.0f'|format(session.metrics.pnoe['vt2']['HeartRate']) if session.metrics.pnoe.get('vt2') and session.metrics.pnoe['vt2'].get('HeartRate') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="vt2_speed" class="block text-sm font-medium text-gray-700">Speed (mph)</label>
<input type="number" step="0.01" name="vt2_speed" id="vt2_speed"
value="{{ '%.2f'|format(session.metrics.pnoe['vt2']['Speed']) if session.metrics.pnoe.get('vt2') and session.metrics.pnoe['vt2'].get('Speed') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="vt2_time" class="block text-sm font-medium text-gray-700">Time (sec)</label>
<input type="number" step="1" name="vt2_time" id="vt2_time"
value="{{ '%.0f'|format(session.metrics.pnoe['vt2']['Time']) if session.metrics.pnoe.get('vt2') and session.metrics.pnoe['vt2'].get('Time') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
</div>
</div>
<!-- Heart Rate Zones -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Heart Rate Zones</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-5">
{% for i in range(1, 6) %}
{% set zone_key = "zone" + i|string + "_bpm" %}
<div>
<label for="{{ zone_key }}" class="block text-sm font-medium text-gray-700">Zone {{ i }} (e.g., 120-140bpm)</label>
<input type="text" name="{{ zone_key }}" id="{{ zone_key }}"
value="{{ session.metrics.pnoe[zone_key] if session.metrics.pnoe.get(zone_key) else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
{% endfor %}
</div>
</div>
<!-- Spirometry Metrics -->
{% if session.metrics.spirometry %}
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Spirometry Metrics</h3>
<div class="space-y-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label for="fvc_best" class="block text-sm font-medium text-gray-700">FVC Best (L)</label>
<input type="number" step="0.01" name="fvc_best" id="fvc_best"
value="{{ '%.2f'|format(session.metrics.spirometry['fvc_best']) if session.metrics.spirometry.get('fvc_best') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fvc_pred" class="block text-sm font-medium text-gray-700">FVC % Predicted</label>
<input type="number" step="0.1" name="fvc_pred" id="fvc_pred"
value="{{ '%.1f'|format(session.metrics.spirometry['fvc_pred']) if session.metrics.spirometry.get('fvc_pred') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fev1_best" class="block text-sm font-medium text-gray-700">FEV1 Best (L)</label>
<input type="number" step="0.01" name="fev1_best" id="fev1_best"
value="{{ '%.2f'|format(session.metrics.spirometry['fev1_best']) if session.metrics.spirometry.get('fev1_best') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fev1_pred" class="block text-sm font-medium text-gray-700">FEV1 % Predicted</label>
<input type="number" step="0.1" name="fev1_pred" id="fev1_pred"
value="{{ '%.1f'|format(session.metrics.spirometry['fev1_pred']) if session.metrics.spirometry.get('fev1_pred') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fev1_fvc_pct_best" class="block text-sm font-medium text-gray-700">FEV1/FVC% Best</label>
<input type="number" step="0.01" name="fev1_fvc_pct_best" id="fev1_fvc_pct_best"
value="{{ '%.2f'|format(session.metrics.spirometry['fev1_fvc_pct_best']) if session.metrics.spirometry.get('fev1_fvc_pct_best') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="fev1_fvc_pct_pred" class="block text-sm font-medium text-gray-700">FEV1/FVC% % Predicted</label>
<input type="number" step="0.1" name="fev1_fvc_pct_pred" id="fev1_fvc_pct_pred"
value="{{ '%.1f'|format(session.metrics.spirometry['fev1_fvc_pct_pred']) if session.metrics.spirometry.get('fev1_fvc_pct_pred') else '' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
</div>
</div>
</div>
{% endif %}
<!-- Submit Button -->
<div class="flex justify-end">
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Regenerate Report
</button>
</div>
</form>
</div>
</div>
{% endif %}
</div>
{% endblock %}
+180
View File
@@ -0,0 +1,180 @@
{% extends "base.html" %}
{% block title %}Report Preview - Report Generator{% endblock %}
{% block content %}
<div class="px-4 py-6 sm:px-0">
{% if not session.get('report_path') %}
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
<p class="text-yellow-800">No report found. Please <a href="/" class="underline">upload files</a> first.</p>
</div>
{% else %}
<div class="bg-white shadow rounded-lg mb-6">
<div class="px-4 py-5 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">Generated Report Preview</h2>
<div class="flex space-x-3">
<a href="/edit" class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
Edit Metrics
</a>
<a href="/download-report/{{ session.report_path.split('/')[-1] }}" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700">
Download PDF
</a>
</div>
</div>
<!-- Patient Information -->
<div class="border-b border-gray-200 pb-6 mb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Patient Information</h3>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div>
<p class="text-sm text-gray-500">Name</p>
<p class="text-base font-medium text-gray-900">{{ session.patient_info['patient_name'] }}</p>
</div>
<div>
<p class="text-sm text-gray-500">Age</p>
<p class="text-base font-medium text-gray-900">{{ session.patient_info['age'] }}</p>
</div>
<div>
<p class="text-sm text-gray-500">Height</p>
<p class="text-base font-medium text-gray-900">{{ session.patient_info['height'] }}</p>
</div>
<div>
<p class="text-sm text-gray-500">Weight</p>
<p class="text-base font-medium text-gray-900">{{ session.patient_info['weight'] }}</p>
</div>
</div>
</div>
<!-- Calculated Metrics -->
{% if session.metrics %}
<div class="space-y-6">
<!-- Pnoe Metrics -->
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Pnoe Metrics</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{% if session.metrics.pnoe.get('vo2_max') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">VO2 Max</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.pnoe['vo2_max']) }} ml/min</p>
</div>
{% endif %}
{% if session.metrics.pnoe.get('vo2_max_per_kg') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">VO2 Max per kg</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.pnoe['vo2_max_per_kg']) }} ml/min/kg</p>
</div>
{% endif %}
{% if session.metrics.pnoe.get('peak_vt') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Peak VT</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.pnoe['peak_vt']) }} L</p>
<p class="text-sm text-gray-500 mt-1">HR: {{ "%.0f"|format(session.metrics.pnoe['peak_vt_hr']) }} bpm</p>
</div>
{% endif %}
{% if session.metrics.pnoe.get('fat_max_value') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Fat Max Value</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.pnoe['fat_max_value']) }} kcal/min</p>
<p class="text-sm text-gray-500 mt-1">HR: {{ "%.0f"|format(session.metrics.pnoe['fat_max_hr']) }} bpm</p>
</div>
{% endif %}
</div>
</div>
<!-- VT1 and VT2 -->
{% if session.metrics.pnoe.get('vt1') or session.metrics.pnoe.get('vt2') %}
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Ventilatory Thresholds</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
{% if session.metrics.pnoe.get('vt1') %}
<div class="bg-blue-50 p-4 rounded-lg">
<p class="text-sm font-medium text-blue-900 mb-2">VT1</p>
<p class="text-sm text-blue-700">Heart Rate: {{ "%.0f"|format(session.metrics.pnoe['vt1']['HeartRate']) }} bpm</p>
<p class="text-sm text-blue-700">Speed: {{ "%.2f"|format(session.metrics.pnoe['vt1']['Speed']) }} mph</p>
<p class="text-sm text-blue-700">Time: {{ "%.0f"|format(session.metrics.pnoe['vt1']['Time']) }} sec</p>
</div>
{% endif %}
{% if session.metrics.pnoe.get('vt2') %}
<div class="bg-green-50 p-4 rounded-lg">
<p class="text-sm font-medium text-green-900 mb-2">VT2</p>
<p class="text-sm text-green-700">Heart Rate: {{ "%.0f"|format(session.metrics.pnoe['vt2']['HeartRate']) }} bpm</p>
<p class="text-sm text-green-700">Speed: {{ "%.2f"|format(session.metrics.pnoe['vt2']['Speed']) }} mph</p>
<p class="text-sm text-green-700">Time: {{ "%.0f"|format(session.metrics.pnoe['vt2']['Time']) }} sec</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Heart Rate Zones -->
{% if session.metrics.pnoe.get('zone1_bpm') %}
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Heart Rate Zones</h3>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-5">
{% for i in range(1, 6) %}
{% set zone_key = "zone" + i|string + "_bpm" %}
{% if session.metrics.pnoe.get(zone_key) %}
<div class="bg-gray-50 p-3 rounded-lg text-center">
<p class="text-xs text-gray-500">Zone {{ i }}</p>
<p class="text-sm font-medium text-gray-900">{{ session.metrics.pnoe[zone_key] }}</p>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<!-- Spirometry Metrics -->
{% if session.metrics.spirometry %}
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">Spirometry Metrics</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
{% if session.metrics.spirometry.get('fvc_best') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">FVC Best</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.spirometry['fvc_best']) }} L</p>
<p class="text-sm text-gray-500 mt-1">{{ "%.1f"|format(session.metrics.spirometry['fvc_pred']) }}% predicted</p>
</div>
{% endif %}
{% if session.metrics.spirometry.get('fev1_best') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">FEV1 Best</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.spirometry['fev1_best']) }} L</p>
<p class="text-sm text-gray-500 mt-1">{{ "%.1f"|format(session.metrics.spirometry['fev1_pred']) }}% predicted</p>
</div>
{% endif %}
{% if session.metrics.spirometry.get('fev1_fvc_pct_best') %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">FEV1/FVC%</p>
<p class="text-2xl font-bold text-gray-900">{{ "%.2f"|format(session.metrics.spirometry['fev1_fvc_pct_best']) }}%</p>
<p class="text-sm text-gray-500 mt-1">{{ "%.1f"|format(session.metrics.spirometry['fev1_fvc_pct_pred']) }}% predicted</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endif %}
<!-- Graphs Section -->
{% if session.graphs_generated %}
<div class="mt-8">
<h3 class="text-lg font-medium text-gray-900 mb-4">Generated Graphs</h3>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
{% for graph in session.graphs_generated %}
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm font-medium text-gray-700 mb-2">{{ graph.name|replace('_', ' ')|title }}</p>
<img src="/graphs/{{ graph.path.split('/')[-1] }}" alt="{{ graph.name }}" class="w-full h-auto rounded">
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
+106
View File
@@ -0,0 +1,106 @@
{% extends "base.html" %}
{% block title %}Upload Patient Data - Report Generator{% endblock %}
{% block content %}
<div class="px-4 py-6 sm:px-0">
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Upload Patient Data and Files</h2>
{% if error %}
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<p class="text-red-800">{{ error }}</p>
</div>
{% endif %}
<form action="/upload" method="post" enctype="multipart/form-data" class="space-y-6">
<!-- Patient Information Section -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Patient Information</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="first_name" class="block text-sm font-medium text-gray-700">First Name</label>
<input type="text" name="first_name" id="first_name" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="last_name" class="block text-sm font-medium text-gray-700">Last Name</label>
<input type="text" name="last_name" id="last_name" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="age" class="block text-sm font-medium text-gray-700">Age</label>
<input type="number" name="age" id="age" required min="1" max="120"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="height" class="block text-sm font-medium text-gray-700">Height (e.g., 5'4" or 165cm)</label>
<input type="text" name="height" id="height" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border"
placeholder="5'4&quot;">
</div>
<div>
<label for="weight" class="block text-sm font-medium text-gray-700">Weight (e.g., 123lbs or 56kg)</label>
<input type="text" name="weight" id="weight" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border"
placeholder="123lbs">
</div>
<div>
<label for="gender" class="block text-sm font-medium text-gray-700">Gender</label>
<select name="gender" id="gender" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
<option value="">Select...</option>
<option value="male">Male</option>
<option value="female">Female</option>
<option value="other">Other</option>
</select>
</div>
<div>
<label for="focus" class="block text-sm font-medium text-gray-700">Training Focus</label>
<input type="text" name="focus" id="focus" value="Endurance"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
<div>
<label for="session_id" class="block text-sm font-medium text-gray-700">Session ID</label>
<input type="text" name="session_id" id="session_id" value="default"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border">
</div>
</div>
</div>
<!-- File Upload Section -->
<div class="border-b border-gray-200 pb-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Upload Files</h3>
<div class="space-y-4">
<div>
<label for="spirometry_pdf" class="block text-sm font-medium text-gray-700">Spirometry PDF</label>
<input type="file" name="spirometry_pdf" id="spirometry_pdf" accept=".pdf" required
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
</div>
<div>
<label for="pnoe_csv" class="block text-sm font-medium text-gray-700">Pnoe CSV</label>
<input type="file" name="pnoe_csv" id="pnoe_csv" accept=".csv" required
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
</div>
<div>
<label for="seca_excel" class="block text-sm font-medium text-gray-700">SECA Excel</label>
<input type="file" name="seca_excel" id="seca_excel" accept=".xlsx,.xls" required
class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100">
</div>
</div>
</div>
<!-- Submit Button -->
<div class="flex justify-end">
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Generate Report
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}