[UPDT] EMPLOYEE: Updated employee dashboard
This commit is contained in:
@@ -179,29 +179,57 @@ def get_template(request, emp_id):
|
||||
@login_required
|
||||
def get_mail_preview(request):
|
||||
"""
|
||||
This method is used to return the mail template
|
||||
Returns the mail template preview as HTML.
|
||||
"""
|
||||
body = request.GET.get("body")
|
||||
template_bdy = template.Template(body)
|
||||
body = request.POST.get("body")
|
||||
if not body:
|
||||
return HttpResponse("No body provided", status=400)
|
||||
|
||||
emp_id = request.GET.get("emp_id")
|
||||
if emp_id:
|
||||
employee = Employee.objects.get(id=emp_id)
|
||||
context = template.Context(
|
||||
{
|
||||
"instance": employee,
|
||||
"self": request.user.employee_get,
|
||||
"request": request,
|
||||
}
|
||||
employee_ids = request.POST.getlist("employees")
|
||||
|
||||
# Fetch one employee for preview if provided
|
||||
employee_obj = None
|
||||
if emp_id or employee_ids:
|
||||
ids = [emp_id] if emp_id else employee_ids
|
||||
employee_obj = Employee.objects.filter(id__in=ids).first()
|
||||
if not employee_obj:
|
||||
return HttpResponse("Employee not found", status=404)
|
||||
|
||||
# Build context
|
||||
context = {
|
||||
"instance": employee_obj,
|
||||
"model_instance": employee_obj,
|
||||
"self": getattr(request.user, "employee_get", None),
|
||||
"request": request,
|
||||
}
|
||||
|
||||
# Render template
|
||||
rendered_body = template.Template(body).render(template.Context(context)) or " "
|
||||
|
||||
# Add preview note if multiple employees
|
||||
if employee_ids and len(employee_ids) > 1 and employee_obj:
|
||||
rendered_body = (
|
||||
f"<p style='color:gray; font-size:13px;'>"
|
||||
f"Preview shown for {employee_obj.get_full_name()}. "
|
||||
f"Mail will be personalized for {len(employee_ids)} employees."
|
||||
f"</p>{rendered_body}"
|
||||
)
|
||||
body = template_bdy.render(context) or " "
|
||||
return JsonResponse({"body": body})
|
||||
|
||||
# Wrap in styled div
|
||||
textarea_field = (
|
||||
f'<div class="oh-input oh-input--textarea" '
|
||||
f'style="border: solid .1px #dbd7d7; padding:5px;">{rendered_body}</div>'
|
||||
)
|
||||
|
||||
return HttpResponse(textarea_field, content_type="text/html")
|
||||
|
||||
|
||||
@login_required
|
||||
@manager_can_enter(perm="recruitment.change_employee")
|
||||
@manager_can_enter(perm="employee.change_employee")
|
||||
def send_mail_to_employee(request):
|
||||
"""
|
||||
This method is used to send acknowledgement mail to the candidate
|
||||
This method is used to send acknowledgement mail to the employee
|
||||
"""
|
||||
employee_id = request.POST["id"]
|
||||
subject = request.POST.get("subject")
|
||||
|
||||
@@ -1,186 +1,190 @@
|
||||
{% load i18n %}
|
||||
<div id="ack-message-{{employee.id}}">
|
||||
</div>
|
||||
<form onsubmit="$(this).closest('.oh-modal--show').removeClass('oh-modal--show')"
|
||||
hx-post='{% url "send-mail-to-employee" %} ' class="oh-general__tab-target oh-profile-section"
|
||||
id='ack-form-{{employee.id}}' hx-target="#ack-message-{{employee.id}}" hx-encoding="multipart/form-data">
|
||||
<input type="hidden" value="{{employee.id}}" name="id">
|
||||
<div class="modal-body">
|
||||
{% if employee %}
|
||||
<div class="oh-timeoff-modal__profile-content">
|
||||
<div class="oh-profile mb-2">
|
||||
<div class="oh-profile__avatar">
|
||||
<img src="{{employee.get_avatar}}"
|
||||
class="oh-profile__image me-2">
|
||||
{% comment %} # 875 {% endcomment %}
|
||||
<button hidden id="previewHxButton"
|
||||
hx-post="{% url 'get-employee-mail-preview' %}?{% if employee %}emp_id={{ employee.id }}&{% endif %}"
|
||||
hx-target="#preview"
|
||||
hx-include="#ack-form-{{ employee.id }}"
|
||||
hx-on-htmx-before-request="if (!$('#template').val()) { event.preventDefault(); }">
|
||||
</button>
|
||||
|
||||
<div id="ack-message-{{ employee.id }}"></div>
|
||||
|
||||
<form id="ack-form-{{ employee.id }}" class="oh-general__tab-target oh-profile-section"
|
||||
hx-post="{% url 'send-mail-to-employee' %}" hx-target="#ack-message-{{ employee.id }}"
|
||||
hx-encoding="multipart/form-data" onsubmit="return handleFormSubmit(event, {{ employee.id }})">
|
||||
<input type="hidden" name="id" value="{{ employee.id }}">
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
{% if employee %}
|
||||
<div class="oh-timeoff-modal__profile-content">
|
||||
<div class="oh-profile mb-2">
|
||||
<div class="oh-profile__avatar">
|
||||
<img src="{{ employee.get_avatar }}" class="oh-profile__image me-2" alt="Employee Avatar">
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user fw-bold">{{ employee.get_full_name }}</span>
|
||||
<span class="oh-timeoff-modal__user m-0" style="font-size: 18px; color: #4d4a4a">
|
||||
{{ employee.get_job_position }} / {{ employee.get_department }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Also Send To -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="employees"><h6>{% trans "Also send to" %}</h6></label>
|
||||
<select class="oh-select oh-select-2" name="employees" id="employees" multiple {% if not employee %} required {% endif %}>
|
||||
{% for emp in employees %}
|
||||
<option value="{{ emp.id }}">{{ emp }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user fw-bold">{{employee.get_full_name}}</span>
|
||||
<span class="oh-timeoff-modal__user m-0" style="font-size: 18px; color: #4d4a4a">
|
||||
{{employee.get_job_position}} /
|
||||
{{employee.get_department}}</span>
|
||||
|
||||
<!-- Subject -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="subject"><h6>{% trans "Subject" %}</h6></label>
|
||||
<input required type="text" name="subject" class="oh-input w-100" id="subject" placeholder="Important Reminder">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group mt-2">
|
||||
<label for="employees">
|
||||
<h6>{% trans "Also send to" %}</h6>
|
||||
</label>
|
||||
<select class="oh-select oh-select-2" {% if not employee %} required {% endif %} name="employees" id="employees" multiple>
|
||||
{% for employee in employees %}
|
||||
<option value="{{employee.id}}">{{employee}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<label for="subject">
|
||||
<h6>{% trans "Subject" %}</h6>
|
||||
</label>
|
||||
<input required type="text" placeholder="Important Reminder" name='subject' class="oh-input w-100" id="subject"
|
||||
placeholder="Subject">
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<label for="template">
|
||||
<h6>{% trans "Template" %}</h6>
|
||||
</label>
|
||||
<select name="template" class="w-100 oh-select" id="template" onchange="templateChange($(this))">
|
||||
<option value="">----</option>
|
||||
{% for template in templates %}
|
||||
<option value="{{template.id}}">{{template.title}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<label for="body">
|
||||
<h6>{% trans "Message Body" %}</h6>
|
||||
</label>
|
||||
<div class="oh-input__group">
|
||||
<div class="oh-tabs__view-buttons mt-2">
|
||||
<span class="tab-btn active" data-tab="write">
|
||||
<span>{% trans "Write" %}</span>
|
||||
</span>
|
||||
<span class="tab-btn" onclick="previewMail()" data-tab="preview">
|
||||
<span>{% trans "Preview" %}</span>
|
||||
</span>
|
||||
|
||||
<!-- Template -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="template"><h6>{% trans "Template" %}</h6></label>
|
||||
<select name="template" class="w-100 oh-select" id="template" hx-on:change="loadTemplate(this)">
|
||||
<option value="">----</option>
|
||||
{% for template in templates %}
|
||||
<option value="{{ template.id }}">{{ template.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div id="write" class="oh-tabs__view active">
|
||||
<textarea id="writeField" data-summernote name="body" required class="oh-input oh-input--textarea" placeholder="Type something here..."></textarea>
|
||||
|
||||
<!-- Message Body -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="body"><h6>{% trans "Message Body" %}</h6></label>
|
||||
<div class="oh-input__group">
|
||||
<div class="oh-tabs__view-buttons mt-2">
|
||||
<span class="tab-btn active" data-tab="write"><span>{% trans "Write" %}</span></span>
|
||||
<span class="tab-btn" data-tab="preview" hx-on:click="$('#previewHxButton').click();"><span>{% trans "Preview" %}</span></span>
|
||||
</div>
|
||||
<div id="write" class="oh-tabs__view active">
|
||||
<textarea id="writeField" data-summernote name="body" required
|
||||
class="oh-input oh-input--textarea" placeholder="Type something here..."></textarea>
|
||||
</div>
|
||||
<div id="preview" class="oh-tabs__view">
|
||||
<div id="previewField" class="oh-input oh-input--textarea" placeholder="preview..."></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="preview" class="oh-tabs__view">
|
||||
<div id="previewField" disabled class="oh-input oh-input--textarea" placeholder="preview..."></div>
|
||||
|
||||
<div class="form-hint" style="margin-top: 10px; font-size: 14px; color: #888;">
|
||||
{% trans "Hint: Type '{' to get sender or receiver data" %}
|
||||
</div>
|
||||
|
||||
<!-- Template as Attachment -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="template_attachments"><h6>{% trans "Template as Attachment" %}</h6></label>
|
||||
<select name="template_attachments" class="w-100 oh-select" id="template_attachments" multiple>
|
||||
{% for template in templates %}
|
||||
<option value="{{ template.id }}">{{ template.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Other Attachments -->
|
||||
<div class="form-group mt-2">
|
||||
<label for="other_attachments"><h6>{% trans "Other Attachments" %}</h6></label>
|
||||
<input type="file" name="other_attachments" id="other_attachments" multiple style="display: block;">
|
||||
</div>
|
||||
|
||||
<div id="messages"></div>
|
||||
|
||||
<div class="modal-footer d-flex flex-row-reverse mt-3">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary submit-send">{% trans "Send Mail" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-hint" style="margin-top: 10px; font-size: 14px; color: #888;">
|
||||
{% trans "Hint: Type '{' to get sender or receiver data" %}
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<label for="template_attachments">
|
||||
<h6>{% trans "Template as Attachment" %}</h6>
|
||||
</label>
|
||||
<select name="template_attachments" class="w-100 oh-select" id="template_attachments" multiple>
|
||||
{% for template in templates %}
|
||||
<option value="{{template.id}}">{{template.title}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<label for="other_attachments">
|
||||
<h6>{% trans "Other Attachments" %}</h6>
|
||||
</label>
|
||||
<input type="file" name="other_attachments" id="other_attachments" multiple style="display: block;">
|
||||
</div>
|
||||
<div class="modal-footer d-flex flex-row-reverse mt-3">
|
||||
<input type="submit" class="oh-btn oh-btn--secondary submit-send" data-message-id="ack-message-{{employee.id}}"
|
||||
name="submit" id="submit" onclick="sendMail()" value="{% trans 'Send Mail' %}">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<script>
|
||||
button = document.querySelector('.submit-send')
|
||||
button.onclick = function (event) {
|
||||
var element = event.target;
|
||||
var valid = true
|
||||
// ---------- UTILITIES ----------
|
||||
function showError(message) {
|
||||
$("#messages").html(`<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">${message}</div>`);
|
||||
}
|
||||
function clearErrors() { $("#messages").empty(); }
|
||||
|
||||
if (!$('#employees').val().length && "{{employee|default:'false'|safe}}"=="false") {
|
||||
valid=false
|
||||
$(`#messages`).html($(`
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">
|
||||
This field is required
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
else if (!$("#subject").val().length) {
|
||||
valid=false
|
||||
$(`#messages`).html($(`
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">
|
||||
The message subject is required
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
else if (!$("#body").val().length) {
|
||||
valid=false
|
||||
$(`#messages`).html($(`
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger" role="alert">
|
||||
The message body is required
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
if (valid) {
|
||||
Swal.fire({
|
||||
title: "Processing",
|
||||
text: "Mail will sent on the background",
|
||||
icon: "info"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
var selectedIds = JSON.parse($("#selectedInstances").attr("data-ids"));
|
||||
$("#employees[name=employees]select[multiple]").val(selectedIds).change()
|
||||
|
||||
});
|
||||
function templateChange(e) {
|
||||
var id = e.val();
|
||||
if (id.length) {
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: `/employee/get-template/${id}/`,
|
||||
data: { "instance_id": "{{employee.id}}" },
|
||||
dataType: "Json",
|
||||
success: function (response) {
|
||||
$('#ack-form-{{employee.id}} [name="body"]').html(response.body).change()
|
||||
$('#ack-form-{{employee.id}} [class="note-editable"]').html(response.body)
|
||||
// ---------- VALIDATION ----------
|
||||
function validateForm(formId, hasEmployee) {
|
||||
clearErrors();
|
||||
let valid = true;
|
||||
if (!$('#employees').val().length && !hasEmployee) {
|
||||
showError("Please select at least one employee.");
|
||||
valid = false;
|
||||
} else if (!$("#subject").val().trim()) {
|
||||
showError("The message subject is required.");
|
||||
valid = false;
|
||||
} else {
|
||||
let body = $(`#writeField`).summernote('code');
|
||||
if (!body || body === '<p><br></p>') {
|
||||
showError("The message body is required.");
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
};
|
||||
function previewMail(){
|
||||
var id = $('#template').val();
|
||||
var body = $('#ack-form-{{employee.id}} [name="body"]').val()
|
||||
if (body && body.trim() !== '') {
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: "{% url 'get-employee-mail-preview' %}" ,
|
||||
data: {"body":body,"emp_id":{{employee.id}} },
|
||||
dataType: "Json",
|
||||
success: function (response) {
|
||||
$('#previewField').html(response.body)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
initializeSummernote({{employee.id}},{{ searchWords|safe }});
|
||||
const buttons = document.querySelectorAll(".tab-btn");
|
||||
const contents = document.querySelectorAll(".oh-tabs__view ");
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
buttons.forEach((btn) => btn.classList.remove("active"));
|
||||
button.classList.add("active");
|
||||
|
||||
contents.forEach((content) => content.classList.remove("active"));
|
||||
document.getElementById(button.dataset.tab).classList.add("active");
|
||||
// ---------- FORM SUBMIT ----------
|
||||
function handleFormSubmit(event, empId) {
|
||||
event.preventDefault();
|
||||
const hasEmployee = "{{employee|default:'false'|safe}}" !== "false";
|
||||
if (!validateForm(empId, hasEmployee)) return false;
|
||||
|
||||
Swal.fire({
|
||||
title: "Processing",
|
||||
text: "Mail will be sent in the background",
|
||||
icon: "info",
|
||||
timer: 1500,
|
||||
showConfirmButton: false
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- TEMPLATE LOAD ----------
|
||||
function loadTemplate(select) {
|
||||
const id = $(select).val();
|
||||
if (!id) return;
|
||||
$.ajax({
|
||||
type: "get",
|
||||
url: `/employee/get-template/${id}/`,
|
||||
dataType: "json",
|
||||
beforeSend: () => { $('#writeField').summernote('disable'); },
|
||||
success: function(response) { $('#writeField').summernote('code', response.body); },
|
||||
complete: () => { $('#writeField').summernote('enable'); }
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- INIT ----------
|
||||
$(document).ready(function() {
|
||||
// Load preselected employees if any
|
||||
let selectedIds = JSON.parse($("#selectedInstances").attr("data-ids") || "[]");
|
||||
$("#employees").val(selectedIds).change();
|
||||
|
||||
// Initialize Summernote conditionally
|
||||
{% if employee %}
|
||||
initializeSummernote({{ employee.id }}, {{ searchWords|safe }});
|
||||
{% else %}
|
||||
if (selectedIds.length > 0) {
|
||||
initializeSummernote(selectedIds[0], {{ searchWords|safe }});
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// Tab switching
|
||||
document.querySelectorAll(".tab-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
document.querySelectorAll(".oh-tabs__view").forEach(c => c.classList.remove("active"));
|
||||
document.getElementById(btn.dataset.tab).classList.add("active");
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
<style>
|
||||
#enlargeImageContainer {
|
||||
position: absolute;
|
||||
left: -300px;
|
||||
top: 100px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
#enlargeImageContainer {
|
||||
position: absolute;
|
||||
left: -300px;
|
||||
top: 100px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title">
|
||||
{{ policy.title }}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<h2 class="oh-modal__dialog-title">{{ policy.title }}</h2>
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-body">
|
||||
<p class="oh-card__footer--border-top"></p>
|
||||
{{ policy.body|safe }}
|
||||
<div id="attachmentContainer" class="d-flex" hx-get="{% url 'get-attachments-policy' %}?policy_id={{ policy.id }}" hx-trigger="load"></div>
|
||||
<div class="oh-modal__dialog-body border rounded-md shadow-card m-[20px]">
|
||||
<p class="oh-card__footer--border-top"></p>
|
||||
{{ policy.body|safe }}
|
||||
<div
|
||||
id="attachmentContainer"
|
||||
class="d-flex"
|
||||
hx-get="{% url 'get-attachments-policy' %}?policy_id={{ policy.id }}"
|
||||
hx-trigger="load"
|
||||
></div>
|
||||
|
||||
<div id="enlargeImageContainer"></div>
|
||||
<div id="enlargeImageContainer"></div>
|
||||
</div>
|
||||
<div class="h-1"></div>
|
||||
|
||||
Reference in New Issue
Block a user