more update in css and templates
This commit is contained in:
parent
6b74990791
commit
3682657527
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -41,11 +41,11 @@ urlpatterns = [
|
||||
path('training/<slug:slug>/delete/', views_frontend.TrainingDeleteView.as_view(), name='training_delete'),
|
||||
|
||||
# Meeting URLs
|
||||
path('', views.ZoomMeetingListView.as_view(), name='list_meetings'),
|
||||
path('create-meeting/', views.ZoomMeetingCreateView.as_view(), name='create_meeting'),
|
||||
path('meeting-details/<slug:slug>/', views.ZoomMeetingDetailsView.as_view(), name='meeting_details'),
|
||||
path('update-meeting/<slug:slug>/', views.ZoomMeetingUpdateView.as_view(), name='update_meeting'),
|
||||
path('delete-meeting/<slug:slug>/', views.ZoomMeetingDeleteView, name='delete_meeting'),
|
||||
path('meetings/', views.ZoomMeetingListView.as_view(), name='list_meetings'),
|
||||
path('meetings/create-meeting/', views.ZoomMeetingCreateView.as_view(), name='create_meeting'),
|
||||
path('meetings/meeting-details/<slug:slug>/', views.ZoomMeetingDetailsView.as_view(), name='meeting_details'),
|
||||
path('meetings/update-meeting/<slug:slug>/', views.ZoomMeetingUpdateView.as_view(), name='update_meeting'),
|
||||
path('meetings/delete-meeting/<slug:slug>/', views.ZoomMeetingDeleteView, name='delete_meeting'),
|
||||
|
||||
# JobPosting functional views URLs (keeping for compatibility)
|
||||
path('api/create/', views.create_job, name='create_job_api'),
|
||||
@ -105,7 +105,6 @@ urlpatterns = [
|
||||
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'),
|
||||
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/delete_meeting_for_candidate/<int:meeting_id>/', views.delete_meeting_for_candidate, name='delete_meeting_for_candidate'),
|
||||
|
||||
|
||||
# users urls
|
||||
path('user/<int:pk>',views.user_detail,name='user_detail')
|
||||
]
|
||||
|
||||
@ -92,7 +92,7 @@ class JobCandidatesListView(LoginRequiredMixin, ListView):
|
||||
context_object_name = 'candidates'
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
# Get the job by slug
|
||||
@ -239,75 +239,14 @@ def candidate_detail(request, slug):
|
||||
|
||||
def candidate_update_stage(request, slug):
|
||||
"""Handle HTMX stage update requests"""
|
||||
try:
|
||||
if not request.user.is_staff:
|
||||
return render(request, 'recruitment/partials/error.html', {'error': 'Permission denied'}, status=403)
|
||||
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
|
||||
if request.method != 'POST':
|
||||
return render(request, 'recruitment/partials/error.html', {'error': 'Only POST method is allowed'}, status=405)
|
||||
|
||||
# Handle form data
|
||||
form = forms.CandidateStageForm(request.POST, candidate=candidate)
|
||||
if form.is_valid():
|
||||
stage_value = form.cleaned_data['stage']
|
||||
|
||||
# Validate stage value
|
||||
valid_stages = [choice[0] for choice in models.Candidate.Stage.choices]
|
||||
if stage_value not in valid_stages:
|
||||
return render(request, 'recruitment/partials/error.html', {'error': f'Invalid stage value. Must be one of: {", ".join(valid_stages)}'}, status=400)
|
||||
|
||||
# Check transition rules
|
||||
if candidate.pk and stage_value != candidate.stage:
|
||||
old_stage = candidate.stage
|
||||
if not candidate.can_transition_to(stage_value):
|
||||
return render(request, 'recruitment/partials/error.html', {'error': f'Cannot transition from "{old_stage}" to "{stage_value}". Transition not allowed.'}, status=400)
|
||||
|
||||
# Update the stage
|
||||
old_stage = candidate.stage
|
||||
candidate.stage = stage_value
|
||||
candidate.save()
|
||||
|
||||
# Return success template
|
||||
context = {
|
||||
'form': form,
|
||||
'success': True,
|
||||
'message': f'Stage updated from "{old_stage}" to "{candidate.stage}"',
|
||||
'new_stage': candidate.stage,
|
||||
'new_stage_display': candidate.get_stage_display(),
|
||||
'candidate': candidate
|
||||
}
|
||||
messages.success(request,"Candidate Stage Updated")
|
||||
return redirect("candidate_detail",slug=candidate.slug)
|
||||
def response():
|
||||
stage_form = forms.CandidateStageForm(candidate=candidate)
|
||||
context['stage_form'] = stage_form
|
||||
stage_form_partial = render_to_string('recruitment/partials/stage_update_modal.html#id-stage', context)
|
||||
success_html = render_to_string('recruitment/partials/stage_update_success.html', context)
|
||||
yield SSE.patch_elements(stage_form_partial,"#id_stage")
|
||||
yield SSE.patch_elements(success_html,"#availableStagesInfo")
|
||||
yield SSE.patch_signals({'stage':candidate.stage})
|
||||
|
||||
return DatastarResponse(response())
|
||||
# return render(request, 'recruitment/partials/stage_update_success.html', context)
|
||||
else:
|
||||
# Return form with errors
|
||||
context = {
|
||||
'form': form,
|
||||
'candidate': candidate,
|
||||
'stage_form': forms.CandidateStageForm(candidate=candidate)
|
||||
}
|
||||
return render(request, 'recruitment/partials/stage_update_form.html', context)
|
||||
|
||||
except Exception as e:
|
||||
# Log the error for debugging
|
||||
import traceback
|
||||
error_details = traceback.format_exc()
|
||||
print(f"Error in candidate_update_stage: {error_details}")
|
||||
|
||||
return render(request, 'partials/error.html', {'error': f'Internal server error: {str(e)}'}, status=500)
|
||||
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
form = forms.CandidateStageForm(request.POST, candidate=candidate)
|
||||
if form.is_valid():
|
||||
stage_value = form.cleaned_data['stage']
|
||||
candidate.stage = stage_value
|
||||
candidate.save(update_fields=['stage'])
|
||||
messages.success(request,"Candidate Stage Updated")
|
||||
return redirect("candidate_detail",slug=candidate.slug)
|
||||
|
||||
class TrainingListView(LoginRequiredMixin, ListView):
|
||||
model = models.TrainingMaterial
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
<style>
|
||||
|
||||
|
||||
</style>
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<div class="input-group flex-grow-1" style="max-width: 300px;">
|
||||
@ -15,10 +15,4 @@
|
||||
value="{{ search_query }}"
|
||||
aria-label="{% trans 'Search' %}">
|
||||
</div>
|
||||
{% comment %} <button type="submit" class="btn btn-primary">
|
||||
<svg class="heroicon icon-sm" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||
</svg>
|
||||
{% trans "Search" %}
|
||||
</button> {% endcomment %}
|
||||
</form>
|
||||
|
||||
@ -184,7 +184,7 @@
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply Filters" %}
|
||||
</button>
|
||||
{% if status_filter or search_query %}
|
||||
<a href="{% url 'meeting_list' %}" class="btn btn-outline-secondary btn-lg">
|
||||
<a href="{% url 'list_meetings' %}" class="btn btn-outline-secondary btn-lg">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@ -118,11 +118,11 @@
|
||||
padding: 1.5rem 1.25rem;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
/* ==================================== */
|
||||
/* NEW: Vertical Timeline Styling */
|
||||
/* ==================================== */
|
||||
|
||||
|
||||
/* Highlight box for the current stage */
|
||||
.current-stage {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
@ -131,7 +131,7 @@
|
||||
.current-stage .text-primary {
|
||||
color: var(--kaauh-teal) !important;
|
||||
}
|
||||
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding-left: 2rem;
|
||||
@ -163,17 +163,17 @@
|
||||
color: white;
|
||||
font-size: 0.8rem;
|
||||
z-index: 10;
|
||||
border: 4px solid white;
|
||||
border: 4px solid white;
|
||||
}
|
||||
.timeline-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Custom Timeline Background Classes for Stages (Using Bootstrap color palette) */
|
||||
.timeline-bg-applied { background-color: var(--kaauh-teal) !important; }
|
||||
.timeline-bg-exam { background-color: #17a2b8 !important; }
|
||||
.timeline-bg-interview { background-color: #ffc107 !important; }
|
||||
.timeline-bg-offer { background-color: #28a745 !important; }
|
||||
.timeline-bg-applied { background-color: var(--kaauh-teal) !important; }
|
||||
.timeline-bg-exam { background-color: #17a2b8 !important; }
|
||||
.timeline-bg-interview { background-color: #ffc107 !important; }
|
||||
.timeline-bg-offer { background-color: #28a745 !important; }
|
||||
.timeline-bg-rejected { background-color: #dc3545 !important; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -187,7 +187,7 @@
|
||||
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Applicant Detail</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
{# LEFT COLUMN: MAIN CANDIDATE DETAILS AND TABS #}
|
||||
<div class="col-lg-8">
|
||||
@ -199,7 +199,7 @@
|
||||
<div>
|
||||
<h1 class="h3 mb-2">{{ candidate.name }}</h1>
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
|
||||
|
||||
<span id="stageDisplay" class="badge"
|
||||
data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}"
|
||||
data-signals-stage="'{{ candidate.stage }}'">
|
||||
@ -345,7 +345,7 @@
|
||||
<h5 class="mb-0 text-muted"><i class="fas fa-route me-2"></i>{% trans "Candidate Journey" %}</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
|
||||
|
||||
<h6 class="text-uppercase text-secondary mb-3">{% trans "Current Stage" %}</h6>
|
||||
<div class="p-3 mb-4 rounded current-stage">
|
||||
<p class="mb-0 fw-bold fs-5 text-primary">{{ candidate.stage }}</p>
|
||||
@ -356,8 +356,8 @@
|
||||
|
||||
<h6 class="text-uppercase text-secondary mb-3 pt-2 border-top">{% trans "Historical Timeline" %}</h6>
|
||||
<div class="timeline">
|
||||
|
||||
|
||||
|
||||
|
||||
{# Base Status: Application Submitted (Always required) #}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon timeline-bg-applied"><i class="fas fa-file-signature"></i></div>
|
||||
@ -365,11 +365,11 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Application Submitted" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.created_at|date:"M d, Y" }}
|
||||
<span class="ms-2">|</span>
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.created_at|date:"h:i A" }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% if candidate.exam_date %}
|
||||
<div class="timeline-item">
|
||||
@ -378,11 +378,11 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Exam" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.exam_date|date:"M d, Y" }}
|
||||
<span class="ms-2">|</span>
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.exam_date|date:"h:i A" }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if candidate.interview_date %}
|
||||
@ -392,11 +392,11 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.interview_date|date:"M d, Y" }}
|
||||
<span class="ms-2">|</span>
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.interview_date|date:"h:i A" }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -407,21 +407,18 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }}
|
||||
<span class="ms-2">|</span>
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.offer_date|date:"h:i A" }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -19,66 +19,21 @@
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Current Stage:</strong>
|
||||
<span class="badge bg-primary ms-2" id="currentStageBadge" data-text="$stage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage Progress Visualization -->
|
||||
<div class="mb-4" >
|
||||
<h6 class="text-muted mb-3">Application Pipeline</h6>
|
||||
<div class="progress-stages">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="stage-item {% if candidate.stage == 'Applied' %}active{% endif %}" data-stage="Applied">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-user-check"></i>
|
||||
</div>
|
||||
<div class="stage-label">Applied</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div data-class="{active: $stage == 'Exam', completed: $stage == 'Interview' || $stage == 'Offer'}" class="stage-connector"></div>
|
||||
|
||||
<div data-class="{active: $stage == 'Exam', completed: $stage == 'Offer' || $stage == 'Interview'}" class="stage-item" data-stage="Exam">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-clipboard-check"></i>
|
||||
</div>
|
||||
<div class="stage-label">Exam</div>
|
||||
</div>
|
||||
<div data-class="{active: $stage == 'Interview', completed: $stage == 'Offer'}" class="stage-connector"></div>
|
||||
|
||||
<div data-class="{active: $stage == 'Interview', completed: $stage == 'Offer'}" class="stage-item" data-stage="Interview">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-comments"></i>
|
||||
</div>
|
||||
<div class="stage-label">Interview</div>
|
||||
</div>
|
||||
<div data-class="{active: $stage == 'Offer'}" class="stage-connector"></div>
|
||||
<div data-class="{active: $stage == 'Offer', completed: $stage == 'Offer'}" class="stage-item" data-stage="Offer">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-handshake"></i>
|
||||
</div>
|
||||
<div class="stage-label">Offer</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-primary ms-2" id="currentStageBadge">{{candidate.stage}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Container -->
|
||||
<div id="stageUpdateFormContainer" data-show="$stage != 'Offer'">
|
||||
{% comment %} <form id="stageUpdateForm" hx-post="{% url 'candidate_update_stage' candidate.slug %}" hx-target="#stageUpdateFormContainer" hx-swap="outerHTML" hx-indicator="#loadingIndicator"> {% endcomment %}
|
||||
{% url 'candidate_update_stage' candidate.slug as stage_update_url %}
|
||||
<form id="stageUpdateForm" data-on-submit="@post('{{ stage_update_url }}',{
|
||||
contentType: 'form',
|
||||
headers: {'X-CSRFToken': '{{ csrf_token }}'}})" data-indicator-fetching>
|
||||
<div id="stageUpdateFormContainer">
|
||||
<form id="stageUpdateForm" action="{% url 'candidate_update_stage' candidate.slug %}" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Stage Selection -->
|
||||
<div class="mb-3">
|
||||
<label for="id_stage" class="form-label">
|
||||
<i class="fas fa-arrow-right me-1"></i>Move to Stage
|
||||
</label>
|
||||
{% partialdef id-stage %}
|
||||
<select data-attr-disabled="$fetching" id="id_stage" name="stage" class="form-select" onchange="updateStageInfo(this.value, '{{ candidate.stage }}')">
|
||||
<select id="id_stage" name="stage" class="form-select">
|
||||
{% for value, label in stage_form.stage.field.choices %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
@ -95,17 +50,15 @@
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
</button>
|
||||
|
||||
<button data-show="$stage != 'Offer'" type="submit" class="btn btn-primary" id="stageUpdateSubmit" data-attr-disabled="$fetching">
|
||||
<button type="submit" class="btn btn-primary" id="stageUpdateSubmit">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
<span data-show="$fetching" class="spinner-border spinner-border-sm ms-2" role="status" aria-hidden="true"></span>
|
||||
<span class="ms-2" data-text="$fetching ? 'Updating...' : 'Update'"></span>
|
||||
<span class="ms-2">Update</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user