message queryset #44
6
.env
6
.env
@ -1,3 +1,3 @@
|
|||||||
DB_NAME=norahuniversity
|
DB_NAME=haikal_db
|
||||||
DB_USER=norahuniversity
|
DB_USER=faheed
|
||||||
DB_PASSWORD=norahuniversity
|
DB_PASSWORD=Faheed@215
|
||||||
@ -897,6 +897,15 @@ class JobPostingStatusForm(forms.ModelForm):
|
|||||||
widgets = {
|
widgets = {
|
||||||
"status": forms.Select(attrs={"class": "form-select"}),
|
"status": forms.Select(attrs={"class": "form-select"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean_status(self):
|
||||||
|
status = self.cleaned_data.get("status")
|
||||||
|
if status == "ACTIVE":
|
||||||
|
if self.instance and self.instance.pk:
|
||||||
|
print(self.instance.assigned_to)
|
||||||
|
if not self.instance.assigned_to:
|
||||||
|
raise ValidationError("Please assign the job posting before setting it to Active.")
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
class LinkedPostContentForm(forms.ModelForm):
|
class LinkedPostContentForm(forms.ModelForm):
|
||||||
@ -2096,23 +2105,19 @@ class CandidateEmailForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from django.forms import HiddenInput
|
||||||
class MessageForm(forms.ModelForm):
|
class MessageForm(forms.ModelForm):
|
||||||
"""Form for creating and editing messages between users"""
|
"""Form for creating and editing messages between users"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Message
|
model = Message
|
||||||
fields = ["recipient", "job", "subject", "content", "message_type"]
|
fields = ["job","recipient", "subject", "content", "message_type"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"recipient": forms.Select(
|
"recipient": forms.Select(
|
||||||
attrs={"class": "form-select", "placeholder": "Select recipient","required": True,}
|
attrs={"class": "form-select", "placeholder": "Select recipient","required": True,}
|
||||||
),
|
),
|
||||||
"job": forms.Select(
|
"job": forms.Select(
|
||||||
attrs={"class": "form-select", "placeholder": "Select job",
|
attrs={"class": "form-select", "placeholder": "Select job"}
|
||||||
"hx-get": "/en/messages/create/",
|
|
||||||
"hx-target": "#id_recipient",
|
|
||||||
"hx-select": "#id_recipient",
|
|
||||||
"hx-swap": "outerHTML",}
|
|
||||||
),
|
),
|
||||||
"subject": forms.TextInput(
|
"subject": forms.TextInput(
|
||||||
attrs={
|
attrs={
|
||||||
@ -2216,6 +2221,8 @@ class MessageForm(forms.ModelForm):
|
|||||||
self.fields["recipient"].queryset = User.objects.filter(
|
self.fields["recipient"].queryset = User.objects.filter(
|
||||||
user_type="staff"
|
user_type="staff"
|
||||||
).order_by("username")
|
).order_by("username")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Validate message form data"""
|
"""Validate message form data"""
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.6 on 2025-12-02 10:27
|
# Generated by Django 5.2.7 on 2025-12-02 14:21
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
|
|||||||
@ -519,6 +519,7 @@ def job_detail(request, slug):
|
|||||||
job_status = status_form.cleaned_data["status"]
|
job_status = status_form.cleaned_data["status"]
|
||||||
form_template = job.form_template
|
form_template = job.form_template
|
||||||
if job_status == "ACTIVE":
|
if job_status == "ACTIVE":
|
||||||
|
|
||||||
form_template.is_active = True
|
form_template.is_active = True
|
||||||
form_template.save(update_fields=["is_active"])
|
form_template.save(update_fields=["is_active"])
|
||||||
else:
|
else:
|
||||||
@ -535,7 +536,9 @@ def job_detail(request, slug):
|
|||||||
|
|
||||||
return redirect("job_detail", slug=slug)
|
return redirect("job_detail", slug=slug)
|
||||||
else:
|
else:
|
||||||
messages.error(request, "Failed to update status due to validation errors.")
|
error_messages = status_form.errors.get('status', [])
|
||||||
|
formatted_errors = "<br>".join(error_messages)
|
||||||
|
messages.error(request, f"{formatted_errors}")
|
||||||
|
|
||||||
# --- 2. Quality Metrics (JSON Aggregation) ---
|
# --- 2. Quality Metrics (JSON Aggregation) ---
|
||||||
|
|
||||||
@ -607,10 +610,13 @@ def job_detail(request, slug):
|
|||||||
if avg_t_in_exam_duration
|
if avg_t_in_exam_duration
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
category_data = (
|
category_data = (
|
||||||
applications.filter(ai_analysis_data__analysis_data_en__category__isnull=False)
|
applications.filter(
|
||||||
.values("ai_analysis_data__analysis_data_en__category")
|
ai_analysis_data__analysis_data_en__category__isnull=False
|
||||||
|
).exclude(
|
||||||
|
ai_analysis_data__analysis_data_en__category__exact=None
|
||||||
|
).values("ai_analysis_data__analysis_data_en__category")
|
||||||
.annotate(
|
.annotate(
|
||||||
application_count=Count("id"),
|
application_count=Count("id"),
|
||||||
category=Cast(
|
category=Cast(
|
||||||
@ -619,6 +625,7 @@ def job_detail(request, slug):
|
|||||||
)
|
)
|
||||||
.order_by("ai_analysis_data__analysis_data_en__category")
|
.order_by("ai_analysis_data__analysis_data_en__category")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prepare data for Chart.js
|
# Prepare data for Chart.js
|
||||||
categories = [item["category"] for item in category_data]
|
categories = [item["category"] for item in category_data]
|
||||||
applications_count = [item["application_count"] for item in category_data]
|
applications_count = [item["application_count"] for item in category_data]
|
||||||
@ -4688,7 +4695,7 @@ def message_detail(request, message_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def message_create(request):
|
def message_create(request):
|
||||||
"""Create a new message"""
|
"""Create a new message"""
|
||||||
from .email_service import EmailService
|
from .email_service import EmailService
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = MessageForm(request.user, request.POST)
|
form = MessageForm(request.user, request.POST)
|
||||||
|
|
||||||
@ -4699,10 +4706,7 @@ def message_create(request):
|
|||||||
# Send email if message_type is 'email' and recipient has email
|
# Send email if message_type is 'email' and recipient has email
|
||||||
|
|
||||||
if message.recipient and message.recipient.email:
|
if message.recipient and message.recipient.email:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
||||||
email_result = async_task('recruitment.tasks._task_send_individual_email',
|
email_result = async_task('recruitment.tasks._task_send_individual_email',
|
||||||
subject=message.subject,
|
subject=message.subject,
|
||||||
body_message=message.content,
|
body_message=message.content,
|
||||||
@ -4730,9 +4734,35 @@ def message_create(request):
|
|||||||
|
|
||||||
messages.error(request, "Please correct the errors below.")
|
messages.error(request, "Please correct the errors below.")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
form = MessageForm(request.user)
|
form = MessageForm(request.user)
|
||||||
|
|
||||||
|
form.fields["job"].widget.attrs.update({"hx-get": "/en/messages/create/",
|
||||||
|
"hx-target": "#id_recipient",
|
||||||
|
"hx-select": "#id_recipient",
|
||||||
|
"hx-swap": "outerHTML",})
|
||||||
|
if request.user.user_type == "staff":
|
||||||
|
job_id = request.GET.get("job")
|
||||||
|
if job_id:
|
||||||
|
job = get_object_or_404(JobPosting, id=job_id)
|
||||||
|
applications=job.applications.all()
|
||||||
|
applicant_users = User.objects.filter(person_profile__in=applications.values_list('person', flat=True))
|
||||||
|
agency_users = User.objects.filter(id__in=AgencyJobAssignment.objects.filter(job=job).values_list('agency__user', flat=True))
|
||||||
|
form.fields["recipient"].queryset = applicant_users | agency_users
|
||||||
|
|
||||||
|
|
||||||
|
# form.fields["recipient"].queryset = User.objects.filter(person_profile__)
|
||||||
|
else:
|
||||||
|
|
||||||
|
form.fields['recipient'].widget = HiddenInput()
|
||||||
|
if request.method == "GET" and "HX-Request" in request.headers and request.user.user_type in ["candidate","agency"]:
|
||||||
|
print()
|
||||||
|
job_id = request.GET.get("job")
|
||||||
|
if job_id:
|
||||||
|
job = get_object_or_404(JobPosting, id=job_id)
|
||||||
|
form.fields["recipient"].queryset = User.objects.filter(id=job.assigned_to.id)
|
||||||
|
form.fields["recipient"].initial = job.assigned_to
|
||||||
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"form": form,
|
"form": form,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "portal_base.html" %}
|
{% extends "portal_base.html" %}
|
||||||
{% load static %}
|
{% load static crispy_forms_tags %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}{% if form.instance.pk %}{% trans "Reply to Message" %}{% else %}{% trans "Compose Message" %}{% endif %}{% endblock %}
|
{% block title %}{% if form.instance.pk %}{% trans "Reply to Message" %}{% else %}{% trans "Compose Message" %}{% endif %}{% endblock %}
|
||||||
|
|
||||||
@ -11,23 +11,23 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
{% if form.instance.pk %}
|
{% if form.instance.pk %}
|
||||||
<i class="fas fa-reply"></i> Reply to Message
|
<i class="fas fa-reply"></i> {% trans "Reply to Message" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fas fa-envelope"></i> Compose Message
|
<i class="fas fa-envelope"></i> {% trans "Compose Message" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if form.instance.parent_message %}
|
{% if form.instance.parent_message %}
|
||||||
<div class="alert alert-info mb-4">
|
<div class="alert alert-info mb-4">
|
||||||
<strong>Replying to:</strong> {{ form.instance.parent_message.subject }}
|
<strong>{% trans "Replying to:" %}</strong> {{ form.instance.parent_message.subject }}
|
||||||
<br>
|
<br>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
From {{ form.instance.parent_message.sender.get_full_name|default:form.instance.parent_message.sender.username }}
|
{% trans "From" %} {{ form.instance.parent_message.sender.get_full_name|default:form.instance.parent_message.sender.username }}
|
||||||
on {{ form.instance.parent_message.created_at|date:"M d, Y H:i" }}
|
{% trans "on" %} {{ form.instance.parent_message.created_at|date:"M d, Y H:i" }}
|
||||||
</small>
|
</small>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<strong>Original message:</strong>
|
<strong>{% trans "Original message:" %}</strong>
|
||||||
<div class="border-start ps-3 mt-2">
|
<div class="border-start ps-3 mt-2">
|
||||||
{{ form.instance.parent_message.content|linebreaks }}
|
{{ form.instance.parent_message.content|linebreaks }}
|
||||||
</div>
|
</div>
|
||||||
@ -38,99 +38,17 @@
|
|||||||
<form method="post" id="messageForm">
|
<form method="post" id="messageForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row">
|
{{form|crispy}}
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.job.id_for_label }}" class="form-label">
|
|
||||||
Related Job <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.job }}
|
|
||||||
{% if form.job.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{{ form.job.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">
|
|
||||||
Select a job if this message is related to a specific position
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.recipient.id_for_label }}" class="form-label">
|
|
||||||
Recipient <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.recipient }}
|
|
||||||
|
|
||||||
{% if form.recipient.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{{ form.recipient.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">
|
|
||||||
Select the user who will receive this message
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.message_type.id_for_label }}" class="form-label">
|
|
||||||
Message Type <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.message_type }}
|
|
||||||
{% if form.message_type.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{{ form.message_type.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">
|
|
||||||
Select the type of message you're sending
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.subject.id_for_label }}" class="form-label">
|
|
||||||
Subject <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.subject }}
|
|
||||||
{% if form.subject.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{{ form.subject.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.content.id_for_label }}" class="form-label">
|
|
||||||
Message <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.content }}
|
|
||||||
{% if form.content.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{{ form.content.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">
|
|
||||||
Write your message here. You can use line breaks and basic formatting.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<a href="{% url 'message_list' %}" class="btn btn-secondary">
|
<a href="{% url 'message_list' %}" class="btn btn-outline-primary">
|
||||||
<i class="fas fa-times"></i> Cancel
|
<i class="fas fa-times"></i> {% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-paper-plane"></i>
|
<i class="fas fa-paper-plane"></i>
|
||||||
{% if form.instance.pk %}
|
{% if form.instance.pk %}
|
||||||
Send Reply
|
{% trans "Send Reply" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
Send Message
|
{% trans "Send Message" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -184,6 +102,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Character counter for subject
|
// Character counter for subject
|
||||||
const subjectField = document.getElementById('id_subject');
|
const subjectField = document.getElementById('id_subject');
|
||||||
const maxLength = 200;
|
const maxLength = 200;
|
||||||
|
const charsLabel = "{% trans 'characters' %}";
|
||||||
|
|
||||||
if (subjectField) {
|
if (subjectField) {
|
||||||
// Add character counter display
|
// Add character counter display
|
||||||
@ -194,7 +113,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
function updateCounter() {
|
function updateCounter() {
|
||||||
const remaining = maxLength - subjectField.value.length;
|
const remaining = maxLength - subjectField.value.length;
|
||||||
counter.textContent = `${subjectField.value.length}/${maxLength} characters`;
|
counter.textContent = `${subjectField.value.length}/${maxLength} ${charsLabel}`;
|
||||||
if (remaining < 20) {
|
if (remaining < 20) {
|
||||||
counter.className = 'text-warning';
|
counter.className = 'text-warning';
|
||||||
} else {
|
} else {
|
||||||
@ -216,19 +135,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
if (!recipient) {
|
if (!recipient) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('Please select a recipient.');
|
alert("{% trans 'Please select a recipient.' %}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!subject) {
|
if (!subject) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('Please enter a subject.');
|
alert("{% trans 'Please enter a subject.' %}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('Please enter a message.');
|
alert("{% trans 'Please enter a message.' %}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,95 +4,90 @@
|
|||||||
{% block title %}{{ message.subject }}{% endblock %}
|
{% block title %}{{ message.subject }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid py-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<!-- Message Header -->
|
<!-- Message Header -->
|
||||||
<div class="card mb-4">
|
<div class="card shadow-sm mb-4 border-0">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header bg-gradient border-0 pb-3">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
{{ message.subject }}
|
<div>
|
||||||
{% if message.parent_message %}
|
<h4 class="mb-2 fw-bold">{{ message.subject }}</h4>
|
||||||
<span class="badge bg-secondary ms-2">{% trans "Reply" %}</span>
|
{% if message.parent_message %}
|
||||||
{% endif %}
|
<span class="badge bg-info">{% trans "Reply" %}</span>
|
||||||
</h5>
|
{% endif %}
|
||||||
<div class="btn-group" role="group">
|
</div>
|
||||||
<a href="{% url 'message_reply' message.id %}" class="btn btn-outline-info">
|
<div class="btn-group flex-wrap" role="group">
|
||||||
<i class="fas fa-reply"></i> {% trans "Reply" %}
|
<a href="{% url 'message_reply' message.id %}" class="btn btn-sm btn-main-action">
|
||||||
</a>
|
<i class="fas fa-reply me-1"></i> {% trans "Reply" %}
|
||||||
{% if message.recipient == request.user %}
|
|
||||||
<a href="{% url 'message_mark_unread' message.id %}"
|
|
||||||
class="btn btn-outline-warning"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-post="{% url 'message_mark_unread' message.id %}">
|
|
||||||
<i class="fas fa-envelope"></i> {% trans "Mark Unread" %}
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% if message.recipient == request.user %}
|
||||||
<button type="button"
|
<button type="button" class="btn btn-sm btn-outline-warning"
|
||||||
class="btn btn-danger btn-lg"
|
hx-post="{% url 'message_mark_unread' message.id %}"
|
||||||
hx-post="{% url 'message_delete' message.id %}"
|
hx-swap="outerHTML">
|
||||||
hx-confirm="{% trans 'Are you sure you want to permanently delete this message? This action cannot be undone.' %}"
|
<i class="fas fa-envelope me-1"></i> {% trans "Mark Unread" %}
|
||||||
hx-redirect="{% url 'message_list' %}"
|
</button>
|
||||||
onclick="this.disabled=true;"
|
{% endif %}
|
||||||
title="{% trans 'Delete Message' %}">
|
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete Message" %}
|
hx-post="{% url 'message_delete' message.id %}"
|
||||||
</button>
|
hx-confirm="{% trans 'Are you sure you want to permanently delete this message? This action cannot be undone.' %}"
|
||||||
<a href="{% url 'message_list' %}" class="btn btn-outline-secondary">
|
hx-redirect="{% url 'message_list' %}"
|
||||||
<i class="fas fa-arrow-left"></i> {% trans "Back to Messages" %}
|
onclick="this.disabled=true;">
|
||||||
</a>
|
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'message_list' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row mb-3">
|
<div class="row g-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "From:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "From:" %}</small>
|
||||||
<span class="text-primary">{{ message.sender.get_full_name|default:message.sender.username }}</span>
|
<span class="fw-semibold">{{ message.sender.get_full_name|default:message.sender.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "To:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "To:" %}</small>
|
||||||
<span class="text-primary">{{ message.recipient.get_full_name|default:message.recipient.username }}</span>
|
<span class="fw-semibold">{{ message.recipient.get_full_name|default:message.recipient.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "Type:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "Type:" %}</small>
|
||||||
<span class="badge bg-{{ message.message_type|lower }}">
|
<span class="badge bg-primary-theme">
|
||||||
{{ message.get_message_type_display }}
|
{{message.get_message_type_display}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "Status:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "Status:" %}</small>
|
||||||
{% if message.is_read %}
|
{% if message.is_read %}
|
||||||
<span class="badge bg-success">{% trans "Read" %}</span>
|
<span class="badge bg-success">{% trans "Read" %}</span>
|
||||||
{% if message.read_at %}
|
{% if message.read_at %}
|
||||||
<small class="text-muted">({{ message.read_at|date:"M d, Y H:i" }})</small>
|
<small class="text-muted">({{ message.read_at|date:"M d, Y H:i" }})</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-warning">{% trans "Unread" %}</span>
|
<span class="badge bg-warning text-dark">{% trans "Unread" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "Created:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "Created:" %}</small>
|
||||||
<span>{{ message.created_at|date:"M d, Y H:i" }}</span>
|
<span>{{ message.created_at|date:"M d, Y H:i" }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if message.job %}
|
{% if message.job %}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>{% trans "Related Job:" %}</strong>
|
<small class="text-muted d-block mb-1">{% trans "Related Job:" %}</small>
|
||||||
<a href="{% url 'job_detail' message.job.slug %}" class="text-primary">
|
<a href="{% url 'job_detail' message.job.slug %}" class="fw-semibold text-decoration-none text-primary-theme">
|
||||||
{{ message.job.title }}
|
{{ message.job.title }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if message.parent_message %}
|
{% if message.parent_message %}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info mt-3 mb-0 border-0">
|
||||||
<strong>{% trans "In reply to:" %}</strong>
|
<strong>{% trans "In reply to:" %}</strong>
|
||||||
<a href="{% url 'message_detail' message.parent_message.id %}">
|
<a href="{% url 'message_detail' message.parent_message.id %}" class="text-decoration-none">
|
||||||
{{ message.parent_message.subject }}
|
{{ message.parent_message.subject }}
|
||||||
</a>
|
</a>
|
||||||
<small class="text-muted d-block">
|
<small class="text-muted d-block mt-2">
|
||||||
{% trans "From" %} {{ message.parent_message.sender.get_full_name|default:message.parent_message.sender.username }}
|
{% trans "From" %} {{ message.parent_message.sender.get_full_name|default:message.parent_message.sender.username }}
|
||||||
{% trans "on" %} {{ message.parent_message.created_at|date:"M d, Y H:i" }}
|
{% trans "on" %} {{ message.parent_message.created_at|date:"M d, Y H:i" }}
|
||||||
</small>
|
</small>
|
||||||
@ -102,10 +97,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message Content -->
|
<!-- Message Content -->
|
||||||
<div class="card">
|
<div class="card shadow-sm border-0 mb-4">
|
||||||
<div class="card-header">
|
|
||||||
<h6 class="mb-0">{% trans "Message" %}</h6>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
{{ message.content|linebreaks }}
|
{{ message.content|linebreaks }}
|
||||||
@ -115,15 +107,15 @@
|
|||||||
|
|
||||||
<!-- Message Thread (if this is a reply and has replies) -->
|
<!-- Message Thread (if this is a reply and has replies) -->
|
||||||
{% if message.replies.all %}
|
{% if message.replies.all %}
|
||||||
<div class="card mt-4">
|
<div class="card shadow-sm border-0">
|
||||||
<div class="card-header">
|
<div class="card-header bg-light border-0">
|
||||||
<h6 class="mb-0">
|
<h6 class="mb-0">
|
||||||
<i class="fas fa-comments"></i> {% trans "Replies" %} ({{ message.replies.count }})
|
<i class="fas fa-comments text-primary"></i> {% trans "Replies" %} <span class="badge bg-primary">{{ message.replies.count }}</span>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% for reply in message.replies.all %}
|
{% for reply in message.replies.all %}
|
||||||
<div class="border-start ps-3 mb-3">
|
<div class="message-reply mb-3 p-3 bg-light rounded">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ reply.sender.get_full_name|default:reply.sender.username }}</strong>
|
<strong>{{ reply.sender.get_full_name|default:reply.sender.username }}</strong>
|
||||||
@ -135,14 +127,12 @@
|
|||||||
{{ reply.get_message_type_display }}
|
{{ reply.get_message_type_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="reply-content">
|
<div class="reply-content mb-3">
|
||||||
{{ reply.content|linebreaks }}
|
{{ reply.content|linebreaks }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<a href="{% url 'message_reply' reply.id %}" class="btn btn-sm btn-outline-primary">
|
||||||
<a href="{% url 'message_reply' reply.id %}" class="btn btn-sm btn-outline-info">
|
<i class="fas fa-reply me-1"></i> {% trans "Reply" %}
|
||||||
<i class="fas fa-reply"></i> {% trans "Reply to this" %}
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -153,31 +143,37 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
.message-content {
|
.message-content {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
padding: 1rem;
|
padding: 1.5rem;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.5rem;
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-content {
|
.reply-content {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-size: 0.9rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-start {
|
|
||||||
border-left: 3px solid #0d6efd;
|
|
||||||
|
.message-reply:hover {
|
||||||
|
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ps-3 {
|
.bg-gradient {
|
||||||
padding-left: 1rem;
|
background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group .btn {
|
||||||
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user