diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index 782d847..c933fed 100644 Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index ce51983..c1e186f 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index e57b975..a41a4aa 100644 Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ diff --git a/recruitment/forms.py b/recruitment/forms.py index e356e97..ea32ac2 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -1698,6 +1698,7 @@ class CandidateEmailForm(forms.Form): return message + class InterviewParticpantsForm(forms.ModelForm): participants = forms.ModelMultipleChoiceField( queryset=Participants.objects.all(), @@ -1706,7 +1707,7 @@ class InterviewParticpantsForm(forms.ModelForm): ) system_users=forms.ModelMultipleChoiceField( - queryset=User.objects.all(), + queryset=User.objects.filter(user_type='staff'), widget=forms.CheckboxSelectMultiple, required=False, label=_("Select Users")) @@ -1861,107 +1862,107 @@ class InterviewParticpantsForm(forms.ModelForm): # self.initial['message_for_participants'] = participants_message.strip() -class InterviewEmailForm(forms.Form): - # ... (Field definitions) +# class InterviewEmailForm(forms.Form): +# # ... (Field definitions) - def __init__(self, *args, candidate, external_participants, system_participants, meeting, job, **kwargs): - super().__init__(*args, **kwargs) +# def __init__(self, *args, candidate, external_participants, system_participants, meeting, job, **kwargs): +# super().__init__(*args, **kwargs) - location = meeting.interview_location +# location = meeting - # --- Data Preparation --- +# # --- Data Preparation --- - # Safely access details through the related InterviewLocation object - if location and location.start_time: - formatted_date = location.start_time.strftime('%Y-%m-%d') - formatted_time = location.start_time.strftime('%I:%M %p') - duration = location.duration - meeting_link = location.details_url if location.details_url else "N/A (See Location Topic)" - else: - # Handle case where location or time is missing/None - formatted_date = "TBD - Awaiting Scheduling" - formatted_time = "TBD" - duration = "N/A" - meeting_link = "Not Available" +# # Safely access details through the related InterviewLocation object +# if location and location.start_time: +# formatted_date = location.start_time.strftime('%Y-%m-%d') +# formatted_time = location.start_time.strftime('%I:%M %p') +# duration = location.duration +# meeting_link = location.details_url if location.details_url else "N/A (See Location Topic)" +# else: +# # Handle case where location or time is missing/None +# formatted_date = "TBD - Awaiting Scheduling" +# formatted_time = "TBD" +# duration = "N/A" +# meeting_link = "Not Available" - job_title = job.title - agency_name = candidate.hiring_agency.name if candidate.belong_to_an_agency and candidate.hiring_agency else "Hiring Agency" +# job_title = job.title +# agency_name = candidate.hiring_agency.name if candidate.belong_to_an_agency and candidate.hiring_agency else "Hiring Agency" - # --- Combined Participants List for Internal Email --- - external_participants_names = ", ".join([p.name for p in external_participants ]) - system_participants_names = ", ".join([p.first_name for p in system_participants ]) - participant_names = ", ".join(filter(None, [external_participants_names, system_participants_names])) +# # --- Combined Participants List for Internal Email --- +# external_participants_names = ", ".join([p.name for p in external_participants ]) +# system_participants_names = ", ".join([p.first_name for p in system_participants ]) +# participant_names = ", ".join(filter(None, [external_participants_names, system_participants_names])) - # --- 1. Candidate Message (Use meeting_link) --- - candidate_message = f""" -Dear {candidate.full_name}, +# # --- 1. Candidate Message (Use meeting_link) --- +# candidate_message = f""" +# Dear {candidate.full_name}, -Thank you for your interest in the **{job_title}** position at KAAUH. We're pleased to invite you to an interview! +# Thank you for your interest in the **{job_title}** position at KAAUH. We're pleased to invite you to an interview! -The details of your virtual interview are as follows: +# The details of your virtual interview are as follows: -- **Date:** {formatted_date} -- **Time:** {formatted_time} (RIYADH TIME) -- **Duration:** {duration} -- **Meeting Link:** {meeting_link} +# - **Date:** {formatted_date} +# - **Time:** {formatted_time} (RIYADH TIME) +# - **Duration:** {duration} +# - **Meeting Link:** {meeting_link} -Please click the link at the scheduled time to join the interview. +# Please click the link at the scheduled time to join the interview. -Kindly reply to this email to **confirm your attendance** or to propose an alternative time if necessary. +# Kindly reply to this email to **confirm your attendance** or to propose an alternative time if necessary. -We look forward to meeting you. +# We look forward to meeting you. -Best regards, -KAAUH Hiring Team -""" - # ... (Messages for agency and participants remain the same, using the updated safe variables) +# Best regards, +# KAAUH Hiring Team +# """ +# # ... (Messages for agency and participants remain the same, using the updated safe variables) - # --- 2. Agency Message (Professional and clear details) --- - agency_message = f""" -Dear {agency_name}, -... -**Interview Details:** -... -- **Date:** {formatted_date} -- **Time:** {formatted_time} (RIYADH TIME) -- **Duration:** {duration} -- **Meeting Link:** {meeting_link} -... -""" +# # --- 2. Agency Message (Professional and clear details) --- +# agency_message = f""" +# Dear {agency_name}, +# ... +# **Interview Details:** +# ... +# - **Date:** {formatted_date} +# - **Time:** {formatted_time} (RIYADH TIME) +# - **Duration:** {duration} +# - **Meeting Link:** {meeting_link} +# ... +# """ - # --- 3. Participants Message (Action-oriented and informative) --- - participants_message = f""" -Hi Team, -... -**Interview Summary:** +# # --- 3. Participants Message (Action-oriented and informative) --- +# participants_message = f""" +# Hi Team, +# ... +# **Interview Summary:** -- **Candidate:** {candidate.full_name} -- **Date:** {formatted_date} -- **Time:** {formatted_time} (RIYADH TIME) -- **Duration:** {duration} -- **Your Fellow Interviewers:** {participant_names} +# - **Candidate:** {candidate.full_name} +# - **Date:** {formatted_date} +# - **Time:** {formatted_time} (RIYADH TIME) +# - **Duration:** {duration} +# - **Your Fellow Interviewers:** {participant_names} -**Action Items:** +# **Action Items:** -1. Please review **{candidate.full_name}'s** resume and notes. -2. The official calendar invite contains the meeting link ({meeting_link}) and should be used to join. -3. Be ready to start promptly at the scheduled time. -... -""" - # Set initial data - self.initial['subject'] = f"Interview Invitation: {job_title} at KAAUH - {candidate.full_name}" - self.initial['message_for_candidate'] = candidate_message.strip() - self.initial['message_for_agency'] = agency_message.strip() - self.initial['message_for_participants'] = participants_message.strip() +# 1. Please review **{candidate.full_name}'s** resume and notes. +# 2. The official calendar invite contains the meeting link ({meeting_link}) and should be used to join. +# 3. Be ready to start promptly at the scheduled time. +# ... +# """ +# # Set initial data +# self.initial['subject'] = f"Interview Invitation: {job_title} at KAAUH - {candidate.full_name}" +# self.initial['message_for_candidate'] = candidate_message.strip() +# self.initial['message_for_agency'] = agency_message.strip() +# self.initial['message_for_participants'] = participants_message.strip() -# class OnsiteLocationForm(forms.ModelForm): -# class Meta: -# model= -# fields=['location'] -# widgets={ -# 'location': forms.TextInput(attrs={'placeholder': 'Enter Interview Location'}), -# } +# # class OnsiteLocationForm(forms.ModelForm): +# # class Meta: +# # model= +# # fields=['location'] +# # widgets={ +# # 'location': forms.TextInput(attrs={'placeholder': 'Enter Interview Location'}), +# # } #during bulk schedule class OnsiteLocationForm(forms.ModelForm): @@ -1986,6 +1987,125 @@ class OnsiteLocationForm(forms.ModelForm): } +class InterviewEmailForm(forms.Form): + subject = forms.CharField(max_length=255, widget=forms.TextInput(attrs={'class': 'form-control'})) + message_for_candidate = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 6})) + message_for_agency = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 6})) + message_for_participants = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 6})) + + def __init__(self, *args, candidate, external_participants, system_participants, meeting, job, **kwargs): + """ + meeting: an InterviewLocation instance (e.g., ZoomMeetingDetails or OnsiteLocationDetails) + """ + super().__init__(*args, **kwargs) + + # ✅ meeting is already the InterviewLocation — do NOT use .interview_location + location = meeting + + # --- Determine concrete details (Zoom or Onsite) --- + if location.location_type == location.LocationType.REMOTE: + details = getattr(location, 'zoommeetingdetails', None) + elif location.location_type == location.LocationType.ONSITE: + details = getattr(location, 'onsitelocationdetails', None) + else: + details = None + + # --- Extract meeting info safely --- + if details and details.start_time: + formatted_date = details.start_time.strftime('%Y-%m-%d') + formatted_time = details.start_time.strftime('%I:%M %p') + duration = details.duration + meeting_link = location.details_url or "N/A (See Location Topic)" + else: + formatted_date = "TBD - Awaiting Scheduling" + formatted_time = "TBD" + duration = "N/A" + meeting_link = "Not Available" + + job_title = job.title + agency_name = ( + candidate.hiring_agency.name + if candidate.belong_to_agency and candidate.hiring_agency + else "Hiring Agency" + ) + + # --- Participant names for internal email --- + external_names = ", ".join([p.name for p in external_participants]) + system_names = ", ".join([u.get_full_name() or u.username for u in system_participants]) + participant_names = ", ".join(filter(None, [external_names, system_names])) + + # --- Candidate Message --- + candidate_message = f""" +Dear {candidate.full_name}, + +Thank you for your interest in the **{job_title}** position at KAAUH. We're pleased to invite you to an interview! + +The details of your interview are as follows: + +- **Date:** {formatted_date} +- **Time:** {formatted_time} (RIYADH TIME) +- **Duration:** {duration} minutes +- **Meeting Link/Location:** {meeting_link} + +Please be ready at the scheduled time. + +Kindly reply to confirm your attendance or propose an alternative if needed. + +We look forward to meeting you. + +Best regards, +KAAUH Hiring Team +""".strip() + + # --- Agency Message --- + agency_message = f""" +Dear {agency_name}, + +This is to inform you that your candidate, **{candidate.full_name}**, has been scheduled for an interview for the **{job_title}** position. + +**Interview Details:** +- **Date:** {formatted_date} +- **Time:** {formatted_time} (RIYADH TIME) +- **Duration:** {duration} minutes +- **Meeting Link/Location:** {meeting_link} + +Please ensure the candidate is informed and prepared. + +Best regards, +KAAUH Hiring Team +""".strip() + + # --- Participants (Interview Panel) Message --- + participants_message = f""" +Hi Team, + +You are scheduled to interview **{candidate.full_name}** for the **{job_title}** role. + +**Interview Summary:** +- **Candidate:** {candidate.full_name} +- **Date:** {formatted_date} +- **Time:** {formatted_time} (RIYADH TIME) +- **Duration:** {duration} minutes +- **Location/Link:** {meeting_link} +- **Fellow Interviewers:** {participant_names} + +**Action Items:** +1. Review the candidate’s resume and application notes. +2. Join via the link above (or be at the physical location) on time. +3. Coordinate among yourselves for role coverage. + +Thank you! +""".strip() + + # --- Set initial values --- + self.initial.update({ + 'subject': f"Interview Invitation: {job_title} - {candidate.full_name}", + 'message_for_candidate': candidate_message, + 'message_for_agency': agency_message, + 'message_for_participants': participants_message, + }) + + class OnsiteReshuduleForm(forms.ModelForm): class Meta: model = OnsiteLocationDetails diff --git a/recruitment/urls.py b/recruitment/urls.py index 0e57def..c72eff2 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -656,5 +656,6 @@ urlpatterns = [ # Detail View (assuming slug is on ScheduledInterview) - # path("interviews/meetings//", views.MeetingDetailView.as_view(), name="meeting_details"), + path("interviews/meetings//", views.meeting_details, name="meeting_details"), + ] diff --git a/recruitment/views.py b/recruitment/views.py index ba422ff..5b09078 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -129,7 +129,8 @@ from .models import ( Message, Document, OnsiteLocationDetails, - InterviewLocation + InterviewLocation, + InterviewNote ) @@ -249,123 +250,7 @@ class ZoomMeetingCreateView(StaffRequiredMixin, CreateView): messages.error(self.request, f"Error creating meeting: {e}") return redirect(reverse("create_meeting", kwargs={"slug": instance.slug})) - -# class ZoomMeetingListView(StaffRequiredMixin, ListView): -# model = ZoomMeetingDetails -# template_name = "meetings/list_meetings.html" -# context_object_name = "meetings" -# paginate_by = 10 - -# def get_queryset(self): -# queryset = super().get_queryset().order_by("-start_time") - -# # Prefetch related interview data efficiently - -# queryset = queryset.prefetch_related( -# Prefetch( -# "interview", # related_name from ZoomMeeting to ScheduledInterview -# queryset=ScheduledInterview.objects.select_related("application", "job"), -# to_attr="interview_details", # Changed to not start with underscore -# ) -# ) - -# # Handle search by topic or meeting_id -# search_query = self.request.GET.get( -# "q", "" -# ) # Renamed from 'search' to 'q' for consistency -# if search_query: -# queryset = queryset.filter( -# Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query) -# ) - -# # Handle filter by status -# status_filter = self.request.GET.get("status", "") -# if status_filter: -# queryset = queryset.filter(status=status_filter) - -# # Handle search by candidate name -# candidate_name = self.request.GET.get("candidate_name", "") -# if candidate_name: -# # Filter based on the name of the candidate associated with the meeting's interview -# queryset = queryset.filter( -# Q(interview__application__first_name__icontains=candidate_name) -# | Q(interview__application__last_name__icontains=candidate_name) -# ) - -# return queryset - -# def get_context_data(self, **kwargs): -# context = super().get_context_data(**kwargs) -# context["search_query"] = self.request.GET.get("q", "") -# context["status_filter"] = self.request.GET.get("status", "") -# context["candidate_name_filter"] = self.request.GET.get("candidate_name", "") -# return context - - - -# @login_required -# def InterviewListView(request): -# # interview_type=request.GET.get('interview_type','Remote') -# # print(interview_type) -# interview_type='Onsite' -# meetings=ScheduledInterview.objects.filter(schedule__interview_type=interview_type) -# return render(request, "meetings/list_meetings.html",{ -# 'meetings':meetings, -# }) - - -# search_query = request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency -# if search_query: -# interviews = interviews.filter( -# Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query) -# ) - -# # Handle filter by status -# status_filter = request.GET.get("status", "") -# if status_filter: -# queryset = queryset.filter(status=status_filter) - -# # Handle search by candidate name -# candidate_name = request.GET.get("candidate_name", "") -# if candidate_name: -# # Filter based on the name of the candidate associated with the meeting's interview -# queryset = queryset.filter( -# Q(interview__candidate__first_name__icontains=candidate_name) | -# Q(interview__candidate__last_name__icontains=candidate_name) -# ) - - -# @login_required -# def InterviewListView(request): -# # interview_type=request.GET.get('interview_type','Remote') -# # print(interview_type) -# interview_type='Onsite' -# meetings=ScheduledInterview.objects.filter(schedule__interview_type=interview_type) -# return render(request, "meetings/list_meetings.html",{ -# 'meetings':meetings, -# }) - - -# search_query = request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency -# if search_query: -# interviews = interviews.filter( -# Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query) -# ) - -# # Handle filter by status -# status_filter = request.GET.get("status", "") -# if status_filter: -# queryset = queryset.filter(status=status_filter) - -# # Handle search by candidate name -# candidate_name = request.GET.get("candidate_name", "") -# if candidate_name: -# # Filter based on the name of the candidate associated with the meeting's interview -# queryset = queryset.filter( -# Q(interview__candidate__first_name__icontains=candidate_name) | -# Q(interview__candidate__last_name__icontains=candidate_name) -# ) - + class ZoomMeetingDetailsView(StaffRequiredMixin, DetailView): model = ZoomMeetingDetails @@ -3122,7 +3007,7 @@ def add_meeting_comment(request, slug): meeting = get_object_or_404(ZoomMeetingDetails, slug=slug) if request.method == "POST": - form = MeetingCommentForm(request.POST) + form = InterviewNoteForm(request.POST) if form.is_valid(): comment = form.save(commit=False) comment.meeting = meeting @@ -3143,7 +3028,7 @@ def add_meeting_comment(request, slug): return redirect("meeting_details", slug=slug) else: - form = MeetingCommentForm() + form = InterviewNoteForm() context = { "form": form, @@ -3169,7 +3054,7 @@ def edit_meeting_comment(request, slug, comment_id): return redirect("meeting_details", slug=slug) if request.method == "POST": - form = MeetingCommentForm(request.POST, instance=comment) + form = InterviewNoteForm(request.POST, instance=comment) if form.is_valid(): comment = form.save() messages.success(request, "Comment updated successfully!") @@ -3187,7 +3072,7 @@ def edit_meeting_comment(request, slug, comment_id): return redirect("meeting_details", slug=slug) else: - form = MeetingCommentForm(instance=comment) + form = InterviewNoteForm(instance=comment) context = {"form": form, "meeting": meeting, "comment": comment} return render(request, "includes/edit_comment_form.html", context) @@ -5773,7 +5658,7 @@ class MeetingListView(ListView): 'details': details, 'type': location.location_type, 'topic': location.topic, - 'slug': interview.slug, + # 'slug': interview.slug, 'start_time': start_datetime, # Combined datetime object # Duration should ideally be on ScheduledInterview or fetched from details 'duration': getattr(details, 'duration', 'N/A'), @@ -5947,3 +5832,60 @@ def schedule_onsite_meeting_for_candidate(request, slug, candidate_pk): return render(request, "meetings/schedule_onsite_meeting_form.html", context) + + +from django.http import Http404 + + +def meeting_details(request, slug): + # Fetch the meeting (InterviewLocation or subclass) by slug + meeting = get_object_or_404( + InterviewLocation.objects.select_related( + 'scheduled_interview__application__person', + 'scheduled_interview__job', + 'zoommeetingdetails', + 'onsitelocationdetails', + ).prefetch_related( + 'scheduled_interview__participants', + 'scheduled_interview__system_users', + 'scheduled_interview__notes', + ), + slug=slug + ) + + try: + interview = meeting.scheduled_interview + except ScheduledInterview.DoesNotExist: + raise Http404("No interview is associated with this meeting.") + + candidate = interview.application + job = interview.job + + external_participants = interview.participants.all() + system_participants = interview.system_users.all() + total_participants = external_participants.count() + system_participants.count() + + # Forms for modals + participant_form = InterviewParticpantsForm(instance=interview) + # email_form = InterviewEmailForm( + # candidate=candidate, + # external_participants=external_participants, # QuerySet of Participants + # system_participants=system_participants, # QuerySet of Users + # meeting=meeting, # ← This is InterviewLocation (e.g., ZoomMeetingDetails) + # job=job, + # ) + + context = { + 'meeting': meeting, + 'interview': interview, + 'candidate': candidate, + 'job': job, + 'external_participants': external_participants, + 'system_participants': system_participants, + 'total_participants': total_participants, + 'form': participant_form, + # 'email_form': email_form, + } + + return render(request, 'interviews/detail_interview.html', context) + diff --git a/templates/interviews/detail_interview.html b/templates/interviews/detail_interview.html index 2843437..0739575 100644 --- a/templates/interviews/detail_interview.html +++ b/templates/interviews/detail_interview.html @@ -1,12 +1,9 @@ {% extends 'base.html' %} {% load static i18n %} {% load widget_tweaks %} + {% block customCSS %} {% endblock %} - {% block content %} -{% comment %} - NOTE: The variable 'meeting' has been renamed to 'interview' (ScheduledInterview) - NOTE: The variable 'meeting.slug' has been renamed to 'interview.slug' - NOTE: All 'meeting' URL names (update_meeting, delete_meeting, etc.) have been renamed -{% endcomment %} -
- {# --- TOP BAR / BACK BUTTON & ACTIONS (EDIT/DELETE) --- #} + {# --- TOP BAR --- #}
- {# Back Button #} - - {% trans "Back to Interviews" %} + + {% trans "Back to Meetings" %} - - {# Edit and Delete Buttons #}
- - {% trans "Edit Interview" %} + + {% trans "Edit Meeting" %} - {# DELETE MEETING FORM #} -
+ {% csrf_token %} -
- {# ========================================================= #} - {# --- MAIN TITLE AT TOP --- #} - {# ========================================================= #} - {% with zoom_details=interview.zoom_details.0 %} + {# --- MAIN TITLE --- #}

- {% if interview.schedule.interview_type == 'Remote' %} - - {{ zoom_details.topic|default:"[Remote Interview]" }} - {% else %} - - {{ interview.schedule.location|default:"[Onsite Interview]" }} - {% endif %} - - - {{ interview.status|title|default:'N/A' }} ({{ interview.schedule.interview_type }}) + + {{ meeting.topic|default:"[Meeting Topic]" }} + + {{ interview.get_status_display|default:"Scheduled" }}

- {# ========================================================= #} - {# --- SECTION 1: INTERVIEW & CONNECTION/LOCATION CARDS SIDE BY SIDE --- #} - {# ========================================================= #} + {# --- INTERVIEW & CONNECTION CARDS --- #}
- - {# --- LEFT HALF: INTERVIEW DETAIL CARD --- #} + {# Interview Detail #}
-

{% trans "Candidate & Job" %}

+

{% trans "Interview Detail" %}

- {# NOTE: Assuming ScheduledInterview has direct relations to candidate and job #} - - -
{% trans "Candidate Email" %}:
-
{% trans "Job Type" %}:
{{ interview.job.job_type|default:"N/A" }}
- {% if interview.candidate.belong_to_agency %} - +
+
{% trans "Job Title" %}:
+
{{ job.title|default:"N/A" }}
+
+
+
{% trans "Candidate Name" %}:
+
{{ candidate.full_name|default:"N/A" }}
+
+
+
{% trans "Candidate Email" %}:
+
{{ candidate.email|default:"N/A" }}
+
+
+
{% trans "Job Type" %}:
+
{{ job.job_type|default:"N/A" }}
+
+ {% if candidate.belong_to_agency %} +
+
{% trans "Agency" %}:
+
{{ candidate.hiring_agency.name|default:"N/A" }}
+
{% endif %}
- {# --- RIGHT HALF: CONNECTION/LOCATION DETAILS CARD --- #} + {# Connection Details #}
-

{% trans "Time & Location" %}

+

{% trans "Connection Details" %}

-
{% trans "Date & Time" %}:
{{ interview.interview_date|date:"M d, Y"|default:"N/A" }} @ {{ interview.interview_time|time:"H:i"|default:"N/A" }}
-
{% trans "Duration" %}:
{{ interview.schedule.interview_duration|default:"N/A" }} {% trans "minutes" %}
- - {% if interview.schedule.interview_type == 'Onsite' %} - {# --- Onsite Details --- #} -
{% trans "Location" %}:
{{ interview.schedule.location|default:"TBD" }}
+
+
{% trans "Date & Time" %}:
+
+ {{ interview.interview_date }} {{ interview.interview_time }} ({{ meeting.timezone }}) +
+
+
+
{% trans "Duration" %}:
+
+ {% if meeting.location_type == "Remote" %} + {{ meeting.zoommeetingdetails.duration|default:"N/A" }} + {% elif meeting.location_type == "Onsite" %} + {{ meeting.onsitelocationdetails.duration|default:"N/A" }} + {% else %} + N/A + {% endif %} + {% trans "minutes" %} +
+
- {% elif interview.schedule.interview_type == 'Remote' and zoom_details %} - {# --- Remote/Zoom Details --- #} -

{% trans "Remote Details" %}

-
{% trans "Meeting ID" %}:
{{ zoom_details.meeting_id|default:"N/A" }}
-
{% trans "Host Email" %}:
{{ zoom_details.host_email|default:"N/A" }}
- - {% if zoom_details.join_url %} -
-
{% trans "Copied!" %}
- -
+ {% if meeting.location_type == "Remote" %} + {% with zoom=meeting.zoommeetingdetails %} +
+
{% trans "Meeting ID" %}:
+
{{ zoom.meeting_id|default:"N/A" }}
+
+
+
{% trans "Host Email" %}:
+
{{ zoom.host_email|default:"N/A" }}
+
+ {% if meeting.details_url %} +
+
+ {% trans "Copied!" %} +
+
{% trans "Join URL" %}: - {{ zoom_details.join_url }} + {{ meeting.details_url }}
{% endif %} - {% else %} -

{% trans "Location/Connection details are not available for this interview type." %}

+ {% endwith %} + {% elif meeting.location_type == "Onsite" %} + {% with onsite=meeting.onsitelocationdetails %} +
+
{% trans "Address" %}:
+
{{ onsite.physical_address|default:"N/A" }}
+
+
+
{% trans "Room" %}:
+
{{ onsite.room_number|default:"TBD" }}
+
+ {% endwith %} {% endif %}
- {% endwith %} - {# ========================================================= #} - {# --- SECTION 2: PERSONNEL TABLES --- #} - {# ========================================================= #} + {# --- PARTICIPANTS --- #}
- - - {# --- PARTICIPANTS TABLE --- #}
-
+

{% trans "Assigned Participants" %}

-
- - - +
+ +
-
+
- + - - + + - {# External Participants #} - {% for participant in interview.participants.all %} + {% for participant in external_participants %} - - - - - + + + + + {% endfor %} - {# System Users (Internal Participants) #} - {% for user in interview.system_users.all %} + {% for user in system_participants %} - - - - + + + + {% endfor %} -
{% trans "Name" %}{% trans "Role/Designation" %}{% trans "Role" %} {% trans "Email" %}{% trans "Phone Number" %}{% trans "Source Type" %}{% trans "Phone" %}{% trans "Type" %}
{{participant.name}}{{participant.designation}}{{participant.email}}{{participant.phone}}{% trans "External Participants" %}{{ participant.name }}{{ participant.designation|default:"Participant" }}{{ participant.email|default:"N/A" }}{{ participant.phone|default:"N/A" }}{% trans "External" %}
{{user.get_full_name}}{% trans "System User" %}{{user.email}}{{user.phone}}{{ user.get_full_name|default:user.username }}Admin{{ user.email|default:"N/A" }}{{ user.phone|default:"N/A" }} {% trans "System User" %}
- {# ========================================================= #} - {# --- SECTION 3: COMMENTS (CORRECTED) --- #} - {# ========================================================= #} + {# --- COMMENTS --- #}
-
-
+
-
+
- {% trans "Comments" %} ({% if interview.comments %}{{ interview.comments.count }}{% else %}0{% endif %}) + {% trans "Comments" %} ({{ interview.notes.count }})
-
- - {# 1. COMMENT DISPLAY & IN-PAGE EDIT FORMS #} +
- {# NOTE: Assuming comment model has a ForeignKey to ScheduledInterview called 'interview' #} - {% if interview.comments.all %} - {% for comment in interview.comments.all|dictsortreversed:"created_at" %} - -
- - {# Read-Only Comment View #} -
-
- - - {% if comment.author == user or user.is_staff %} -
- {# Edit Button: Toggles the hidden form #} - - - {# Delete Form: Submits a POST request #} -
- {% csrf_token %} - -
-
- {% endif %} -
-

{{ comment.content|linebreaksbr }}

+ {% for note in interview.notes.all|dictsortreversed:"created_at" %} +
+
+
+ - - {# Hidden Edit Form #} - - {% endfor %} - {% else %} -

{% trans "No comments yet. Be the first to comment!" %}

- {% endif %} +

{{ note.content|linebreaksbr }}

+
+ + +
+ {% empty %} +

{% trans "No comments yet. Be the first to comment!" %}

+ {% endfor %}

- - {# 2. NEW COMMENT SUBMISSION (Remains the same) #} -
{% trans "Add a New Comment" %}
- {% if user.is_authenticated %} -
- {% csrf_token %} - {% if comment_form %} - {{ comment_form.as_p }} - {% else %} -
- - -
- {% endif %} - -
- {% else %} -

{% trans "You must be logged in to add a comment." %}

- {% endif %} +
{% trans "Add a New Comment" %}
+
+ {% csrf_token %} +
+ + +
+ +
- -{# --- MODALS (Updated to use interview.slug) --- #} - -