Corp/backend/templates/requests_app/request_detail.html

259 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}