diff --git a/0.5 b/0.5 deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/app/models/patient.py b/app/models/patient.py index 8debfc512245fcbdb92c91c7e55cb7a33518af72..53ff188d5d7bb492a457417daabd9f18ecc02eef 100644 --- a/app/models/patient.py +++ b/app/models/patient.py @@ -82,13 +82,13 @@ class Patient(db.Model): return "Not Available" if self.bmi < 18.5: - return "Thiếu cân" + return "Underweight" elif self.bmi < 25: - return "Bình thường" + return "Normal" elif self.bmi < 30: - return "Thừa cân" + return "Overweight" else: - return "Béo phì" + return "Obese" def get_bmi_color_class(self): """Trả về class màu cho BMI để hiển thị trên UI""" diff --git a/app/routes/dietitian.py b/app/routes/dietitian.py index b62cccf91b071c8e8cc86239ddee0ee7f90a0c2f..163fcfd8faf837098c0356749ea45f0a41154ca8 100644 --- a/app/routes/dietitian.py +++ b/app/routes/dietitian.py @@ -313,14 +313,8 @@ def new_procedure(patient_id): if redirect_to_report_id: return redirect(url_for('reports.view_report', report_id=redirect_to_report_id)) - # Kiểm tra xem có phải đang đến từ trang procedures chung không - referrer = request.referrer or '' - if 'procedures' in referrer and 'patient' not in referrer: - # Nếu đến từ trang procedures chung, redirect về đó với filter - return redirect(url_for('.list_my_procedures', patient_id=patient.id)) - else: - # Ngược lại về trang procedures của bệnh nhân cụ thể - return redirect(url_for('.list_procedures', patient_id=patient.id)) + # Luôn redirect về trang procedures chính + return redirect(url_for('.list_my_procedures')) except Exception as e: db.session.rollback() @@ -366,8 +360,8 @@ def edit_procedure(procedure_id): db.session.commit() flash(f'Procedure "{procedure.procedureType}" updated successfully.', 'success') - # Redirect về danh sách procedure của bệnh nhân - return redirect(url_for('.list_procedures', patient_id=patient.id)) + # Redirect về trang procedures chính + return redirect(url_for('.list_my_procedures')) except Exception as e: db.session.rollback() flash(f'Error updating procedure: {e}', 'danger') @@ -397,8 +391,8 @@ def delete_procedure(procedure_id): db.session.rollback() flash(f'Error deleting procedure: {e}', 'danger') - # Redirect về danh sách procedure của bệnh nhân - return redirect(url_for('.list_procedures', patient_id=patient_id)) + # Redirect về trang procedures chính + return redirect(url_for('.list_my_procedures')) # Route để hiển thị danh sách Thủ thuật cho một bệnh nhân cụ thể @dietitian_bp.route('/patient/<string:patient_id>/procedures', methods=['GET']) diff --git a/app/static/img/logo.png b/app/static/img/logo.png index 66882e7e04ebeb5992299c855c9f0eea03b899bb..04970816379be3b2ad723b0c6b19e90db75823b0 100644 Binary files a/app/static/img/logo.png and b/app/static/img/logo.png differ diff --git a/app/templates/base.html b/app/templates/base.html index 00792b72d0b77333803785ab834fc64d44fd0cf8..ca9f8fc4b067caa41f147ca6ee334c5f56ad7e56 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -284,9 +284,9 @@ {% if current_user.is_authenticated %} <!-- Sidebar - Fixed, with collapse functionality --> <div id="sidebar" class="sidebar bg-blue-600 text-white py-4 px-6 flex-shrink-0"> - <div class="flex items-center justify-between mb-10"> - <span class="text-xl font-bold animate-pulse-slow sidebar-brand-full">CCU HTM</span> - <span class="text-xl font-bold sidebar-brand-mini" style="display: none;">C H</span> + <div class="flex items-center justify-center mb-10"> + <span class="text-xl font-bold animate-pulse-slow sidebar-brand-full w-full text-center">CCU HTM</span> + <span class="text-xl font-bold sidebar-brand-mini" style="display: none;">BALLS</span> <div class="sidebar-toggle" id="sidebarToggle"> <i class="fas fa-chevron-left"></i> </div> @@ -363,7 +363,7 @@ <div class="container mx-auto px-6 py-3"> <div class="flex justify-between items-center"> <div class="text-xl font-bold text-blue-600 animate-bounce-in"> - {% block header_title %}CCU HTM{% endblock %} + {% block header_title %}<img src="{{ url_for('static', filename='img/logo.png') }}" alt="CCU HTM" class="h-14 w-auto">{% endblock %} </div> <div class="flex items-center space-x-4"> @@ -415,10 +415,6 @@ </div> </a> </div> - - <div class="px-4 py-2 border-t border-gray-200"> - <a href="#" class="text-sm text-blue-600 hover:text-blue-800">View all notifications</a> - </div> </div> </div> @@ -500,7 +496,7 @@ <!-- Footer --> <footer class="bg-white py-6 mt-12"> <div class="container mx-auto px-4"> - <p class="text-center text-gray-500 text-sm">© {{ current_year|default(2023) }} CCU HTM - Critical Care Unit by HuanTrungMing</p> + <p class="text-center text-gray-500 text-sm">© 2025 CCU HTM - Critical Care Unit by HuanTrungMing</p> </div> </footer> diff --git a/app/templates/dietitian_procedures.html b/app/templates/dietitian_procedures.html index 89189adfa22651169ca9a853feda92c2aac7db8a..fb411661d09d5acabc34a8ace0b623b66e769976 100644 --- a/app/templates/dietitian_procedures.html +++ b/app/templates/dietitian_procedures.html @@ -9,7 +9,7 @@ {{ flash_messages() }} <div class="mb-6 flex justify-between items-center"> - <h1 class="text-2xl font-bold text-gray-800">Add procedures</h1> + <h1 class="text-2xl font-bold text-gray-800">Your procedures</h1> {# Hiển thị nút Add Procedure chỉ khi đã chọn bệnh nhân cụ thể #} {% if selected_patient_id %} <a href="{{ url_for('dietitian.new_procedure', patient_id=selected_patient_id) }}" @@ -23,7 +23,7 @@ <form method="GET" action="{{ url_for('.list_my_procedures') }}" class="mb-6 bg-white shadow-md rounded px-8 pt-6 pb-8"> <div class="flex flex-wrap gap-4 items-end"> <div class="flex-grow"> - <label for="patient_id" class="block text-gray-700 text-sm font-bold mb-2">Filter by Patient:</label> + <label for="patient_id" class="block text-gray-700 text-sm font-bold mb-2">Choose a Patient:</label> <select id="patient_id" name="patient_id" class="shadow border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"> <option value="">All Patients</option> {% for patient in patients_for_filter %} @@ -32,7 +32,7 @@ </select> </div> <div class="flex items-end space-x-2"> - <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Filter</button> + <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Choose</button> <a href="{{ url_for('.list_my_procedures') }}" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">Reset</a> </div> </div> @@ -122,12 +122,12 @@ </div> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left flex-grow"> <h3 class="text-lg font-semibold leading-6 text-gray-900" id="deleteProcedureConfirmModalLabel"> - Xác nhận Xóa Thủ thuật + Confirm Delete Procedure </h3> <div class="mt-2"> <p class="text-sm text-gray-600"> - Bạn có chắc chắn muốn xóa thủ thuật <strong id="procedure-info-placeholder" class="font-medium">[Procedure Info]</strong>? -Hành động này không thể hoàn tác. + Do you really want to delete this procedure? <strong id="procedure-info-placeholder" class="font-medium">[Procedure Info]</strong>? +This action is irreversible! </p> </div> </div> @@ -141,11 +141,11 @@ Hành động này không thể hoàn tác. <form id="delete-procedure-form" action="#" method="POST" class="inline-block"> {{ empty_form.csrf_token }} <button id="confirm-delete-procedure-btn" type="submit" class="inline-flex w-full justify-center rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto"> - Xóa + Delete </button> </form> <button type="button" class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto modal-close-btn"> - Hủy bỏ + Cancel </button> </div> </div> diff --git a/app/templates/index.html b/app/templates/index.html deleted file mode 100644 index df0643623877aba23d6c1ad41c009468bc2455e1..0000000000000000000000000000000000000000 --- a/app/templates/index.html +++ /dev/null @@ -1,88 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Trang chủ - CCU HTM{% endblock %} - -{% block content %} -<div class="min-h-screen bg-gradient-to-b from-primary-50 to-primary-100"> - <div class="container mx-auto px-4 py-16"> - <div class="max-w-5xl mx-auto text-center"> - <h1 class="text-4xl md:text-5xl font-bold text-primary-800 mb-8 animate-fadeIn"> - Chào mừng đến với CCU HTM - </h1> - <p class="text-xl text-gray-700 mb-10 animate-fadeIn delay-150"> - Hệ thống Quản lý Bệnh nhân và Đo lường Y sinh - </p> - - <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-16"> - <div class="bg-white rounded-xl shadow-md p-8 transform transition hover:scale-105 hover:shadow-lg"> - <div class="text-5xl text-primary-600 mb-4"> - <i class="fas fa-user-md"></i> - </div> - <h2 class="text-2xl font-bold text-gray-800 mb-4">Dành cho Chuyên viên Y tế</h2> - <p class="text-gray-600 mb-6"> - Truy cập hệ thống để quản lý bệnh nhân, theo dõi dữ liệu lâm sàng và phân tích báo cáo - </p> - <a href="{{ url_for('auth.login') }}" class="inline-block bg-primary-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-primary-700 transition"> - Đăng nhập - </a> - </div> - - <div class="bg-white rounded-xl shadow-md p-8 transform transition hover:scale-105 hover:shadow-lg"> - <div class="text-5xl text-primary-600 mb-4"> - <i class="fas fa-info-circle"></i> - </div> - <h2 class="text-2xl font-bold text-gray-800 mb-4">Tìm hiểu thêm</h2> - <p class="text-gray-600 mb-6"> - Khám phá khả năng của hệ thống và cách CCU HTM có thể hỗ trợ quy trình làm việc của bạn - </p> - <a href="#features" class="inline-block bg-gray-200 text-gray-800 px-6 py-3 rounded-lg font-medium hover:bg-gray-300 transition"> - Tìm hiểu thêm - </a> - </div> - </div> - - <div id="features" class="py-8"> - <h2 class="text-3xl font-bold text-gray-800 mb-8">Tính năng chính</h2> - - <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> - <div class="bg-white rounded-lg p-6 shadow"> - <div class="text-primary-600 text-4xl mb-4"> - <i class="fas fa-chart-line"></i> - </div> - <h3 class="text-xl font-semibold text-gray-800 mb-2">Phân tích Dữ liệu</h3> - <p class="text-gray-600"> - Phân tích các số liệu dinh dưỡng và theo dõi xu hướng qua thời gian - </p> - </div> - - <div class="bg-white rounded-lg p-6 shadow"> - <div class="text-primary-600 text-4xl mb-4"> - <i class="fas fa-file-alt"></i> - </div> - <h3 class="text-xl font-semibold text-gray-800 mb-2">Báo cáo Thông minh</h3> - <p class="text-gray-600"> - Tạo báo cáo chi tiết và trực quan để hỗ trợ việc ra quyết định - </p> - </div> - - <div class="bg-white rounded-lg p-6 shadow"> - <div class="text-primary-600 text-4xl mb-4"> - <i class="fas fa-user-nurse"></i> - </div> - <h3 class="text-xl font-semibold text-gray-800 mb-2">Quản lý Bệnh nhân</h3> - <p class="text-gray-600"> - Hệ thống toàn diện để quản lý thông tin và quá trình điều trị của bệnh nhân - </p> - </div> - </div> - - <div class="mt-12"> - <a href="{{ url_for('auth.login') }}" class="inline-block bg-blue-600 text-white px-8 py-4 rounded-lg font-medium hover:bg-blue-700 transition text-lg"> - Đăng nhập ngay - </a> - </div> - </div> - </div> - </div> -</div> -{% endblock %} \ No newline at end of file diff --git a/app/templates/list_patient_procedures.html b/app/templates/list_patient_procedures.html deleted file mode 100644 index 6f1261ea5fe0c4de6b751319f6310eec3cd5bf3d..0000000000000000000000000000000000000000 --- a/app/templates/list_patient_procedures.html +++ /dev/null @@ -1,106 +0,0 @@ -{% extends 'base.html' %} - -{% block title %}Procedures for {{ patient.full_name }} - Dietitian Area{% endblock %} - -{% block content %} -<div class="container mx-auto px-4 py-6 animate-slide-in"> - - <div class="bg-white shadow-md rounded-lg p-6 mb-8"> - <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 pb-4 border-b border-gray-200"> - <div> - <h1 class="text-2xl font-bold text-gray-800"> - Procedures for: {{ patient.full_name }} ({{ patient.id }}) - </h1> - <p class="text-sm text-gray-500 mt-1"> - <a href="{{ url_for('patients.patient_detail', patient_id=patient.id) }}" class="text-primary-600 hover:underline">Back to Patient Detail</a> - </p> - </div> - {# Nút Add Procedure #} - <a href="{{ url_for('.new_procedure', patient_id=patient.id) }}" - class="mt-3 sm:mt-0 inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-150 - {% if has_ongoing_encounter %}bg-blue-600 hover:bg-blue-700{% else %}bg-gray-400 cursor-not-allowed{% endif %}" - {% if not has_ongoing_encounter %}aria-disabled="true" title="No active encounter to add procedure to." tabindex="-1" {% endif %}> - <i class="fas fa-plus mr-2"></i> Add New Procedure - </a> - </div> - - {# Procedure Table #} - {% if procedures and procedures|length > 0 %} - <div class="overflow-x-auto"> - <table class="min-w-full divide-y divide-gray-200"> - <thead class="bg-gray-50"> - <tr> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Performed Time</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">End Time</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Results</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Encounter</th> - <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> - </tr> - </thead> - <tbody class="bg-white divide-y divide-gray-200"> - {% for proc in procedures %} - <tr class="hover:bg-gray-50"> - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ proc.procedureType }}</td> - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ proc.procedureName or 'N/A' }}</td> - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ proc.procedureDateTime.strftime('%d/%m/%Y %H:%M') if proc.procedureDateTime else 'N/A' }}</td> - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ proc.procedureEndDateTime.strftime('%d/%m/%Y %H:%M') if proc.procedureEndDateTime else 'N/A' }}</td> - <td class="px-6 py-4 whitespace-normal text-sm text-gray-500 max-w-xs truncate" title="{{ proc.description or 'N/A' }}">{{ proc.description or 'N/A' }}</td> - <td class="px-6 py-4 whitespace-normal text-sm text-gray-500 max-w-xs truncate" title="{{ proc.procedureResults or 'N/A' }}">{{ proc.procedureResults or 'N/A' }}</td> - <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> - {% if proc.encounter %} - <a href="{{ url_for('patients.encounter_measurements', patient_id=proc.patient_id, encounter_id=proc.encounter.encounterID) }}" class="text-blue-600 hover:underline"> - {{ proc.encounter.custom_encounter_id or proc.encounter.encounterID }} - </a> - {% else %} - N/A - {% endif %} - </td> - <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2"> - {# Nút Edit #} - <a href="{{ url_for('.edit_procedure', procedure_id=proc.id) }}" - class="text-indigo-600 hover:text-indigo-900 text-lg p-1 rounded-md hover:bg-indigo-100 transition duration-150 ease-in-out" - title="Edit Procedure"> - <i class="fas fa-edit"></i> - </a> - {# Nút Delete (dùng JS confirm hoặc modal nếu có) #} - <form action="{{ url_for('.delete_procedure', procedure_id=proc.id) }}" method="POST" class="inline needs-confirmation" data-confirmation-message="Are you sure you want to delete procedure #{{ proc.id }}?"> - <input type="hidden" name="_method" value="DELETE"> {# Nếu dùng method override #} - {{ csrf_token() if csrf_token else '' }} {# Add CSRF token #} - <button type="submit" - class="text-red-600 hover:text-red-900 text-lg p-1 rounded-md hover:bg-red-100 transition duration-150 ease-in-out" - title="Delete Procedure"> - <i class="fas fa-trash-alt"></i> - </button> - </form> - </td> - </tr> - {% endfor %} - </tbody> - </table> - </div> - {% else %} - <p class="text-gray-500 italic text-center py-4">No procedures recorded for this patient yet.</p> - {% endif %} - </div> - -</div> - -{# Optional: Add JS for confirmation modal if needed #} -<script> -document.addEventListener('DOMContentLoaded', function() { - // Basic confirmation for delete buttons - document.querySelectorAll('form.needs-confirmation').forEach(form => { - form.addEventListener('submit', function(event) { - const message = this.getAttribute('data-confirmation-message') || 'Are you sure?'; - if (!confirm(message)) { - event.preventDefault(); // Stop submission if user cancels - } - }); - }); -}); -</script> - -{% endblock %} \ No newline at end of file diff --git a/app/templates/patient_detail.html b/app/templates/patient_detail.html index f8e5c6d8ecf5ee7f81ba7769f0a0f35196de9653..cac6f961eb9cbc4e85204a63d7414718ade15f00 100644 --- a/app/templates/patient_detail.html +++ b/app/templates/patient_detail.html @@ -123,7 +123,7 @@ } %} {% set r_color = r_color_map.get(r_status, 'gray') %} <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{ r_color }}-100 text-{{ r_color }}-800"> - {{ r_status.value if r_status else 'Không có' }} {# Hiển thị giá trị Enum hoặc 'Không có' #} + {{ r_status.value if r_status else 'None' }} {# Hiển thị giá trị Enum hoặc 'Không có' #} </span> </div> </div> @@ -262,8 +262,8 @@ </div> <div class="mt-2 flex justify-between w-full text-xs text-gray-500"> <span>0</span> - <span>18.5</span> - <span>25</span> + <span>12.5</span> + <span>22</span> <span>30</span> <span>40+</span> </div> @@ -873,7 +873,7 @@ </div> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <h3 class="text-lg leading-6 font-medium text-gray-900" id="add-encounter-modal-title"> - Xác nhận Thêm Lượt khám + Confirm Add Encounter? </h3> <div class="mt-2"> <p class="text-sm text-gray-500" id="addEncounterConfirmationMessage"> @@ -886,10 +886,10 @@ <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <button type="button" id="confirmAddEncounterBtn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> <!-- Button text might change (e.g., 'Confirm', 'Go to Report') --> - Xác nhận + Confirm </button> <button type="button" id="cancelAddEncounterBtn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm modal-cancel-btn"> - Hủy bỏ + Cancel </button> </div> </div> @@ -1139,8 +1139,8 @@ if (isAdmin) { // Admin can interrupt with confirmation - confirmMsg = `Chuyên gia DD ${dietitianName} chưa hoàn thành báo cáo cho lượt khám hiện tại (ID: ${displayId}). Bạn có chắc chắn muốn gián đoạn và tạo lượt khám mới không?`; - confirmButtonText = 'Tạo lượt khám mới'; + confirmMsg = `Dietitian ${dietitianName} has not yet completed the report for the current Encounter (ID: ${displayId}). Are you sure you want to interrupt and create a new encounter?`; + confirmButtonText = 'Create New Encounter'; actionOnClick = () => { console.log("[New Encounter Form Logic] Admin confirmed via modal, submitting form"); addEncounterModalInstance.hide(); @@ -1151,8 +1151,8 @@ // Dietitian must complete report first if (editReportUrl) { // Modify popup for Dietitian: simple alert, OK button only - confirmMsg = 'Lượt khám hiện tại đang diễn ra. Vui lòng hoàn thành báo cáo đánh giá trước khi tạo lượt khám mới!'; - confirmButtonText = 'Đã hiểu'; // Change button text to 'OK' or similar + confirmMsg = 'The current encounter is still in progress. Please complete the assessment report before creating a new encounter!'; + confirmButtonText = 'Understood'; // Change button text to 'OK' or similar actionOnClick = handleCancelClick; // Just close the modal console.log("[New Encounter Form Logic] Preparing dietitian informational modal"); // Hide cancel button for this case @@ -1194,7 +1194,7 @@ addEncounterModalInstance.show(); } else if (currentStatus === 'NOT_STARTED') { - alert('Không thể thêm lượt khám mới khi lượt khám trước đó chưa bắt đầu.'); + alert('Cannot create a new encounter while the previous one has not yet started.'); } else { // Status is FINISHED or null (no encounter) console.log("[New Encounter Form Logic] Status is FINISHED or null, submitting form"); diff --git a/app/templates/patients.html b/app/templates/patients.html index 5cd8513aacbd2d59d1fbdf2301c1de16ea587808..e2c91c773aabd0f97d5f1d4dcf896bd38f183a22 100644 --- a/app/templates/patients.html +++ b/app/templates/patients.html @@ -362,7 +362,7 @@ deleteForm.action = `{{ url_for('patients.delete_patient', patient_id='PATIENT_ID_PLACEHOLDER') }}`.replace('PATIENT_ID_PLACEHOLDER', patientId); // (Tùy chọn) Hiển thị ID/tên bệnh nhân trong modal - const patientNameElement = this.closest('tr').querySelector('.text-sm.font-medium.text-gray.900'); + const patientNameElement = this.closest('tr').querySelector('.text-sm.font-medium.text-gray-900'); // Lấy element để hiển thị thông báo trong modal (cần id hoặc class cụ thể) const modalMessageElement = deleteModal.querySelector('#delete-confirmation-message'); // Giả sử element có id này if (modalMessageElement && patientNameElement) { diff --git a/app/templates/procedure_form.html b/app/templates/procedure_form.html index 54c52f99d7b0c2c9dc40c559c392ffe10b34d841..0da40622438caa903815eb814f0e3b3ab239ec39 100644 --- a/app/templates/procedure_form.html +++ b/app/templates/procedure_form.html @@ -1,6 +1,7 @@ {% extends 'base.html' %} {% block title %}{% if edit_mode %}Edit Procedure{% else %}New Procedure{% endif %} - Dietitian Area{% endblock %} +{% block header %}{% if edit_mode %}Edit Procedure{% else %}New Procedure{% endif %}{% endblock %} {% block head %} {{ super() }} @@ -19,7 +20,7 @@ Add New Procedure for Patient: {{ patient.full_name }} ({{ patient.id }}) {% endif %} </h1> - <a href="{{ url_for('.list_procedures', patient_id=patient.id if patient else None) }}" class="text-gray-600 hover:text-gray-800 font-medium flex items-center transition duration-300"> + <a href="{{ url_for('.list_my_procedures') }}" class="text-gray-600 hover:text-gray-800 font-medium flex items-center transition duration-300"> <i class="fas fa-arrow-left mr-2"></i> Back to Procedures List </a> diff --git a/app/templates/report_detail.html b/app/templates/report_detail.html index 87da5cfd0797d76b74e774c5764b0e5734a85254..d97f1e44399994ae6f242bd44f01215e7a6476b1 100644 --- a/app/templates/report_detail.html +++ b/app/templates/report_detail.html @@ -216,21 +216,18 @@ <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <div> <div class="h-16 mb-2"> - {% if report.status == 'finalized' %} - <img src="{{ url_for('static', filename='img/signature.png') }}" alt="Digital Signature" class="h-full"> + {% if report.status == 'finalized' and report.dietitian and report.dietitian.signature_image %} + <img src="{{ url_for('static', filename='img/signatures/' + report.dietitian.signature_image) }}" alt="Digital Signature" class="h-full"> {% endif %} </div> - <p class="font-medium">{{ report.author.full_name }}</p> - <p class="text-sm text-gray-600">{{ report.author.role }}</p> - <p class="text-sm text-gray-600">License #: {{ report.author.license_number }}</p> + <p class="font-medium">{{ report.dietitian.full_name if report.dietitian else 'Pending Assignment' }}</p> + <p class="text-sm text-gray-600">{{ report.dietitian.role if report.dietitian else 'Dietitian' }}</p> + <p class="text-sm text-gray-600">License #: {{ report.dietitian.license_number if report.dietitian else 'N/A' }}</p> </div> <div class="text-right"> - <p class="text-sm text-gray-600 mb-2">Report Status:</p> - {% if report.status == 'draft' %} - <p class="text-yellow-600 font-semibold">DRAFT - NOT FOR DISTRIBUTION</p> - {% elif report.status == 'finalized' %} - <p class="text-green-600 font-semibold">FINALIZED</p> + <p class="text-sm text-gray-600">Date of Report: {{ report.report_date.strftime('%B %d, %Y') }}</p> + {% if report.status == 'finalized' %} <p class="text-sm text-gray-600">Date Finalized: {{ report.finalized_at.strftime('%B %d, %Y') if report.finalized_at else 'N/A' }}</p> {% endif %} </div> @@ -261,44 +258,5 @@ </p> </div> {% endif %} - - <!-- Audit Trail Section (Not printed) --> - {% if current_user.is_authorized('view_report_audit') %} - <div class="mt-6 bg-gray-50 border border-gray-200 rounded-lg p-4 no-print"> - <h3 class="text-lg font-semibold text-gray-700 mb-2 flex items-center"> - <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path> - </svg> - Audit Trail - </h3> - <div class="overflow-x-auto"> - <table class="min-w-full"> - <thead> - <tr class="border-b border-gray-200"> - <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th> - <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User</th> - <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Action</th> - <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th> - </tr> - </thead> - <tbody> - {% for audit in audit_trail %} - <tr class="border-b border-gray-200 hover:bg-gray-100"> - <td class="px-4 py-2 text-sm text-gray-500">{{ audit.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td> - <td class="px-4 py-2 text-sm text-gray-900">{{ audit.user.full_name }}</td> - <td class="px-4 py-2 text-sm text-gray-900">{{ audit.action }}</td> - <td class="px-4 py-2 text-sm text-gray-500">{{ audit.details }}</td> - </tr> - {% endfor %} - {% if not audit_trail %} - <tr> - <td colspan="4" class="px-4 py-2 text-sm text-gray-500 text-center">No audit records available</td> - </tr> - {% endif %} - </tbody> - </table> - </div> - </div> - {% endif %} </div> {% endblock %} \ No newline at end of file diff --git a/app/templates/report_form.html b/app/templates/report_form.html index 2c106efd27f40c999319223aed6c482c23d430ef..8fad6037228fcafdc638d8b79e5f230f47e8f1e2 100644 --- a/app/templates/report_form.html +++ b/app/templates/report_form.html @@ -1,6 +1,7 @@ {% extends 'base.html' %} {% block title %}{% if report %}Edit Report{% else %}New Report{% endif %} - CCU HTM{% endblock %} +{% block header %}Edit Report{% endblock %} {% block head %} {{ super() }} @@ -32,7 +33,7 @@ <div class="bg-white shadow-md rounded-lg p-6 mb-8"> <div class="flex justify-between items-center mb-6 pb-4 border-b border-gray-200"> <h1 class="text-2xl font-bold text-gray-800"> - {% if report %}Edit Report #{{ report.id }}{% else %}Create New Report{% endif %} + {% if report %}Editing Report #{{ report.id }}{% else %}Create New Report{% endif %} </h1> <a href="{{ url_for('report.index') }}" class="hover-scale text-gray-600 hover:text-gray-800 font-medium flex items-center transition duration-300"> <svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> @@ -312,6 +313,21 @@ </div> {% endif %} </div> + + <!-- Add your signature section --> + <div> + <label for="signature" class="block text-sm font-medium text-gray-700 mb-1">Digital Signature (Optional)</label> + <input type="file" id="signature" name="signature" accept="image/png,image/jpeg,image/jpg" class="w-full rounded-md border border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50"> + <p class="text-xs text-gray-500 mt-1"> + Upload your signature image. This will appear on finalized reports. + </p> + {% if report and report.dietitian and report.dietitian.signature_image %} + <div class="mt-2 p-2 bg-gray-50 rounded-md"> + <p class="text-sm text-gray-700 mb-1">Current signature:</p> + <img src="{{ url_for('static', filename='img/signatures/' + report.dietitian.signature_image) }}" alt="Current signature" class="h-12"> + </div> + {% endif %} + </div> </div> </div> diff --git a/app/templates/upload.html b/app/templates/upload.html index c6b4c46c89096d20ef9ab45459b46e557ab1726e..cd73edf8b3a2260a894b42e53d2c9d671effca02 100644 --- a/app/templates/upload.html +++ b/app/templates/upload.html @@ -1,22 +1,23 @@ {% extends "base.html" %} -{% block title %}Tải lên CSV - CCU_BVNM{% endblock %} +{% block title %}Upload CSV - CCU_BVNM{% endblock %} +{% block header %} Upload Patients Data {% endblock %} {% block content %} <div class="animate-slide-in"> <div class="md:flex md:items-center md:justify-between mb-6"> <div class="flex-1 min-w-0"> - <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate"> - Tải lên dữ liệu + <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-2xl sm:truncate"> + Upload Data </h2> <p class="mt-1 text-sm text-gray-500"> - Tải lên CSV chứa dữ liệu bệnh nhân và các đo lường sinh lý + Upload CSV containing patient data and physiological measurements </p> </div> </div> <div class="grid grid-cols-1 gap-5 lg:grid-cols-3 mb-8"> - <!-- Phần tải lên --> + <!-- Upload Section --> <div class="lg:col-span-2"> <div class="bg-white shadow rounded-lg transition-all duration-300 hover:shadow-lg animate-fade-in"> <div class="px-4 py-5 sm:p-6"> @@ -24,7 +25,7 @@ {{ form.csrf_token }} <div class="space-y-6"> <div> - <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Tải lên tệp CSV</h3> + <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Upload CSV File</h3> <div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md hover:border-primary-300 transition-colors duration-200" id="dropZone"> <div class="space-y-1 text-center"> <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true"> @@ -32,13 +33,13 @@ </svg> <div class="flex text-sm text-gray-600"> <label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-primary-600 hover:text-primary-500 focus-within:outline-none"> - <span>Tải lên tệp</span> + <span>Upload a file</span> <input id="file-upload" name="file" type="file" accept=".csv" class="sr-only"> </label> - <p class="pl-1">hoặc kéo và thả vào đây</p> + <p class="pl-1">or drag and drop</p> </div> <p class="text-xs text-gray-500"> - CSV tối đa 10MB + CSV up to 10MB </p> </div> </div> @@ -55,16 +56,16 @@ <div class="grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6"> <div class="sm:col-span-3"> - <label for="delimiter" class="block text-sm font-medium text-gray-700">Dấu phân cách</label> + <label for="delimiter" class="block text-sm font-medium text-gray-700">Delimiter</label> <select id="delimiter" name="delimiter" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm rounded-md transition duration-200"> - <option value="comma">Dấu phẩy (,)</option> - <option value="semicolon">Dấu chấm phẩy (;)</option> + <option value="comma">Comma (,)</option> + <option value="semicolon">Semicolon (;)</option> <option value="tab">Tab</option> </select> </div> <div class="sm:col-span-3"> - <label for="encoding" class="block text-sm font-medium text-gray-700">Mã hóa</label> + <label for="encoding" class="block text-sm font-medium text-gray-700">Encoding</label> <!-- name="encoding" is kept for form submission, but it maps to file_encoding in the model --> <select id="encoding" name="encoding" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm rounded-md transition duration-200"> <option value="utf-8">UTF-8</option> @@ -75,16 +76,16 @@ </div> <div> - <label for="description" class="block text-sm font-medium text-gray-700">Mô tả (tùy chọn)</label> + <label for="description" class="block text-sm font-medium text-gray-700">Description (optional)</label> <div class="mt-1"> - <textarea id="description" name="description" rows="3" class="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md transition duration-200" placeholder="Thêm ghi chú về tệp dữ liệu này"></textarea> + <textarea id="description" name="description" rows="3" class="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md transition duration-200" placeholder="Add notes about this data file"></textarea> </div> </div> <div class="flex items-center"> <input id="process_referrals" name="process_referrals" type="checkbox" class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded transition duration-200"> <label for="process_referrals" class="ml-2 block text-sm text-gray-900"> - Tự động xử lý các giới thiệu sau khi tải lên + Automatically process referrals after upload </label> </div> @@ -93,7 +94,7 @@ style="background-color: #9ca3af;" class="w-full inline-flex justify-center items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white transition-all duration-200 ease-in-out"> <i class="fas fa-cloud-upload-alt mr-2"></i> - Tải lên + Upload </button> </div> </div> @@ -102,11 +103,11 @@ </div> </div> - <!-- Phần hướng dẫn --> + <!-- Instructions Section --> <div class="lg:col-span-1"> <div class="bg-white shadow rounded-lg transition-all duration-300 hover:shadow-lg animate-fade-in"> <div class="px-4 py-5 sm:p-6"> - <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Hướng dẫn tải lên (Dữ liệu Bệnh nhân Ban đầu)</h3> + <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Upload Instructions (Initial Patient Data)</h3> <div class="space-y-4"> <div class="flex items-start"> <div class="flex-shrink-0"> @@ -115,7 +116,7 @@ </div> </div> <div class="ml-3 text-sm text-gray-600"> - <p>Đảm bảo tệp CSV của bạn có đúng định dạng với tất cả các cột bắt buộc.</p> + <p>Make sure your CSV file has the correct format with all required columns.</p> </div> </div> <div class="flex items-start"> @@ -125,7 +126,7 @@ </div> </div> <div class="ml-3 text-sm text-gray-600"> - <p>Kiểm tra xem tệp có đầy đủ các cột theo thứ tự: <code>patientID</code>, <code>firstName</code>, <code>lastName</code>, <code>age</code>, <code>gender</code>, <code>height</code>, <code>weight</code>, <code>blood_type</code>, <code>measurementDateTime</code>, <code>temperature</code>, <code>heart_rate</code>, <code>blood_pressure_systolic</code>, ... (và các cột đo lường khác).</p> + <p>Check that your file has all columns in order: <code>patientID</code>, <code>firstName</code>, <code>lastName</code>, <code>age</code>, <code>gender</code>, <code>height</code>, <code>weight</code>, <code>blood_type</code>, <code>measurementDateTime</code>, <code>temperature</code>, <code>heart_rate</code>, <code>blood_pressure_systolic</code>, ... (and other measurement columns).</p> </div> </div> <div class="flex items-start"> @@ -135,7 +136,7 @@ </div> </div> <div class="ml-3 text-sm text-gray-600"> - <p>Chọn đúng dấu phân cách và mã hóa mà tệp của bạn sử dụng.</p> + <p>Select the correct delimiter and encoding that your file uses.</p> </div> </div> <div class="flex items-start"> @@ -145,13 +146,13 @@ </div> </div> <div class="ml-3 text-sm text-gray-600"> - <p>Kích thước tệp không được vượt quá 10MB.</p> + <p>File size must not exceed 10MB.</p> </div> </div> </div> <div class="mt-6 border-t border-gray-200 pt-4"> - <h4 class="text-sm font-medium text-gray-900 mb-2">Mẫu định dạng CSV (Bệnh nhân + Đo lường ban đầu)</h4> + <h4 class="text-sm font-medium text-gray-900 mb-2">Sample CSV Format (Patient + Initial Measurements)</h4> <div class="bg-gray-50 p-3 rounded-md overflow-x-auto"> <code class="text-xs"> patientID,firstName,lastName,age,gender,height,weight,blood_type,measurementDateTime,temperature,heart_rate,blood_pressure_systolic,blood_pressure_diastolic,oxygen_saturation,resp_rate,fio2,tidal_vol,...,bmi,referral<br> @@ -164,7 +165,7 @@ <div class="mt-6"> <a href="{{ url_for('upload.download_template', template_type='new_patients') }}" class="text-primary-600 hover:text-primary-500 text-sm font-medium flex items-center"> - <i class="fas fa-download mr-1"></i> Tải xuống mẫu CSV (Bệnh nhân mới) + <i class="fas fa-download mr-1"></i> Download CSV Template (New Patients) </a> </div> </div> @@ -172,7 +173,7 @@ <div class="bg-white shadow rounded-lg mt-5 transition-all duration-300 hover:shadow-lg animate-fade-in"> <div class="px-4 py-5 sm:p-6"> - <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Lịch sử tải lên</h3> + <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Upload History</h3> <div class="space-y-3"> {% if recent_uploads %} {% for upload in recent_uploads %} @@ -188,29 +189,29 @@ {% elif upload.status == 'failed' %}bg-red-100 text-red-800 {% elif upload.status == 'completed_with_errors' %}bg-orange-100 text-orange-800 {% else %}bg-gray-100 text-gray-800{% endif %}"> - {% if upload.status == 'completed' %}Thành công - {% elif upload.status == 'processing' %}Đang xử lý - {% elif upload.status == 'failed' %}Thất bại - {% elif upload.status == 'completed_with_errors' %}Hoàn thành (có lỗi) + {% if upload.status == 'completed' %}Success + {% elif upload.status == 'processing' %}Processing + {% elif upload.status == 'failed' %}Failed + {% elif upload.status == 'completed_with_errors' %}Completed (with errors) {% else %}{{ upload.status|capitalize }}{% endif %} </span> </div> <div class="mt-1 text-xs text-gray-500"> - {% if upload.total_records %}Đã nhập {{ upload.processed_records }} / {{ upload.total_records }} bản ghi{% endif %} - {% if upload.error_records > 0 %} ({{ upload.error_records }} lỗi){% endif %} + {% if upload.total_records %}Imported {{ upload.processed_records }} / {{ upload.total_records }} records{% endif %} + {% if upload.error_records > 0 %} ({{ upload.error_records }} errors){% endif %} </div> </div> {% endfor %} {% else %} <div class="text-sm text-gray-500 text-center py-4"> - Chưa có lịch sử tải lên + No upload history yet </div> {% endif %} </div> <div class="mt-4"> <a href="{{ url_for('upload.history') }}" class="text-primary-600 hover:text-primary-500 text-sm font-medium"> - Xem tất cả lịch sử + View all history </a> </div> </div> @@ -231,7 +232,7 @@ const removeFile = document.getElementById('removeFile'); const submitBtn = document.getElementById('submitBtn'); - // Xử lý kéo và thả + // Drag and drop handling ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, preventDefaults, false); }); @@ -276,13 +277,13 @@ function handleFiles(file) { if (file.type !== 'text/csv' && !file.name.endsWith('.csv')) { - alert('Vui lòng chọn tệp CSV'); + alert('Please select a CSV file'); resetFileInput(); return; } if (file.size > 10 * 1024 * 1024) { - alert('Kích thước tệp quá lớn. Tối đa 10MB.'); + alert('File size too large. Maximum is 10MB.'); resetFileInput(); return; } @@ -308,16 +309,16 @@ removeFile.addEventListener('click', resetFileInput); - // Xử lý submit form + // Form submission handling const uploadForm = document.getElementById('uploadForm'); uploadForm.addEventListener('submit', function(e) { if (fileInput.files.length === 0) { e.preventDefault(); - alert('Vui lòng chọn tệp để tải lên'); + alert('Please select a file to upload'); return; } - // Hiển thị loader + // Show loader const loader = document.getElementById('pageLoader'); if (loader) { loader.style.display = 'flex'; diff --git a/app/templates/upload_history.html b/app/templates/upload_history.html index c5d083a66f65eb9cc5e3203d40c4308c79565d0e..8fe210d294ec6bcb36cb4bccb48f3ef1a3212d22 100644 --- a/app/templates/upload_history.html +++ b/app/templates/upload_history.html @@ -1,21 +1,21 @@ {% extends "base.html" %} -{% block title %}Lịch sử Tải lên - CCU_BVNM{% endblock %} +{% block title %}Upload History - CCU_BVNM{% endblock %} {% block content %} <div class="animate-slide-in"> <div class="md:flex md:items-center md:justify-between mb-6"> <div class="flex-1 min-w-0"> <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate"> - Lịch sử Tải lên + Upload History </h2> <p class="mt-1 text-sm text-gray-500"> - Xem lại các tệp đã tải lên và trạng thái xử lý. + Review uploaded files and their processing status. </p> </div> <div class="mt-4 flex md:mt-0 md:ml-4"> <a href="{{ url_for('upload.index') }}" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition duration-200"> - <i class="fas fa-upload mr-2"></i> Tải lên tệp mới + <i class="fas fa-upload mr-2"></i> Upload new file </a> </div> </div> @@ -25,13 +25,13 @@ <table class="min-w-full divide-y divide-gray-200"> <thead class="bg-gray-50"> <tr> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tên tệp gốc</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ngày tải lên</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Người tải</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Trạng thái</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tổng số dòng</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Đã xử lý</th> - <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Lỗi</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Original Filename</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Upload Date</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Uploaded By</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total Rows</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Processed</th> + <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Errors</th> <th scope="col" class="relative px-6 py-3"> <span class="sr-only">Actions</span> </th> @@ -46,13 +46,13 @@ <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ upload.uploader.email if upload.uploader else 'N/A' }}</td> <td class="px-6 py-4 whitespace-nowrap text-sm"> {% if upload.status == 'completed' %} - <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Hoàn thành</span> + <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Completed</span> {% elif upload.status == 'processing' %} - <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">Đang xử lý</span> + <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800">Processing</span> {% elif upload.status == 'failed' %} - <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Thất bại</span> + <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">Failed</span> {% elif upload.status == 'completed_with_errors' %} - <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-orange-100 text-orange-800">Hoàn thành (có lỗi)</span> + <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-orange-100 text-orange-800">Completed (with errors)</span> {% else %} <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">{{ upload.status|capitalize }}</span> {% endif %} @@ -66,12 +66,12 @@ {# Nút Xem trước nếu cần #} {# <a href="{{ url_for('upload.preview', file_id=upload.id) }}" class="text-indigo-600 hover:text-indigo-900">Preview</a> #} {% if upload.error_records > 0 and upload.error_details %} - <button type="button" class="text-yellow-600 hover:text-yellow-900 view-errors" data-errors='{{ upload.error_details }}'>Xem lỗi</button> + <button type="button" class="text-yellow-600 hover:text-yellow-900 view-errors" data-errors='{{ upload.error_details }}'>View errors</button> {% endif %} - <a href="{{ url_for('upload.download', file_id=upload.id) }}" class="text-blue-600 hover:text-blue-900">Tải xuống</a> - <form action="{{ url_for('upload.delete', file_id=upload.id) }}" method="POST" class="inline" onsubmit="return confirm('Bạn có chắc chắn muốn xóa tệp này và lịch sử của nó?');"> + <a href="{{ url_for('upload.download', file_id=upload.id) }}" class="text-blue-600 hover:text-blue-900">Download</a> + <form action="{{ url_for('upload.delete', file_id=upload.id) }}" method="POST" class="inline" onsubmit="return confirm('Are you sure you want to delete this file and its history?');"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> - <button type="submit" class="text-red-600 hover:text-red-900">Xóa</button> + <button type="submit" class="text-red-600 hover:text-red-900">Delete</button> </form> </td> </tr> @@ -79,7 +79,7 @@ {% else %} <tr> <td colspan="8" class="px-6 py-10 text-center text-sm text-gray-500"> - Chưa có lịch sử tải lên nào. + No upload history available. </td> </tr> {% endif %} @@ -92,30 +92,30 @@ <nav class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6" aria-label="Pagination"> <div class="hidden sm:block"> <p class="text-sm text-gray-700"> - Hiển thị + Showing <span class="font-medium">{{ uploads.offset + 1 }}</span> - đến + to <span class="font-medium">{{ uploads.offset + uploads.items|length }}</span> - của + of <span class="font-medium">{{ uploads.total }}</span> - kết quả + results </p> </div> <div class="flex-1 flex justify-between sm:justify-end"> <a href="{{ url_for('upload.history', page=uploads.prev_num) if uploads.has_prev else '#' }}" class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 {% if not uploads.has_prev %}opacity-50 cursor-not-allowed{% endif %}"> - Trước + Previous </a> <a href="{{ url_for('upload.history', page=uploads.next_num) if uploads.has_next else '#' }}" class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 {% if not uploads.has_next %}opacity-50 cursor-not-allowed{% endif %}"> - Sau + Next </a> </div> </nav> {% endif %} </div> - <!-- Modal để hiển thị lỗi --> + <!-- Modal to display errors --> <div id="errorModal" class="fixed z-10 inset-0 overflow-y-auto hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div> @@ -128,7 +128,7 @@ </div> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full"> <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title"> - Chi tiết lỗi xử lý CSV + CSV Processing Error Details </h3> <div class="mt-2"> <div class="text-sm text-gray-500 max-h-96 overflow-y-auto"> @@ -140,7 +140,7 @@ </div> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" id="closeErrorModal"> - Đóng + Close </button> </div> </div> @@ -165,13 +165,13 @@ document.addEventListener('DOMContentLoaded', () => { const errorsJson = button.getAttribute('data-errors'); const errors = JSON.parse(errorsJson); - // Kiểm tra xem lỗi có được tóm tắt không + // Check if errors are summarized if (errors.summary) { - // Đây là dạng tóm tắt lỗi + // This is a summary format let formattedSummary = `${errors.summary}\n\n`; if (errors.sample_patients && errors.sample_patients.length) { - formattedSummary += `Ví dụ các ID bệnh nhân đã tồn tại: ${errors.sample_patients.join(', ')}\n\n`; + formattedSummary += `Example of existing patient IDs: ${errors.sample_patients.join(', ')}\n\n`; } if (errors.message) { @@ -180,25 +180,25 @@ document.addEventListener('DOMContentLoaded', () => { errorDetailsContent.textContent = formattedSummary; } else { - // Dạng lỗi chi tiết theo từng dòng + // Detailed per-row errors let formattedErrors = ""; if (Array.isArray(errors)) { errors.forEach(err => { if (err.summary) { - // Đây là thông báo tóm tắt ở cuối + // This is a summary message at the end formattedErrors += `\n${err.summary}\n`; } else { - formattedErrors += `Dòng ${err.row}: ${err.error}\n`; + formattedErrors += `Row ${err.row}: ${err.error}\n`; if(err.data) { - formattedErrors += ` Dữ liệu: ${JSON.stringify(err.data)}\n\n`; + formattedErrors += ` Data: ${JSON.stringify(err.data)}\n\n`; } else { formattedErrors += '\n'; } } }); } else { - formattedErrors = "Định dạng lỗi không đúng."; + formattedErrors = "Invalid error format."; } errorDetailsContent.textContent = formattedErrors; @@ -207,7 +207,7 @@ document.addEventListener('DOMContentLoaded', () => { errorModal.classList.remove('hidden'); } catch (e) { console.error("Error parsing or displaying errors:", e); - errorDetailsContent.textContent = "Không thể hiển thị chi tiết lỗi."; + errorDetailsContent.textContent = "Could not display error details."; errorModal.classList.remove('hidden'); } }); @@ -217,7 +217,7 @@ document.addEventListener('DOMContentLoaded', () => { errorModal.classList.add('hidden'); }); - // Đóng modal khi click bên ngoài + // Close modal when clicking outside errorModal.addEventListener('click', (event) => { if (event.target === errorModal) { errorModal.classList.add('hidden'); diff --git a/fix_user_password.py b/fix_user_password.py deleted file mode 100644 index 442695718a6e270d35c9411446a893d48d53ec44..0000000000000000000000000000000000000000 --- a/fix_user_password.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from flask_bcrypt import Bcrypt -from flask import Flask -import mysql.connector -import sys - -def fix_user_password(user_email, new_password): - """Sửa mật khẩu tài khoản người dùng trực tiếp trong database""" - - # Tạo Flask app tạm thời để sử dụng bcrypt - app = Flask(__name__) - bcrypt = Bcrypt(app) - - # Tạo hash mật khẩu mới - password_hash = bcrypt.generate_password_hash(new_password).decode('utf-8') - - print(f"Tạo hash mật khẩu mới: {password_hash}") - - # Thông tin kết nối database - lấy từ config Flask - db_host = 'localhost' - db_user = 'root' - db_password = 'MinhDZ3009' - db_name = 'ccu' - - try: - # Kết nối tới MySQL - print(f"Đang kết nối đến MySQL với user '{db_user}' tại {db_host}...") - connection = mysql.connector.connect( - host=db_host, - user=db_user, - password=db_password, - database=db_name - ) - - cursor = connection.cursor() - - # Cập nhật mật khẩu cho tài khoản người dùng - print(f"Đang cập nhật mật khẩu cho {user_email}...") - update_query = "UPDATE users SET password_hash = %s WHERE email = %s" - cursor.execute(update_query, (password_hash, user_email)) - - # Kiểm tra xem có bao nhiêu dòng bị ảnh hưởng - affected_rows = cursor.rowcount - - if affected_rows > 0: - print(f"Cập nhật thành công! {affected_rows} tài khoản đã được cập nhật.") - connection.commit() - else: - print(f"Không tìm thấy tài khoản {user_email}!") - - # Đóng kết nối - cursor.close() - connection.close() - - if affected_rows > 0: - print(f"\nMật khẩu cho {user_email} đã được đặt lại thành: {new_password}") - - return affected_rows > 0 - - except mysql.connector.Error as err: - print(f"Lỗi MySQL: {err}") - return False - except Exception as e: - print(f"Lỗi: {e}") - return False - -if __name__ == "__main__": - print("Tiện ích sửa mật khẩu người dùng CCU HTM") - print("---------------------------------------") - - # Lấy thông tin từ người dùng - user_email = input("Nhập email của tài khoản cần sửa: ") - new_password = input("Nhập mật khẩu mới: ") - - result = fix_user_password(user_email, new_password) - - if result: - print("\nHoàn tất! Mật khẩu đã được đặt lại.") - sys.exit(0) - else: - print("\nLỗi! Không thể đặt lại mật khẩu.") - sys.exit(1) \ No newline at end of file