From 8851e591dafd2cb1fc4b07226914a32235f2de3f Mon Sep 17 00:00:00 2001 From: Faheed Date: Wed, 29 Oct 2025 19:59:56 +0300 Subject: [PATCH] chnages to dashobard and meetings details --- .../__pycache__/models.cpython-312.pyc | Bin 62223 -> 63964 bytes recruitment/__pycache__/urls.cpython-312.pyc | Bin 13530 -> 13530 bytes .../views_frontend.cpython-312.pyc | Bin 27564 -> 27674 bytes recruitment/migrations/0001_initial.py | 30 +- .../migrations/0002_candidate_retry.py | 18 - ...created_at_remove_profile_slug_and_more.py | 25 + ..._jobposting_linkedin_post_formated_data.py | 18 - ..._jobposting_linkedin_post_formated_data.py | 18 - ..._jobposting_linkedin_post_formated_data.py | 18 - recruitment/migrations/0006_participants.py | 24 - ...s_created_at_participants_slug_and_more.py | 30 - .../0008_rename_job_participants_jobs.py | 18 - .../0009_jobposting_assigned_users.py | 20 - .../0010_remove_jobposting_assigned_users.py | 17 - .../0011_jobposting_internal_participant.py | 20 - .../0012_remove_participants_jobs_and_more.py | 29 - ...obposting_external_participant_and_more.py | 33 - recruitment/models.py | 46 +- recruitment/views_frontend.py | 55 +- templates/meetings/meeting_details.html | 675 +++++++++--------- templates/participants/participants_list.html | 24 +- templates/recruitment/dashboard.html | 140 ++-- 22 files changed, 551 insertions(+), 707 deletions(-) delete mode 100644 recruitment/migrations/0002_candidate_retry.py create mode 100644 recruitment/migrations/0002_remove_profile_created_at_remove_profile_slug_and_more.py delete mode 100644 recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py delete mode 100644 recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py delete mode 100644 recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py delete mode 100644 recruitment/migrations/0006_participants.py delete mode 100644 recruitment/migrations/0007_participants_created_at_participants_slug_and_more.py delete mode 100644 recruitment/migrations/0008_rename_job_participants_jobs.py delete mode 100644 recruitment/migrations/0009_jobposting_assigned_users.py delete mode 100644 recruitment/migrations/0010_remove_jobposting_assigned_users.py delete mode 100644 recruitment/migrations/0011_jobposting_internal_participant.py delete mode 100644 recruitment/migrations/0012_remove_participants_jobs_and_more.py delete mode 100644 recruitment/migrations/0013_remove_jobposting_external_participant_and_more.py diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index b4e2d8a063fc9b2b5fd1dca57a48e4e8d306b548..b3fa580085721c185bf20f96e5001b78878100d0 100644 GIT binary patch delta 10376 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 D*Hg9Q delta 9117 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 F`xl~-D(V0L diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index d6c2da6..48af4fe 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2025-10-23 14:08 +# Generated by Django 5.2.7 on 2025-10-29 11:45 import django.core.validators import django.db.models.deletion @@ -66,6 +66,22 @@ class Migration(migrations.Migration): 'ordering': ['name'], }, ), + migrations.CreateModel( + name='Participants', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Participant Name')), + ('email', models.EmailField(max_length=254, verbose_name='Email')), + ('phone', models.CharField(blank=True, max_length=12, null=True, verbose_name='Phone Number')), + ('designation', models.CharField(blank=True, max_length=100, null=True, verbose_name='Designation')), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='Source', fields=[ @@ -211,6 +227,7 @@ class Migration(migrations.Migration): ('offer_status', models.CharField(blank=True, choices=[('Accepted', 'Accepted'), ('Rejected', 'Rejected')], max_length=100, null=True, verbose_name='Offer Status')), ('join_date', models.DateField(blank=True, null=True, verbose_name='Join Date')), ('ai_analysis_data', models.JSONField(default=dict, help_text='Full JSON output from the resume scoring model.', verbose_name='AI Analysis Data')), + ('retry', models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry')), ('submitted_by_agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_candidates', to='recruitment.hiringagency', verbose_name='Submitted by Agency')), ], options={ @@ -248,6 +265,7 @@ class Migration(migrations.Migration): ('posted_to_linkedin', models.BooleanField(default=False)), ('linkedin_post_status', models.CharField(blank=True, help_text='Status of LinkedIn posting', max_length=50)), ('linkedin_posted_at', models.DateTimeField(blank=True, null=True)), + ('linkedin_post_formated_data', models.TextField(blank=True, null=True)), ('published_at', models.DateTimeField(blank=True, db_index=True, null=True)), ('position_number', models.CharField(blank=True, help_text='University position number', max_length=50)), ('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)), @@ -257,6 +275,8 @@ class Migration(migrations.Migration): ('cancelled_by', models.CharField(blank=True, help_text='Name of person who cancelled this job', max_length=100, verbose_name='Cancelled By')), ('cancelled_at', models.DateTimeField(blank=True, null=True)), ('hiring_agency', models.ManyToManyField(blank=True, help_text='External agency responsible for sourcing candidates for this role', related_name='jobs', to='recruitment.hiringagency', verbose_name='Hiring Agency')), + ('users', models.ManyToManyField(blank=True, help_text='Internal staff involved in the recruitment process for this job', related_name='jobs_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Internal Participant')), + ('participants', models.ManyToManyField(blank=True, help_text='External participants involved in the recruitment process for this job', related_name='jobs_participating', to='recruitment.participants', verbose_name='External Participant')), ('source', models.ForeignKey(blank=True, help_text='The system or channel from which this job posting originated or was first published.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='job_postings', to='recruitment.source')), ], options={ @@ -308,9 +328,17 @@ class Migration(migrations.Migration): name='Profile', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), ('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size])), + ('designation', models.CharField(blank=True, max_length=100, null=True)), + ('phone', models.CharField(blank=True, max_length=12, null=True, verbose_name='Phone Number')), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), ], + options={ + 'abstract': False, + }, ), migrations.CreateModel( name='SharedFormTemplate', diff --git a/recruitment/migrations/0002_candidate_retry.py b/recruitment/migrations/0002_candidate_retry.py deleted file mode 100644 index 0f405fc..0000000 --- a/recruitment/migrations/0002_candidate_retry.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-26 11:53 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='candidate', - name='retry', - field=models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry'), - ), - ] diff --git a/recruitment/migrations/0002_remove_profile_created_at_remove_profile_slug_and_more.py b/recruitment/migrations/0002_remove_profile_created_at_remove_profile_slug_and_more.py new file mode 100644 index 0000000..00aaa1e --- /dev/null +++ b/recruitment/migrations/0002_remove_profile_created_at_remove_profile_slug_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.7 on 2025-10-29 12:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='profile', + name='created_at', + ), + migrations.RemoveField( + model_name='profile', + name='slug', + ), + migrations.RemoveField( + model_name='profile', + name='updated_at', + ), + ] diff --git a/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py deleted file mode 100644 index 5e92015..0000000 --- a/recruitment/migrations/0003_jobposting_linkedin_post_formated_data.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-27 10:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0002_candidate_retry'), - ] - - operations = [ - migrations.AddField( - model_name='jobposting', - name='linkedin_post_formated_data', - field=models.JSONField(blank=True, null=True), - ), - ] diff --git a/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py deleted file mode 100644 index d821d60..0000000 --- a/recruitment/migrations/0004_alter_jobposting_linkedin_post_formated_data.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-27 11:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0003_jobposting_linkedin_post_formated_data'), - ] - - operations = [ - migrations.AlterField( - model_name='jobposting', - name='linkedin_post_formated_data', - field=models.CharField(blank=True, null=True), - ), - ] diff --git a/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py b/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py deleted file mode 100644 index 6bd9476..0000000 --- a/recruitment/migrations/0005_alter_jobposting_linkedin_post_formated_data.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-27 11:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0004_alter_jobposting_linkedin_post_formated_data'), - ] - - operations = [ - migrations.AlterField( - model_name='jobposting', - name='linkedin_post_formated_data', - field=models.TextField(blank=True, null=True), - ), - ] diff --git a/recruitment/migrations/0006_participants.py b/recruitment/migrations/0006_participants.py deleted file mode 100644 index db585d3..0000000 --- a/recruitment/migrations/0006_participants.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 12:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0005_alter_jobposting_linkedin_post_formated_data'), - ] - - operations = [ - migrations.CreateModel( - name='Participants', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='Participant Name')), - ('email', models.EmailField(max_length=254, verbose_name='Email')), - ('phone', models.CharField(blank=True, max_length=20, verbose_name='Phone')), - ('designation', models.CharField(blank=True, max_length=100, verbose_name='Designation')), - ('job', models.ManyToManyField(blank=True, related_name='participants', to='recruitment.jobposting')), - ], - ), - ] diff --git a/recruitment/migrations/0007_participants_created_at_participants_slug_and_more.py b/recruitment/migrations/0007_participants_created_at_participants_slug_and_more.py deleted file mode 100644 index fc89f39..0000000 --- a/recruitment/migrations/0007_participants_created_at_participants_slug_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 12:14 - -import django_extensions.db.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0006_participants'), - ] - - operations = [ - migrations.AddField( - model_name='participants', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=None, verbose_name='Created at'), - preserve_default=False, - ), - migrations.AddField( - model_name='participants', - name='slug', - field=django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug'), - ), - migrations.AddField( - model_name='participants', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated at'), - ), - ] diff --git a/recruitment/migrations/0008_rename_job_participants_jobs.py b/recruitment/migrations/0008_rename_job_participants_jobs.py deleted file mode 100644 index 4bea33f..0000000 --- a/recruitment/migrations/0008_rename_job_participants_jobs.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 13:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0007_participants_created_at_participants_slug_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='participants', - old_name='job', - new_name='jobs', - ), - ] diff --git a/recruitment/migrations/0009_jobposting_assigned_users.py b/recruitment/migrations/0009_jobposting_assigned_users.py deleted file mode 100644 index 1ec5f0b..0000000 --- a/recruitment/migrations/0009_jobposting_assigned_users.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 16:41 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0008_rename_job_participants_jobs'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='jobposting', - name='assigned_users', - field=models.ManyToManyField(blank=True, related_name='assigned_jobs', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/recruitment/migrations/0010_remove_jobposting_assigned_users.py b/recruitment/migrations/0010_remove_jobposting_assigned_users.py deleted file mode 100644 index 3b7548d..0000000 --- a/recruitment/migrations/0010_remove_jobposting_assigned_users.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 17:12 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0009_jobposting_assigned_users'), - ] - - operations = [ - migrations.RemoveField( - model_name='jobposting', - name='assigned_users', - ), - ] diff --git a/recruitment/migrations/0011_jobposting_internal_participant.py b/recruitment/migrations/0011_jobposting_internal_participant.py deleted file mode 100644 index e051508..0000000 --- a/recruitment/migrations/0011_jobposting_internal_participant.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 20:42 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0010_remove_jobposting_assigned_users'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='jobposting', - name='internal_participant', - field=models.ManyToManyField(blank=True, help_text='Internal staff involved in the recruitment process for this job', related_name='internal_participant_jobs', to=settings.AUTH_USER_MODEL, verbose_name='Internal Participant'), - ), - ] diff --git a/recruitment/migrations/0012_remove_participants_jobs_and_more.py b/recruitment/migrations/0012_remove_participants_jobs_and_more.py deleted file mode 100644 index 799ee52..0000000 --- a/recruitment/migrations/0012_remove_participants_jobs_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 21:30 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0011_jobposting_internal_participant'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name='participants', - name='jobs', - ), - migrations.AddField( - model_name='jobposting', - name='external_participant', - field=models.ManyToManyField(blank=True, help_text='External participants involved in the recruitment process for this job', related_name='jobs', to='recruitment.participants', verbose_name='External Participant'), - ), - migrations.AlterField( - model_name='jobposting', - name='internal_participant', - field=models.ManyToManyField(blank=True, help_text='Internal staff involved in the recruitment process for this job', related_name='jobs', to=settings.AUTH_USER_MODEL, verbose_name='Internal Participant'), - ), - ] diff --git a/recruitment/migrations/0013_remove_jobposting_external_participant_and_more.py b/recruitment/migrations/0013_remove_jobposting_external_participant_and_more.py deleted file mode 100644 index e830392..0000000 --- a/recruitment/migrations/0013_remove_jobposting_external_participant_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 5.2.7 on 2025-10-28 22:20 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0012_remove_participants_jobs_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.RemoveField( - model_name='jobposting', - name='external_participant', - ), - migrations.RemoveField( - model_name='jobposting', - name='internal_participant', - ), - migrations.AddField( - model_name='jobposting', - name='participants', - field=models.ManyToManyField(blank=True, help_text='External participants involved in the recruitment process for this job', related_name='jobs_participating', to='recruitment.participants', verbose_name='External Participant'), - ), - migrations.AddField( - model_name='jobposting', - name='users', - field=models.ManyToManyField(blank=True, help_text='Internal staff involved in the recruitment process for this job', related_name='jobs_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Internal Participant'), - ), - ] diff --git a/recruitment/models.py b/recruitment/models.py index a258a42..7d7f08a 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -28,6 +28,8 @@ class Base(models.Model): class Profile(models.Model): profile_image = models.ImageField(null=True, blank=True, upload_to="profile_pic/",validators=[validate_image_size]) + designation = models.CharField(max_length=100, blank=True,null=True) + phone=models.CharField(blank=True,null=True,verbose_name=_("Phone Number"),max_length=12) user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") def __str__(self): @@ -735,6 +737,41 @@ class ZoomMeeting(Base): def __str__(self): return self.topic + + @property + def get_job(self): + try: + job=self.interview.job.first() + return job + except: + return None + @property + def get_candidate(self): + try: + candidate=self.interview.candidate.first() + return candidate + except: + return None + + @property + def get_external_participants(self): + try: + interview=self.interview.first() + if interview: + return interview.job.participants.all() + return None + except: + return None + @property + def get_users_participants(self): + try: + interview=self.interview.first() + if interview: + return interview.job.users.all() + return None + except: + return None + class MeetingComment(Base): @@ -1267,7 +1304,8 @@ class InterviewSchedule(Base): models.Index(fields=['end_date']), models.Index(fields=['created_by']), ] - + + class ScheduledInterview(Base): """Stores individual scheduled interviews""" @@ -1319,11 +1357,11 @@ class ScheduledInterview(Base): class Participants(Base): """Model to store Participants details""" - name = models.CharField(max_length=255, verbose_name=_("Participant Name")) + name = models.CharField(max_length=255, verbose_name=_("Participant Name"),null=True,blank=True) email= models.EmailField(verbose_name=_("Email")) - phone = models.CharField(max_length=20, blank=True, verbose_name=_("Phone")) + phone = models.CharField(max_length=12,verbose_name=_("Phone Number"),null=True,blank=True) designation = models.CharField( - max_length=100, blank=True, verbose_name=_("Designation") + max_length=100, blank=True, verbose_name=_("Designation"),null=True ) def __str__(self): diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index a7b0b8c..938e150 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -336,7 +336,8 @@ class TrainingDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): success_url = reverse_lazy('training_list') success_message = 'Training material deleted successfully.' - +from django.db.models import F, IntegerField, Count, Avg +from django.db.models.functions import Cast, Coalesce @login_required def dashboard_view(request): all_candidates_count=0 @@ -362,39 +363,39 @@ def dashboard_view(request): # Assuming 'match_score' is a direct IntegerField/FloatField on the Candidate model # (based on the final, optimized version of handle_reume_parsing_and_scoring) + # The path to your score: ai_analysis_data['analysis_data']['match_score'] + SCORE_PATH = 'ai_analysis_data__analysis_data__match_score' - # Average Match Score (Overall Quality) - candidates_with_score = models.Candidate.objects.filter( - # Filter only candidates that have been parsed/scored + # --- The Annotate Step --- + candidates_with_score_query = models.Candidate.objects.filter( is_resume_parsed=True ).annotate( - score_as_text=KeyTextTransform( - 'match_score', - KeyTextTransform('scoring_data', F('ai_analysis_data')) + # 1. Use Coalesce to handle cases where the score might be missing or NULL + # (It defaults the value to 0 if missing). + # 2. Use Cast to convert the JSON value (which is often returned as text/string by the DB) + # into a proper IntegerField so we can perform math on it. + annotated_match_score=Coalesce( + Cast(SCORE_PATH, output_field=IntegerField()), + 0 ) - ).annotate( - # Cast the extracted text score to a FloatField so AVG() can operate on it. - sortable_score=Cast('score_as_text', output_field=FloatField()) ) + # Now calculate the average match score + avg_match_score_result = candidates_with_score_query.aggregate( + avg_score=Avg('annotated_match_score') + )['avg_score'] + avg_match_score = round(avg_match_score_result or 0, 1) - # 2b. AGGREGATE using the newly created 'sortable_score' field - avg_match_score_result = candidates_with_score.aggregate( - avg_score=Avg('sortable_score') - )['avg_score'] + hight_potential_count=0 + # --- The Filter Step for High Potential Candidates --- + candidates_with_score_gte_75 = candidates_with_score_query.filter( + annotated_match_score__gte=75 + ) + high_potential_count=candidates_with_score_gte_75.count() + high_potential_ratio = round((hight_potential_count / total_candidates) * 100, 1) if total_candidates > 0 else 0 - avg_match_score = round(avg_match_score_result or 0, 1) - - # 2c. Use the annotated QuerySet for other metrics - - # Scored Candidates Ratio (Now simpler, as we filtered the QuerySet) - total_scored = candidates_with_score.count() - scored_ratio = round((total_scored / total_candidates) * 100, 1) if total_candidates > 0 else 0 - - # High Potential Candidates (Filter the annotated QuerySet) - high_potential_count = candidates_with_score.filter( - sortable_score__gte=75 - ).count() - high_potential_ratio = round((high_potential_count / total_candidates) * 100, 1) if total_candidates > 0 else 0 + # Scored Candidates Ratio + total_scored_candidates = candidates_with_score_query.count() + scored_ratio = round((total_scored_candidates / total_candidates) * 100, 1) if total_candidates > 0 else 0 jobs=models.JobPosting.objects.all().order_by('internal_job_id') selected_job_pk=request.GET.get('selected_job_pk','') diff --git a/templates/meetings/meeting_details.html b/templates/meetings/meeting_details.html index 2d3761a..8df9080 100644 --- a/templates/meetings/meeting_details.html +++ b/templates/meetings/meeting_details.html @@ -3,199 +3,199 @@ {% block customCSS %} {% endblock %} @@ -203,251 +203,263 @@ body { {% block content %}
- {# --- TOP BAR / BACK BUTTON --- #} -
+ {# --- TOP BAR / BACK BUTTON & ACTIONS (EDIT/DELETE) --- #} +
+ {# Back Button #} {% trans "Back to Meetings" %} + + {# Edit and Delete Buttons #} +
+ + {% trans "Edit Meeting" %} + + {# DELETE MEETING FORM #} +
+ {% csrf_token %} + +
+
-
+ {# ========================================================= #} + {# --- SECTION 1: PROMINENT TOP DETAILS & JOIN INFO --- #} + {# ========================================================= #} +
- {# --- LEFT COLUMN (COMMENTS & INTERNAL CONTEXT) - Takes 50% of the screen #} -
- - {# --- 1. INTERNAL NOTES / DESCRIPTION CARD (New CRM Feature) --- #} - {% if meeting.description %} -
-
-

{% trans "Internal Context" %}

-

{% trans "Meeting agenda, purpose, or interview details for internal team use." %}

-
-

{{ meeting.description|safe }}

-
+ {# --- LEFT HALF: MAIN TOPIC & JOB CONTEXT --- #} +
+
+

+ + {{ meeting.topic|default:"[Meeting Topic N/A]" }} + + {{ meeting.status|title|default:'N/A' }} + +

+
+ + {# JOB CONTEXT DETAILS (Simple Divs) #} +
+

{% trans "Interview Detail" %}

+
+
{% trans "Job Title" %}:
{{ meeting.get_job.title|default:"N/A" }}
+ + +
{% trans "Job Type" %}:
{{ meeting.get_job.job_type|default:"N/A" }}
- {% endif %} +
- {# --- 2. Comments Section (Now in the Left Column) --- #} -
+ {# --- RIGHT HALF: ZOOM LINK / ACTIONS --- #} +
+
+

{% trans "Connection Details" %}

+
+
{% trans "Date & Time" %}:
{{ meeting.start_time|date:"M d, Y H:i"|default:"N/A" }}
+
{% trans "Duration" %}:
{{ meeting.duration|default:"N/A" }} {% trans "minutes" %}
+
{% trans "Meeting ID" %}:
{{ meeting.meeting_id|default:"N/A" }}
+
{% trans "Host Email" %}:
{{ meeting.host_email|default:"N/A" }}
+ {% if meeting.join_url %} +
+
{% trans "Copied!" %}
+ +
+
+ {% trans "Join URL" %}: + {{ meeting.join_url }} +
+ +
+
+ {% endif %} +
+
+
+ +
+ + {# ========================================================= #} + {# --- SECTION 2: PERSONNEL TABLES --- #} + {# ========================================================= #} +
+ + + {# --- PARTICIPANTS TABLE --- #} +
+
+

{% trans "Assigned Participants" %}

+ + + + + + + + + + + + + {% for participant in meeting.get_external_participants %} + + + + + + + + {% endfor %} + {% for participant in meeting.get_users_participants %} + + + + + + + + {% endfor %} + + +
{% trans "Name" %}{% trans "Role/Designation" %}{% trans "Email" %}{% trans "Phone Number" %}{% trans "Source Type" %}
{{participant.name}}{{participant.designation}}{{participant.email}}{{participant.phone}}{% trans "External Participants" %}
{{participant.name}}{{participant.designation}}{{participant.email}}{{participant.phone}}{% trans "System User" %}
+
+
+
+ + {# ========================================================= #} + {# --- SECTION 3: COMMENTS (CORRECTED) --- #} + {# ========================================================= #} +
+ +
+
- {% trans "Comments" %} ({{ meeting.comments.count }}) + {% trans "Comments" %} ({% if meeting.comments %}{{ meeting.comments.count }}{% else %}0{% endif %})
- {% if user.is_authenticated %} - - {% endif %}
-
+ + {# 1. COMMENT DISPLAY & IN-PAGE EDIT FORMS #} +
{% if meeting.comments.all %} {% for comment in meeting.comments.all|dictsortreversed:"created_at" %} -
-
-
- {{ comment.author.get_full_name|default:comment.author.username }} - {% if comment.author != user %} - {% trans "Comment" %} - {% endif %} -
- {{ comment.created_at|date:"M d, Y P" }} -
-
-

{{ comment.content|safe }}

-
- -
-
+ +
- {# --- RIGHT COLUMN (MAIN DETAILS & JOIN INFO) - Takes 50% of the screen #} -
-
- - {# --- CRM ASSOCIATED RECORD CARD (Elevated Importance) --- #} - {% if meeting.interview %} -
-
-
- -
-
{% trans "Associated Record" %}
- - - {{ meeting.interview.candidate.name }} - - - {{ meeting.interview.job_position }} + {# 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 %} - - {# --- 1. MAIN DETAILS CARD --- #} -
- {# --- CONSOLIDATED HEADER --- #} -
-
-
-

- - - - - {{ meeting.topic }} -

-
- - {{ meeting.status|title }} - -
-
-
-
- - {# --- CONNECTION DETAIL BODY (Renamed from Core Details) --- #} -
-

{% trans "Connection Details" %}

-
-
{% trans "Meeting ID" %}:
{{ meeting.meeting_id }}
-
{% trans "Start Time" %}:
{{ meeting.start_time|date:"M d, Y H:i" }}
-
{% trans "Duration" %}:
{{ meeting.duration }} {% trans "minutes" %}
-
{% trans "Timezone" %}:
{{ meeting.timezone|default:"UTC" }}
-
{% trans "Host Email" %}:
{{ meeting.host_email|default:"N/A" }}
-
-
- - {# --- ACTION BAR AT THE BOTTOM OF THE MAIN CARD --- #} - + + {% else %} +

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

+ {% endif %}
- - {# --- 2. JOIN INFO CARD (Separate from details, but in the same column) --- #} - {% if meeting.join_url %} -
-
-

{% trans "Join Information" %}

- - - {% trans "Join Meeting Now" %} - - -
- {# Message should not be display: none; but opacity: 0; for smooth transition #} -
{% trans "Copied!" %}
- -
-
- {% trans "Join URL" %}: - {{ meeting.join_url }} -
- -
-
- - {% if meeting.password %} -
-
{% trans "Password" %}:
-
{{ meeting.password }}
-
- {% endif %} -
-
- {% endif %} -
-
-
- - {# --- API RESPONSE CARD (Full width, hidden by default) --- #} - {% if meeting.zoom_gateway_response %} - - {% endif %} - -
- -{# MODALS (KEEP OUTSIDE OF THE MAIN LAYOUT ROWS) #} -{% comment %} {% include 'modals/delete_modal.html' with item_name="Meeting" delete_url_name='delete_meeting' %} {% endcomment %} - + {% endblock %} {% block customJS %}