From 04892ffb85248367248c27a97f132890ad4b0a85 Mon Sep 17 00:00:00 2001 From: Faheed Date: Wed, 14 Jan 2026 11:26:44 +0300 Subject: [PATCH] update the complaint and inquiry creation for the source user --- ...MPLAINT_FORM_DJANGO_FORM_IMPLEMENTATION.md | 106 +++++++ apps/complaints/forms.py | 200 ++++++++++++- apps/complaints/ui_views.py | 174 +++++------ templates/complaints/complaint_detail.html | 9 +- templates/complaints/complaint_form.html | 276 +++++++----------- templates/complaints/inquiry_detail.html | 8 +- templates/complaints/inquiry_form.html | 217 +++++--------- 7 files changed, 589 insertions(+), 401 deletions(-) create mode 100644 apps/complaints/COMPLAINT_FORM_DJANGO_FORM_IMPLEMENTATION.md diff --git a/apps/complaints/COMPLAINT_FORM_DJANGO_FORM_IMPLEMENTATION.md b/apps/complaints/COMPLAINT_FORM_DJANGO_FORM_IMPLEMENTATION.md new file mode 100644 index 0000000..95fa52b --- /dev/null +++ b/apps/complaints/COMPLAINT_FORM_DJANGO_FORM_IMPLEMENTATION.md @@ -0,0 +1,106 @@ +# Complaint Form Django Form Implementation + +## Summary + +The complaint form has been successfully refactored to use Django's built-in form rendering instead of manual HTML fields and complex AJAX calls. + +## Changes Made + +### 1. New `ComplaintForm` in `apps/complaints/forms.py` + +**Fields Included:** +- `patient` - Required dropdown, filtered by user hospital +- `hospital` - Required dropdown, pre-filtered by user permissions +- `department` - Optional dropdown, filtered by selected hospital +- `staff` - Optional dropdown, filtered by selected department +- `encounter_id` - Optional text field +- `description` - Required textarea + +**Fields Removed (AI will determine):** +- `category` - AI will determine automatically +- `subcategory` - AI will determine automatically +- `source` - Set to 'staff' for authenticated users + +**Features:** +- User permission filtering (PX admins see all, hospital users see only their hospital) +- Dependent queryset initialization (departments load when hospital is pre-selected) +- Full Django form validation +- Clean error messages + +### 2. Updated `complaint_create` View in `apps/complaints/ui_views.py` + +**Changes:** +- Uses `ComplaintForm(request.POST, user=request.user)` for form handling +- Handles `form.is_valid()` validation +- Sets AI defaults before saving: + - `title = 'Complaint'` (AI will generate) + - `category = None` (AI will determine) + - `subcategory = ''` (AI will determine) + - `source = 'staff'` (default for authenticated users) + - `priority = 'medium'` (AI will update) + - `severity = 'medium'` (AI will update) +- Creates initial update record +- Triggers AI analysis via Celery +- Logs audit trail +- Handles hospital parameter for form pre-selection + +### 3. Updated Template `templates/complaints/complaint_form.html` + +**Changes:** +- Uses Django form rendering: `{{ form.field }}` +- Removed all manual HTML input fields +- Removed complex AJAX endpoints +- Kept minimal JavaScript: + - Hospital change → reload form with hospital parameter + - Department change → load staff via `/complaints/ajax/physicians/` + - Form validation + +**Removed AJAX Endpoints:** +- `/api/organizations/departments/` - No longer needed +- `/api/organizations/patients/` - No longer needed +- `/complaints/ajax/get-staff-by-department/` - Changed to `/complaints/ajax/physicians/` + +**Structure:** +- Patient Information section +- Organization section (Hospital, Department, Staff) +- Complaint Details section (Description) +- AI Classification info alert +- SLA Information alert +- Action buttons + +## Benefits + +1. **Simpler Code** - Django handles form rendering and validation +2. **Better Error Handling** - Form validation with clear error messages +3. **Less JavaScript** - Only minimal JS for dependent dropdowns +4. **Cleaner Separation** - Business logic in forms, presentation in templates +5. **User Permissions** - Automatic filtering based on user role +6. **AI Integration** - Category, subcategory, severity, and priority determined by AI + +## Testing Checklist + +- [ ] Form loads correctly for PX admin users +- [ ] Form loads correctly for hospital users (filtered to their hospital) +- [ ] Hospital dropdown pre-fills when hospital parameter in URL +- [ ] Department dropdown populates when hospital selected +- [ ] Staff dropdown populates when department selected +- [ ] Form validation works for required fields +- [ ] Complaint saves successfully +- [ ] AI analysis task is triggered after creation +- [ ] User is redirected to complaint detail page +- [ ] Back links work for both regular and source users + +## Related Files + +- `apps/complaints/forms.py` - ComplaintForm definition +- `apps/complaints/ui_views.py` - complaint_create view +- `templates/complaints/complaint_form.html` - Form template +- `apps/complaints/urls.py` - URL configuration +- `apps/complaints/tasks.py` - AI analysis task + +## Notes + +- The form uses a simple reload approach for hospital selection to keep JavaScript minimal +- Staff loading still uses AJAX because it's a common pattern and provides good UX +- All AI-determined fields are hidden from the user interface +- The form is bilingual-ready using Django's translation system diff --git a/apps/complaints/forms.py b/apps/complaints/forms.py index 7cb2858..7ed3f8a 100644 --- a/apps/complaints/forms.py +++ b/apps/complaints/forms.py @@ -12,9 +12,10 @@ from apps.complaints.models import ( ComplaintCategory, ComplaintSource, ComplaintStatus, + Inquiry ) from apps.core.models import PriorityChoices, SeverityChoices -from apps.organizations.models import Department, Hospital +from apps.organizations.models import Department, Hospital, Patient, Staff class MultiFileInput(forms.FileInput): @@ -32,7 +33,7 @@ class MultiFileInput(forms.FileInput): def value_from_datadict(self, data, files, name): """ - Get all uploaded files for the given field name. + Get all uploaded files for a given field name. Returns a list of uploaded files instead of a single file. """ @@ -249,6 +250,201 @@ class PublicComplaintForm(forms.ModelForm): return cleaned_data +class ComplaintForm(forms.ModelForm): + """ + Form for creating complaints by authenticated users. + + Uses Django form rendering with minimal JavaScript for dependent dropdowns. + Category, subcategory, and source are omitted - AI will determine them. + """ + + patient = forms.ModelChoiceField( + label=_("Patient"), + queryset=Patient.objects.filter(status='active'), + empty_label=_("Select Patient"), + required=True, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'patientSelect'}) + ) + + hospital = forms.ModelChoiceField( + label=_("Hospital"), + queryset=Hospital.objects.filter(status='active'), + empty_label=_("Select Hospital"), + required=True, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'}) + ) + + department = forms.ModelChoiceField( + label=_("Department"), + queryset=Department.objects.none(), + empty_label=_("Select Department"), + required=False, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'departmentSelect'}) + ) + + staff = forms.ModelChoiceField( + label=_("Staff"), + queryset=Staff.objects.none(), + empty_label=_("Select Staff"), + required=False, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'staffSelect'}) + ) + + encounter_id = forms.CharField( + label=_("Encounter ID"), + required=False, + widget=forms.TextInput(attrs={'class': 'form-control', + 'placeholder': _('Optional encounter/visit ID')}) + ) + + description = forms.CharField( + label=_("Description"), + required=True, + widget=forms.Textarea(attrs={'class': 'form-control', + 'rows': 6, + 'placeholder': _('Detailed description of complaint...')}) + ) + + class Meta: + model = Complaint + fields = ['patient', 'hospital', 'department', 'staff', + 'encounter_id', 'description'] + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + super().__init__(*args, **kwargs) + + # Filter hospital and patient by user permissions + if user and not user.is_px_admin() and user.hospital: + self.fields['hospital'].queryset = Hospital.objects.filter( + id=user.hospital.id + ) + self.fields['patient'].queryset = Patient.objects.filter( + primary_hospital=user.hospital, + status='active' + ) + + # Check for hospital selection in both initial data and POST data + # This is needed for validation to work correctly + hospital_id = None + if 'hospital' in self.data: + hospital_id = self.data.get('hospital') + elif 'hospital' in self.initial: + hospital_id = self.initial.get('hospital') + + if hospital_id: + # Filter departments based on selected hospital + self.fields['department'].queryset = Department.objects.filter( + hospital_id=hospital_id, + status='active' + ).order_by('name') + + # Filter staff based on selected hospital + self.fields['staff'].queryset = Staff.objects.filter( + hospital_id=hospital_id, + status='active' + ).order_by('first_name', 'last_name') + + +class InquiryForm(forms.ModelForm): + """ + Form for creating inquiries by authenticated users. + + Similar to ComplaintForm - supports patient search, department filtering, + and proper field validation with AJAX support. + """ + + patient = forms.ModelChoiceField( + label=_("Patient (Optional)"), + queryset=Patient.objects.filter(status='active'), + empty_label=_("Select Patient"), + required=False, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'patientSelect'}) + ) + + hospital = forms.ModelChoiceField( + label=_("Hospital"), + queryset=Hospital.objects.filter(status='active'), + empty_label=_("Select Hospital"), + required=True, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'hospitalSelect'}) + ) + + department = forms.ModelChoiceField( + label=_("Department (Optional)"), + queryset=Department.objects.none(), + empty_label=_("Select Department"), + required=False, + widget=forms.Select(attrs={'class': 'form-select', 'id': 'departmentSelect'}) + ) + + category = forms.ChoiceField( + label=_("Inquiry Type"), + choices=[ + ('general', 'General Inquiry'), + ('appointment', 'Appointment Related'), + ('billing', 'Billing & Insurance'), + ('medical_records', 'Medical Records'), + ('pharmacy', 'Pharmacy'), + ('insurance', 'Insurance'), + ('feedback', 'Feedback'), + ('other', 'Other'), + ], + required=True, + widget=forms.Select(attrs={'class': 'form-control'}) + ) + + subject = forms.CharField( + label=_("Subject"), + max_length=200, + required=True, + widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Brief subject')}) + ) + + message = forms.CharField( + label=_("Message"), + required=True, + widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'placeholder': _('Describe your inquiry')}) + ) + + # Contact info for inquiries without patient + contact_name = forms.CharField(label=_("Contact Name"), max_length=200, required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) + contact_phone = forms.CharField(label=_("Contact Phone"), max_length=20, required=False, widget=forms.TextInput(attrs={'class': 'form-control'})) + contact_email = forms.EmailField(label=_("Contact Email"), required=False, widget=forms.EmailInput(attrs={'class': 'form-control'})) + + class Meta: + model = Inquiry + fields = ['patient', 'hospital', 'department', 'subject', 'message', + 'contact_name', 'contact_phone', 'contact_email'] + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + super().__init__(*args, **kwargs) + + # Filter hospital by user permissions + if user and not user.is_px_admin() and user.hospital: + self.fields['hospital'].queryset = Hospital.objects.filter( + id=user.hospital.id + ) + + # Check for hospital selection in both initial data and POST data + hospital_id = None + if 'hospital' in self.data: + hospital_id = self.data.get('hospital') + elif 'hospital' in self.initial: + hospital_id = self.initial.get('hospital') + + if hospital_id: + # Filter departments based on selected hospital + self.fields['department'].queryset = Department.objects.filter( + hospital_id=hospital_id, + status='active' + ).order_by('name') + + + + + class PublicInquiryForm(forms.Form): """Public inquiry submission form (simpler, for general questions)""" diff --git a/apps/complaints/ui_views.py b/apps/complaints/ui_views.py index b58c729..914b8a5 100644 --- a/apps/complaints/ui_views.py +++ b/apps/complaints/ui_views.py @@ -13,6 +13,7 @@ from django.views.decorators.http import require_http_methods from apps.accounts.models import User from apps.core.services import AuditService from apps.organizations.models import Department, Hospital, Staff +from apps.px_sources.models import SourceUser, PXSource from .models import ( Complaint, @@ -177,6 +178,10 @@ def complaint_detail(request, pk): - Linked PX actions - Workflow actions (assign, status change, add note) """ + from apps.px_sources.models import SourceUser + source_user = SourceUser.objects.filter(user=request.user).first() + base_layout = 'layouts/source_user_base.html' if source_user else 'layouts/base.html' + complaint = get_object_or_404( Complaint.objects.select_related( 'patient', 'hospital', 'department', 'staff', @@ -241,6 +246,8 @@ def complaint_detail(request, pk): 'status_choices': ComplaintStatus.choices, 'can_edit': user.is_px_admin() or user.is_hospital_admin(), 'hospital_departments': hospital_departments, + 'base_layout': base_layout, + 'source_user': source_user, } return render(request, 'complaints/complaint_detail.html', context) @@ -250,6 +257,8 @@ def complaint_detail(request, pk): @require_http_methods(["GET", "POST"]) def complaint_create(request): """Create new complaint with AI-powered classification""" + from apps.complaints.forms import ComplaintForm + # Determine base layout based on user type from apps.px_sources.models import SourceUser source_user = SourceUser.objects.filter(user=request.user).first() @@ -257,47 +266,49 @@ def complaint_create(request): if request.method == 'POST': # Handle form submission + form = ComplaintForm(request.POST, user=request.user) + + if not form.is_valid(): + # Debug: print form errors + print("Form validation errors:", form.errors) + messages.error(request, f"Please correct the errors: {form.errors}") + context = { + 'form': form, + 'base_layout': base_layout, + 'source_user': source_user, + } + return render(request, 'complaints/complaint_form.html', context) + try: - from apps.organizations.models import Patient - - # Get form data - patient_id = request.POST.get('patient_id') - hospital_id = request.POST.get('hospital_id') - department_id = request.POST.get('department_id', None) - staff_id = request.POST.get('staff_id', None) - - description = request.POST.get('description') - category_id = request.POST.get('category') - subcategory_id = request.POST.get('subcategory', '') - source = request.POST.get('source') - encounter_id = request.POST.get('encounter_id', '') - - # Validate required fields - if not all([patient_id, hospital_id, description, category_id, source]): - messages.error(request, "Please fill in all required fields.") - return redirect('complaints:complaint_create') - - # Get category and subcategory objects - category = ComplaintCategory.objects.get(id=category_id) - subcategory_obj = None - if subcategory_id: - subcategory_obj = ComplaintCategory.objects.get(id=subcategory_id) - # Create complaint with AI defaults - complaint = Complaint.objects.create( - patient_id=patient_id, - hospital_id=hospital_id, - department_id=department_id if department_id else None, - staff_id=staff_id if staff_id else None, - title='Complaint', # AI will generate title - description=description, - category=category, - subcategory=subcategory_obj.code if subcategory_obj else '', - priority='medium', # AI will update - severity='medium', # AI will update - source=source, - encounter_id=encounter_id, - ) + complaint = form.save(commit=False) + + # Set AI-determined defaults + complaint.title = 'Complaint' # AI will generate title + # category can be None, AI will determine it + complaint.subcategory = '' # AI will determine + + # Set source from logged-in source user + if source_user and source_user.source: + complaint.source = source_user.source + else: + # Fallback: get or create a 'staff' source + from apps.px_sources.models import PXSource + try: + source_obj = PXSource.objects.get(code='staff') + except PXSource.DoesNotExist: + source_obj = PXSource.objects.create( + code='staff', + name='Staff', + description='Complaints submitted by staff members' + ) + complaint.source = source_obj + + complaint.priority = 'medium' # AI will update + complaint.severity = 'medium' # AI will update + complaint.created_by = request.user + + complaint.save() # Create initial update ComplaintUpdate.objects.create( @@ -307,7 +318,7 @@ def complaint_create(request): created_by=request.user ) - # Trigger AI analysis in the background using Celery + # Trigger AI analysis in background using Celery from apps.complaints.tasks import analyze_complaint_with_ai analyze_complaint_with_ai.delay(str(complaint.id)) @@ -318,9 +329,8 @@ def complaint_create(request): user=request.user, content_object=complaint, metadata={ - 'category': category.name_en, 'severity': complaint.severity, - 'patient_mrn': complaint.patient.mrn, + 'patient_mrn': complaint.patient.mrn if complaint.patient else None, 'ai_analysis_pending': True } ) @@ -328,20 +338,21 @@ def complaint_create(request): messages.success(request, f"Complaint #{complaint.id} created successfully. AI is analyzing and classifying the complaint.") return redirect('complaints:complaint_detail', pk=complaint.id) - except ComplaintCategory.DoesNotExist: - messages.error(request, "Selected category not found.") - return redirect('complaints:complaint_create') except Exception as e: messages.error(request, f"Error creating complaint: {str(e)}") return redirect('complaints:complaint_create') # GET request - show form - hospitals = Hospital.objects.filter(status='active') - if not request.user.is_px_admin() and request.user.hospital: - hospitals = hospitals.filter(id=request.user.hospital.id) + # Check for hospital parameter from URL (for pre-selection) + initial_data = {} + hospital_id = request.GET.get('hospital') + if hospital_id: + initial_data['hospital'] = hospital_id + + form = ComplaintForm(user=request.user, initial=initial_data) context = { - 'hospitals': hospitals, + 'form': form, 'base_layout': base_layout, 'source_user': source_user, } @@ -901,6 +912,10 @@ def inquiry_detail(request, pk): - Attachments management - Workflow actions (assign, status change, add note, respond) """ + from apps.px_sources.models import SourceUser + source_user = SourceUser.objects.filter(user=request.user).first() + base_layout = 'layouts/source_user_base.html' if source_user else 'layouts/base.html' + inquiry = get_object_or_404( Inquiry.objects.select_related( 'patient', 'hospital', 'department', 'assigned_to', 'responded_by' @@ -947,6 +962,8 @@ def inquiry_detail(request, pk): 'assignable_users': assignable_users, 'status_choices': status_choices, 'can_edit': user.is_px_admin() or user.is_hospital_admin(), + 'base_layout': base_layout, + 'source_user': source_user, } return render(request, 'complaints/inquiry_detail.html', context) @@ -957,46 +974,38 @@ def inquiry_detail(request, pk): def inquiry_create(request): """Create new inquiry""" from .models import Inquiry + from .forms import InquiryForm from apps.organizations.models import Patient + from apps.px_sources.models import SourceUser, PXSource # Determine base layout based on user type - from apps.px_sources.models import SourceUser source_user = SourceUser.objects.filter(user=request.user).first() base_layout = 'layouts/source_user_base.html' if source_user else 'layouts/base.html' if request.method == 'POST': + # Handle form submission + form = InquiryForm(request.POST, user=request.user) + + if not form.is_valid(): + messages.error(request, f"Please correct the errors: {form.errors}") + context = { + 'form': form, + 'base_layout': base_layout, + 'source_user': source_user, + } + return render(request, 'complaints/inquiry_form.html', context) + try: - # Get form data - patient_id = request.POST.get('patient_id', None) - hospital_id = request.POST.get('hospital_id') - department_id = request.POST.get('department_id', None) - - subject = request.POST.get('subject') - message = request.POST.get('message') - category = request.POST.get('category') - - # Contact info (if no patient) - contact_name = request.POST.get('contact_name', '') - contact_phone = request.POST.get('contact_phone', '') - contact_email = request.POST.get('contact_email', '') - - # Validate required fields - if not all([hospital_id, subject, message, category]): - messages.error(request, "Please fill in all required fields.") - return redirect('complaints:inquiry_create') - - # Create inquiry - inquiry = Inquiry.objects.create( - patient_id=patient_id if patient_id else None, - hospital_id=hospital_id, - department_id=department_id if department_id else None, - subject=subject, - message=message, - category=category, - contact_name=contact_name, - contact_phone=contact_phone, - contact_email=contact_email, - ) + # Save inquiry + inquiry = form.save(commit=False) + + # Set source for source users + source_user = SourceUser.objects.filter(user=request.user).first() + if source_user: + inquiry.source = source_user.source + inquiry.created_by = request.user + + inquiry.save() # Log audit AuditService.log_event( @@ -1015,12 +1024,13 @@ def inquiry_create(request): return redirect('complaints:inquiry_create') # GET request - show form + form = InquiryForm(user=request.user) hospitals = Hospital.objects.filter(status='active') if not request.user.is_px_admin() and request.user.hospital: hospitals = hospitals.filter(id=request.user.hospital.id) context = { - 'hospitals': hospitals, + 'form': form, 'base_layout': base_layout, 'source_user': source_user, } diff --git a/templates/complaints/complaint_detail.html b/templates/complaints/complaint_detail.html index 4b4499e..c0f9d39 100644 --- a/templates/complaints/complaint_detail.html +++ b/templates/complaints/complaint_detail.html @@ -1,4 +1,4 @@ -{% extends "layouts/base.html" %} +{% extends base_layout %} {% load i18n %} {% load static %} {% load math %} @@ -113,12 +113,19 @@ {% endblock %} {% block content %} +
+ {% if source_user %} + + {{ _("Back to My Complaints")}} + + {% else %} {{ _("Back to Complaints")}} + {% endif %}
diff --git a/templates/complaints/complaint_form.html b/templates/complaints/complaint_form.html index 8b0fa98..bf3e1d3 100644 --- a/templates/complaints/complaint_form.html +++ b/templates/complaints/complaint_form.html @@ -48,7 +48,7 @@

{{ _("File a new patient complaint with SLA tracking")}}

-
+ {% csrf_token %}
@@ -61,17 +61,24 @@
- - - {{ _("Search by MRN or name")}} + + {{ form.patient }} + {% if form.patient.errors %} +
+ {% for error in form.patient.errors %} + {{ error }} + {% endfor %} +
+ {% endif %}
- - + + {{ form.encounter_id }}
@@ -84,36 +91,51 @@
- - +
+ {% endif %}
- - + + {{ form.department }} + {% if form.department.errors %} +
+ {% for error in form.department.errors %} + {{ error }} + {% endfor %} +
+ {% endif %}
- - + + {{ form.staff }} + {% if form.staff.errors %} +
+ {% for error in form.staff.errors %} + {{ error }} + {% endfor %} +
+ {% endif %}
- - -
@@ -121,9 +143,17 @@
- - + + {{ form.description }} + {% if form.description.errors %} +
+ {% for error in form.description.errors %} + {{ error }} + {% endfor %} +
+ {% endif %}
@@ -133,7 +163,23 @@
- {{ _("SLA Information")}} + {{ _("AI Classification")}} +
+

+ {{ _("AI will automatically analyze and classify your complaint:")}} +

+
    +
  • {{ _("Title") }}: {{ _("AI-generated title")}}
  • +
  • {{ _("Category") }}: {{ _("AI-determined category")}}
  • +
  • {{ _("Severity") }}: {{ _("AI-calculated severity")}}
  • +
  • {{ _("Priority") }}: {{ _("AI-calculated priority")}}
  • +
+
+ + +
+
+ {{ _("SLA Information")}}

{{ _("SLA deadline will be automatically calculated based on severity")}}: @@ -173,157 +219,49 @@ document.addEventListener('DOMContentLoaded', function() { const hospitalSelect = document.getElementById('hospitalSelect'); const departmentSelect = document.getElementById('departmentSelect'); const staffSelect = document.getElementById('staffSelect'); - const categorySelect = document.getElementById('categorySelect'); - const subcategorySelect = document.getElementById('subcategorySelect'); - const patientSelect = document.getElementById('patientSelect'); - // Get current language - const currentLang = document.documentElement.lang || 'en'; - - // Hospital change handler - hospitalSelect.addEventListener('change', function() { - const hospitalId = this.value; - - // Clear dependent dropdowns - departmentSelect.innerHTML = ''; - staffSelect.innerHTML = ''; - categorySelect.innerHTML = ''; - subcategorySelect.innerHTML = ''; - - if (hospitalId) { - // Load departments - fetch(`/api/organizations/departments/?hospital=${hospitalId}`) - .then(response => response.json()) - .then(data => { - departmentSelect.innerHTML = ''; - data.results.forEach(dept => { - const option = document.createElement('option'); - option.value = dept.id; - const deptName = currentLang === 'ar' && dept.name_ar ? dept.name_ar : dept.name_en; - option.textContent = deptName; - departmentSelect.appendChild(option); - }); - }) - .catch(error => { - console.error('Error loading departments:', error); - departmentSelect.innerHTML = ''; - }); - - // Load categories (using public API endpoint) - fetch(`/complaints/public/api/load-categories/?hospital_id=${hospitalId}`) - .then(response => response.json()) - .then(data => { - categorySelect.innerHTML = ''; - data.categories.forEach(cat => { - // Only show parent categories (no parent_id) - if (!cat.parent_id) { - const option = document.createElement('option'); - option.value = cat.id; - option.dataset.code = cat.code; - const catName = currentLang === 'ar' && cat.name_ar ? cat.name_ar : cat.name_en; - option.textContent = catName; - categorySelect.appendChild(option); - } - }); - }) - .catch(error => { - console.error('Error loading categories:', error); - categorySelect.innerHTML = ''; - }); - } else { - categorySelect.innerHTML = ''; - } - }); + // Hospital change handler - reload form with selected hospital + if (hospitalSelect) { + hospitalSelect.addEventListener('change', function() { + const hospitalId = this.value; + const form = document.getElementById('complaintForm'); + const actionUrl = form.action; + + // Create URL with hospital_id parameter + const url = new URL(actionUrl, window.location); + url.searchParams.set('hospital', hospitalId); + + // Reload form with selected hospital + window.location.href = url.toString(); + }); + } // Department change handler - load staff - departmentSelect.addEventListener('change', function() { - const departmentId = this.value; + if (departmentSelect) { + departmentSelect.addEventListener('change', function() { + const departmentId = this.value; - // Clear staff dropdown - staffSelect.innerHTML = ''; + // Clear staff dropdown + staffSelect.innerHTML = ''; - if (departmentId) { - // Load staff (filtered by department) - fetch(`/complaints/ajax/get-staff-by-department/?department_id=${departmentId}`) - .then(response => response.json()) - .then(data => { - staffSelect.innerHTML = ''; - data.staff.forEach(staff => { - const option = document.createElement('option'); - option.value = staff.id; - option.textContent = `${staff.first_name} ${staff.last_name} (${staff.job_title || staff.staff_type})`; - staffSelect.appendChild(option); - }); - }) - .catch(error => { - console.error('Error loading staff:', error); - staffSelect.innerHTML = ''; - }); - } - }); - - // Category change handler - load subcategories - categorySelect.addEventListener('change', function() { - const categoryId = this.value; - - // Clear subcategory dropdown - subcategorySelect.innerHTML = ''; - - if (categoryId) { - // Load categories again and filter for subcategories of selected parent - const hospitalId = hospitalSelect.value; - if (hospitalId) { - fetch(`/complaints/public/api/load-categories/?hospital_id=${hospitalId}`) + if (departmentId) { + // Load staff via minimal AJAX + fetch(`/complaints/ajax/physicians/?department_id=${departmentId}`) .then(response => response.json()) .then(data => { - subcategorySelect.innerHTML = ''; - data.categories.forEach(cat => { - // Only show subcategories (has parent_id matching selected category) - if (cat.parent_id == categoryId) { - const option = document.createElement('option'); - option.value = cat.id; - option.dataset.code = cat.code; - const catName = currentLang === 'ar' && cat.name_ar ? cat.name_ar : cat.name_en; - option.textContent = catName; - subcategorySelect.appendChild(option); - } + staffSelect.innerHTML = ''; + data.staff.forEach(staff => { + const option = document.createElement('option'); + option.value = staff.id; + option.textContent = `${staff.first_name} ${staff.last_name} (${staff.job_title || staff.staff_type})`; + staffSelect.appendChild(option); }); - if (subcategorySelect.options.length <= 1) { - subcategorySelect.innerHTML = ''; - } }) .catch(error => { - console.error('Error loading subcategories:', error); - subcategorySelect.innerHTML = ''; + console.error('Error loading staff:', error); }); } - } - }); - - // Patient search - patientSelect.addEventListener('focus', function() { - if (this.options.length === 1) { - loadPatients(''); - } - }); - - function loadPatients(searchTerm) { - const url = searchTerm - ? `/api/organizations/patients/?search=${encodeURIComponent(searchTerm)}` - : '/api/organizations/patients/?page_size=50'; - - fetch(url) - .then(response => response.json()) - .then(data => { - patientSelect.innerHTML = ''; - data.results.forEach(patient => { - const option = document.createElement('option'); - option.value = patient.id; - option.textContent = `${patient.first_name} ${patient.last_name} (MRN: ${patient.mrn})`; - patientSelect.appendChild(option); - }); - }) - .catch(error => console.error('Error loading patients:', error)); + }); } // Form validation diff --git a/templates/complaints/inquiry_detail.html b/templates/complaints/inquiry_detail.html index e947d1f..6575e40 100644 --- a/templates/complaints/inquiry_detail.html +++ b/templates/complaints/inquiry_detail.html @@ -1,4 +1,4 @@ -{% extends "layouts/base.html" %} +{% extends base_layout %} {% load i18n %} {% load static %} @@ -103,9 +103,15 @@

+ {% if source_user %} + + {{ _("Back to My Inquiries")}} + + {% else %} {{ _("Back to Inquiries")}} + {% endif %}
diff --git a/templates/complaints/inquiry_form.html b/templates/complaints/inquiry_form.html index b5b22ae..ce61e37 100644 --- a/templates/complaints/inquiry_form.html +++ b/templates/complaints/inquiry_form.html @@ -21,10 +21,6 @@ padding-bottom: 10px; border-bottom: 2px solid #17a2b8; } - .required-field::after { - content: " *"; - color: #dc3545; - } {% endblock %} @@ -51,6 +47,12 @@ {% csrf_token %} + {% if form.non_field_errors %} +
+ {{ form.non_field_errors }} +
+ {% endif %} +
@@ -59,23 +61,23 @@ {{ _("Organization") }} -
-
- - -
- -
- - -
+
+ {{ form.hospital.label_tag }} + {{ form.hospital }} + {% if form.hospital.help_text %} + {{ form.hospital.help_text }} + {% endif %} + {% for error in form.hospital.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+ {{ form.department.label_tag }} + {{ form.department }} + {% for error in form.department.errors %} +
{{ error }}
+ {% endfor %}
@@ -85,37 +87,37 @@ {{ _("Contact Information")}} - +
- - - - - + {{ form.patient.label_tag }} + {{ form.patient }} + {% for error in form.patient.errors %} +
{{ error }}
+ {% endfor %}
-
- {{ _("OR") }} -
- -
- - + {{ form.contact_name.label_tag }} + {{ form.contact_name }} + {% for error in form.contact_name.errors %} +
{{ error }}
+ {% endfor %}
- - + {{ form.contact_phone.label_tag }} + {{ form.contact_phone }} + {% for error in form.contact_phone.errors %} +
{{ error }}
+ {% endfor %}
- - + {{ form.contact_email.label_tag }} + {{ form.contact_email }} + {% for error in form.contact_email.errors %} +
{{ error }}
+ {% endfor %}
@@ -127,45 +129,33 @@
- - + {{ form.category.label_tag }} + {{ form.category }} + {% for error in form.category.errors %} +
{{ error }}
+ {% endfor %}
- - + {{ form.subject.label_tag }} + {{ form.subject }} + {% for error in form.subject.errors %} +
{{ error }}
+ {% endfor %}
- - + {{ form.message.label_tag }} + {{ form.message }} + {% for error in form.message.errors %} +
{{ error }}
+ {% endfor %}
- -
- - - - {{ _("Leave empty for default")}} - -
@@ -177,10 +167,7 @@


- {{ _("If the inquiry is from a registered patient, search and select them. Otherwise, provide contact information.")}} -

-

- {{ _("Fields marked with * are required.")}} + {{ _("Fill in the inquiry details. Fields marked with * are required.")}}

@@ -208,9 +195,9 @@ {% block extra_js %} {% endblock %}