259 lines
10 KiB
HTML
259 lines
10 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
|
||
{% block title %}Заявление #{{ request_obj.id }}{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{% static 'css/components/card-list.css' %}">
|
||
<link rel="stylesheet" href="{% static 'css/components/message-field.css' %}">
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
|
||
<div class="container-fluid py-3">
|
||
<div class="row justify-content-center">
|
||
<div class="col-12 col-lg-10 col-xl-8">
|
||
|
||
{% if error %}
|
||
<div class="card mb-3">
|
||
<div class="card-body text-center py-5">
|
||
<i class="bi bi-exclamation-triangle text-warning" style="font-size: 3rem;"></i>
|
||
<h5 class="mt-3">{{ error }}</h5>
|
||
<p class="text-muted">Код ошибки: {{ error_code }}</p>
|
||
<a href="{% url 'requests:request_list' %}" class="btn btn-primary mt-3">
|
||
<i class="bi-arrow-left me-2"></i>Вернуться к списку
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
|
||
{# ===== Основная информация о заявлении ===== #}
|
||
<article class="card mb-3">
|
||
<header class="card-header d-flex align-items-start gap-3">
|
||
<div class="card-icon">
|
||
<i class="bi-clipboard-check"></i>
|
||
</div>
|
||
|
||
<div class="card-meta flex-grow-1">
|
||
<div class="d-flex align-items-center gap-2 flex-wrap mb-2">
|
||
<span class="card-title">
|
||
{{ request_obj.employee.full_name|default:request_obj.employee.display_name }}
|
||
</span>
|
||
|
||
{# статус — бейдж #}
|
||
{% if request_obj.status == 'pending' %}
|
||
<span class="badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle">На рассмотрении</span>
|
||
{% elif request_obj.status == 'approved' %}
|
||
<span class="badge bg-success-subtle text-success-emphasis border border-success-subtle">Одобрено</span>
|
||
{% elif request_obj.status == 'rejected' %}
|
||
<span class="badge bg-danger-subtle text-danger-emphasis border border-danger-subtle">Отклонено</span>
|
||
{% elif request_obj.status == 'cancelled' %}
|
||
<span class="badge bg-dark-subtle text-dark-emphasis border border-dark-subtle">Отменено</span>
|
||
{% elif request_obj.status == 'draft' %}
|
||
<span class="badge bg-light text-body border">Черновик</span>
|
||
{% else %}
|
||
<span class="badge bg-light text-body border">Неизвестно</span>
|
||
{% endif %}
|
||
|
||
</div>
|
||
|
||
<div class="card-subtitle">
|
||
<time datetime="{{ request_obj.created_at|date:'c' }}">{{ request_obj.created_at }}</time>
|
||
{% if request_obj.date_from or request_obj.date_to %}
|
||
· Период: {{ request_obj.date_from|date:'d.m.Y' }}{% if request_obj.date_to %} — {{ request_obj.date_to|date:'d.m.Y' }}{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{# Отозвать — только если можно (ожидание и не финальное) #}
|
||
{% if request_obj.status == 'pending' and not request_obj.is_final %}
|
||
<a href="{% url 'requests:request_cancel' request_obj.id %}"
|
||
class="btn btn-ghost ms-auto">
|
||
<i class="bi-x-circle"></i> Отозвать
|
||
</a>
|
||
{% endif %}
|
||
</header>
|
||
|
||
<div class="card-body">
|
||
{% if request_obj.title %}
|
||
<h6 class="mb-2">{{ request_obj.title }}</h6>
|
||
{% endif %}
|
||
|
||
<div class="mb-3">
|
||
<span class="text-secondary small d-block mb-1">Комментарий:</span>
|
||
<div>{{ request_obj.comment|default:"—"|linebreaksbr }}</div>
|
||
</div>
|
||
|
||
{% if request_obj.attachment_url %}
|
||
<a class="btn btn-ghost d-inline-flex align-items-center gap-2"
|
||
href="{{ request_obj.attachment_url }}" target="_blank" rel="noopener">
|
||
<i class="bi-paperclip"></i>
|
||
<span>Вложение</span>
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</article>
|
||
|
||
{# ===== Комментарии ===== #}
|
||
<article class="card" id="comments">
|
||
<header class="card-header">
|
||
<div class="card-icon">
|
||
<i class="bi-chat-dots"></i>
|
||
</div>
|
||
<div class="card-meta">
|
||
<div class="card-title">Комментарии</div>
|
||
<div class="card-subtitle">Всего: <span class="comments-count">{{ comments|length }}</span></div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="card-body">
|
||
{% if comments %}
|
||
<div class="d-flex flex-column gap-2 mb-3">
|
||
{% for c in comments %}
|
||
<div class="card-list-item">
|
||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||
<div>
|
||
<span class="fw-semibold">{{ c.author.full_name|default:c.author.display_name }}</span>
|
||
<span class="text-secondary mx-1">·</span>
|
||
<time class="text-muted small">{{ c.created_at }}</time>
|
||
</div>
|
||
|
||
{# удалить — только автору комментария #}
|
||
{% if c.author.id == request.user.id %}
|
||
<form method="post" action="{% url 'requests:request_comment_delete' request_obj.id c.id %}">
|
||
{% csrf_token %}
|
||
<button class="btn btn-sm btn-ghost text-danger" type="submit" title="Удалить">
|
||
<i class="bi-trash"></i>
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
<div class="small">{{ c.text|linebreaksbr }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="text-secondary small text-center py-3">Комментариев пока нет.</div>
|
||
{% endif %}
|
||
|
||
{# Добавить комментарий — #}
|
||
<form class="mt-3 request-comment-form" method="post" action="{% url 'requests:request_comment_add' request_obj.id %}">
|
||
{% csrf_token %}
|
||
<div class="message-field message-field--compact">
|
||
<!-- Кнопка выбора эмодзи -->
|
||
<div class="dropdown message-emoji">
|
||
<button type="button" class="btn btn-ghost btn-emoji message-icon-btn"
|
||
data-bs-toggle="dropdown"
|
||
aria-expanded="false"
|
||
title="Вставить смайлик">
|
||
<i class="bi-emoji-smile"></i>
|
||
</button>
|
||
<div class="dropdown-menu dropdown-menu-start message-emoji-menu">
|
||
<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>
|
||
</div>
|
||
</div>
|
||
|
||
<textarea
|
||
name="text"
|
||
class="message-input"
|
||
placeholder="Добавить комментарий..."
|
||
rows="2"
|
||
required
|
||
></textarea>
|
||
|
||
<button type="submit" class="btn btn-primary message-send-btn">
|
||
<i class="bi-send-fill"></i>
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</article>
|
||
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script type="module">
|
||
// Обработка формы добавления комментария
|
||
const commentForm = document.querySelector('.request-comment-form');
|
||
if (commentForm) {
|
||
commentForm.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
const textarea = commentForm.querySelector('textarea[name="text"]');
|
||
const text = textarea.value.trim();
|
||
|
||
if (!text) {
|
||
alert('Пожалуйста, введите текст комментария');
|
||
return;
|
||
}
|
||
|
||
const submitBtn = commentForm.querySelector('button[type="submit"]');
|
||
const originalBtnContent = submitBtn.innerHTML;
|
||
submitBtn.disabled = true;
|
||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||
|
||
try {
|
||
const response = await fetch(commentForm.action, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': commentForm.querySelector('[name="csrfmiddlewaretoken"]').value
|
||
},
|
||
body: JSON.stringify({ text: text })
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}`);
|
||
}
|
||
|
||
// Перезагрузить страницу для отображения нового комментария
|
||
window.location.reload();
|
||
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
alert('Ошибка при добавлении комментария: ' + error.message);
|
||
submitBtn.disabled = false;
|
||
submitBtn.innerHTML = originalBtnContent;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Обработка форм удаления комментариев
|
||
document.querySelectorAll('form[action*="request_comment_delete"]').forEach(form => {
|
||
form.addEventListener('submit', async (e) => {
|
||
e.preventDefault();
|
||
|
||
if (!confirm('Вы уверены, что хотите удалить этот комментарий?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(form.action, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': form.querySelector('[name="csrfmiddlewaretoken"]').value
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}`);
|
||
}
|
||
|
||
// Перезагрузить страницу
|
||
window.location.reload();
|
||
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
alert('Ошибка при удалении комментария: ' + error.message);
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
{% endblock %}
|