Files
customerTicket/templates/tickets/staff_dispatch_detail.html

84 lines
16 KiB
HTML

{% extends 'base.html' %}{% load static jalali %}
{% block title %}{{ dispatch.subject }}{% endblock %}
{% block extra_head %}<link rel="stylesheet" href="{% static 'css/dispatch-workspace.css' %}?v={{ static_asset_version }}">{% endblock %}
{% block content %}
<div class="operations-page operations-dispatch-detail dispatch-workspace" data-dispatch-report>
<nav class="dispatch-breadcrumb" aria-label="مسیر صفحه"><a href="{% url 'staff_tickets:dispatch_create' %}#dispatch-history-panel">{% include 'tickets/_dispatch_icon.html' with icon='arrow' %} سوابق ارسال گروهی</a><span>گزارش و پاسخ‌ها</span></nav>
<header class="dispatch-report-hero">
<div class="dispatch-report-title"><div class="dispatch-title-line"><span class="dispatch-eyebrow">{{ dispatch.get_operation_type_display }}</span><span class="report-status status-{{ dispatch.status|lower }}">{{ dispatch.get_status_display }}</span></div><h1>{{ dispatch.subject }}</h1>
<div class="dispatch-meta-list"><span><b>واحد</b>{{ dispatch.unit }}</span><span><b>گروه</b>{{ dispatch.contact_group.name }}</span><span><b>گیرندگان</b>{{ dispatch.recipient_count }} نفر</span><span><b>ایجاد</b><time datetime="{{ dispatch.created_at|date:'c' }}">{{ dispatch.created_at|jalali_datetime }}</time></span></div>
</div><a class="dispatch-secondary" href="{% url 'staff_tickets:dispatch_create' %}">{% include 'tickets/_dispatch_icon.html' with icon='send' %} ارسال گروهی جدید</a>
</header>
{% if response_control %}<section class="dispatch-response-banner" aria-label="وضعیت بازگشایی پاسخ‌ها">
{% if responses_locked %}<h2>پاسخ‌ها هنوز بازگشایی نشده‌اند</h2><p>{{ metrics.replied }} مخاطب پاسخ داده‌اند. متن و پیوست پاسخ‌ها برای همه کارکنان، حتی admin، تا بازگشایی قفل است.</p>
{% if can_open_responses %}<a class="dispatch-primary" href="{% url 'staff_tickets:dispatch_open_responses' dispatch.public_id %}">بازگشایی همه پاسخ‌ها</a>{% else %}<p>بازگشایی توسط ایجادکننده یا کاربر دارای مجوز انجام می‌شود.</p>{% endif %}
{% elif response_control.opened_at %}<h2>پاسخ‌ها بازگشایی شده‌اند</h2><p>{{ response_control.opened_at|jalali_datetime_precise }} — توسط {{ response_control.opened_by.get_full_name|default:response_control.opened_by.username }}</p><p>پاسخ‌های جدید و ویرایش‌های بعدی برای کاربران مجاز قابل مشاهده‌اند.</p>
{% for item in opening_jobs %}<p>پیامک بازگشایی: {{ item.count }} مورد — {% if item.status == 'PENDING' %}در صف{% elif item.status == 'SENT' %}ارسال‌شده{% elif item.status == 'FAILED' %}ناموفق{% elif item.status == 'RETRYING' %}در انتظار تلاش مجدد{% elif item.status == 'PROCESSING' %}در حال ارسال{% else %}لغوشده{% endif %}</p>{% endfor %}
{% else %}<h2>مشاهده جداگانه پاسخ‌ها</h2><p>هر پاسخ پس از ثبت برای کاربران مجاز قابل مشاهده است. پیامک بازگشایی غیرفعال است.</p>{% endif %}
</section>{% endif %}
<section class="dispatch-kpi-grid" aria-label="شاخص‌های ارسال گروهی">
<article class="dispatch-kpi kpi-success">{% include 'tickets/_dispatch_icon.html' with icon='check' %}<div><small>تحویل به گوشی</small><strong>{{ metrics.delivered }}</strong><p>تحویل قطعی پیامک</p></div></article>
<article class="dispatch-kpi kpi-info">{% include 'tickets/_dispatch_icon.html' with icon='history' %}<div><small>در صف ارسال</small><strong>{{ metrics.pending }}</strong><p>{{ metrics.tracking }} پیامک در پیگیری تحویل</p></div></article>
<article class="dispatch-kpi kpi-danger">{% include 'tickets/_dispatch_icon.html' %}<div><small>نیازمند اقدام</small><strong>{{ metrics.failed }}</strong><p>ناموفق یا تحویل تأییدنشده</p></div></article>
<article class="dispatch-kpi kpi-view">{% include 'tickets/_dispatch_icon.html' with icon='eye' %}<div><small>مخاطبان بازدیدکننده</small><strong>{{ metrics.opened }}</strong><p>{{ metrics.total_views }} بازدید از لینک‌ها</p></div></article>
<article class="dispatch-kpi kpi-reply">{% include 'tickets/_dispatch_icon.html' with icon='reply' %}<div><small>پاسخ دریافتی</small><strong>{{ metrics.replied }}</strong><p>مخاطب دارای پاسخ</p></div></article>
</section>
<section class="delivery-overview" aria-label="خلاصه تحویل پیامک">
<div class="delivery-progress-copy"><div><span>تحویل قطعی پیامک</span><strong>{{ metrics.delivery_percent }}٪</strong></div><p>{{ metrics.delivered }} از {{ dispatch.recipient_count }} پیامک به گوشی رسیده است.</p></div>
<div class="delivery-progress-track" role="progressbar" aria-label="درصد تحویل قطعی پیامک" aria-valuemin="0" aria-valuemax="100" aria-valuenow="{{ metrics.delivery_percent }}"><span style="width:{{ metrics.delivery_percent }}%"></span></div>
<div class="delivery-overview-note">{% if metrics.pending %}<b>{{ metrics.pending }} پیامک در صف ارسال است</b>{% elif metrics.tracking %}<b>{{ metrics.tracking }} پیامک در حال پیگیری تحویل است</b>{% elif metrics.failed %}<b>موارد ناموفق را بررسی کنید</b>{% else %}<b>وضعیت تحویل در گزارش هر مخاطب</b>{% endif %}<span>تحویل پیامک به معنی مشاهده لینک یا دریافت پاسخ نیست.</span></div>
</section>
<section class="dispatch-original-message" aria-labelledby="dispatch-message-title">
<header><div><span class="dispatch-eyebrow">متن اولیه کارشناس</span><h2 id="dispatch-message-title">پیام اصلی این ارسال</h2></div><span>ایجادکننده: <b>{{ dispatch.created_by.staff_profile.display_name|default:dispatch.created_by.username }}</b></span></header>
<blockquote>{{ dispatch.body|linebreaksbr }}</blockquote>
<details class="dispatch-operation-details"><summary>اطلاعات عملیات</summary><dl><div><dt>شناسه عملیات</dt><dd dir="ltr">{{ dispatch.public_id }}</dd></div><div><dt>دسته‌بندی</dt><dd>{{ dispatch.category }}</dd></div></dl></details>
</section>
<section class="dispatch-recipient-report" aria-labelledby="dispatch-recipients-title">
<header class="stream-head"><div><h2 id="dispatch-recipients-title">وضعیت پیامک، مشاهده و پاسخ</h2><p>پاسخ‌های هر مخاطب همراه با زمان ثبت و پیوست‌ها در همین گزارش نمایش داده می‌شوند.</p></div><span>{{ dispatch.recipient_count }} مخاطب</span></header>
<div class="dispatch-recipient-tools" hidden data-recipient-tools>
<label><span>جستجوی مخاطب</span><input type="search" data-recipient-search placeholder="نام، شماره همراه یا شماره تیکت"></label>
<label><span>نمایش مخاطبان</span><select data-recipient-filter><option value="all">همه مخاطبان</option><option value="unanswered">بدون پاسخ</option><option value="answered">دارای پاسخ</option><option value="unopened">بدون بازدید لینک</option><option value="opened">دارای بازدید لینک</option><option value="retryable">پیامک نیازمند اقدام</option><option value="active">تیکت‌های باز</option></select></label>
<button type="button" class="dispatch-secondary" data-recipient-reset>پاک کردن فیلتر</button><p role="status" data-recipient-result-count></p>
</div>
<div class="table-scroll"><table class="dispatch-detail-table"><caption class="dispatch-sr-only">گیرندگان این ارسال، وضعیت پیامک و تمامی پاسخ‌های هر مخاطب</caption><thead><tr><th scope="col">مخاطب و تیکت</th><th scope="col">وضعیت پیامک</th><th scope="col">مشاهده لینک</th><th scope="col">پاسخ‌ها و زمان ثبت</th></tr></thead><tbody>
{% for ticket in tickets %}
<tr data-recipient-row data-answered="{{ ticket.response_count|yesno:'true,false' }}" data-opened="{{ ticket.magic_total_views|yesno:'true,false' }}" data-retryable="{{ ticket.sms_retryable|yesno:'true,false' }}" data-active="{% if ticket.status == 'RESOLVED' or ticket.status == 'CLOSED' %}false{% else %}true{% endif %}" data-search="{{ ticket.customer.customer_profile|default:ticket.customer.username }} {{ ticket.customer.local_mobile }} {{ ticket.ticket_number }}">
<td data-label="مخاطب و تیکت"><div class="recipient-cell"><span class="recipient-avatar" aria-hidden="true">{{ ticket.customer.customer_profile.first_name|first|default:'•' }}</span><div><strong>{{ ticket.customer.customer_profile|default:ticket.customer.username }}</strong><small dir="ltr">{{ ticket.customer.local_mobile }}</small></div></div>{% if response_control.mode == 'SEALED' %}<span class="ticket-reference"><strong dir="ltr">{{ ticket.ticket_number }}</strong></span>{% else %}<a class="ticket-reference" href="{% url 'staff_tickets:detail' ticket.public_id %}"><strong dir="ltr">{{ ticket.ticket_number }}</strong></a>{% endif %}<span class="ticket-state-pill state-{{ ticket.status|lower }}">{{ ticket.get_status_display }}</span></td>
<td data-label="وضعیت پیامک" class="dispatch-sms-state">
<div class="sms-state-card tone-{{ ticket.sms_state_tone }}"><span class="sms-state-icon">{% if ticket.sms_state_tone == 'success' %}{% include 'tickets/_dispatch_icon.html' with icon='check' %}{% elif ticket.sms_state_tone == 'warning' %}{% include 'tickets/_dispatch_icon.html' with icon='history' %}{% else %}{% include 'tickets/_dispatch_icon.html' %}{% endif %}</span><div><strong>{{ ticket.sms_state_title }}</strong><p>{{ ticket.sms_state_hint }}</p>{% if ticket.sms_job.delivery_next_check_at %}<small>استعلام بعدی: {{ ticket.sms_job.delivery_next_check_at|timeuntil }} دیگر</small>{% endif %}</div></div>
{% if ticket.sms_job %}<details class="sms-technical-details"><summary>جزئیات فنی و رهگیری</summary><dl>
<div><dt>ارسال</dt><dd>{% if ticket.sms_job.status == 'SENT' and ticket.sms_job.attempts == 1 %}موفق در اولین تلاش{% else %}{{ ticket.sms_job.attempts }} تلاش از {{ sms_max_attempts }}{% endif %}</dd></div>
{% if ticket.sms_job.provider_message_id %}<div><dt>شناسه رهگیری</dt><dd dir="ltr">{{ ticket.sms_job.provider_message_id }}</dd></div>{% endif %}
{% if ticket.sms_job.delivery_status_code %}<div><dt>کد تحویل</dt><dd dir="ltr">{{ ticket.sms_job.delivery_status_code }}</dd></div>{% endif %}
{% if ticket.sms_job.delivery_check_attempts %}<div><dt>تعداد استعلام</dt><dd>{{ ticket.sms_job.delivery_check_attempts }} نوبت</dd></div>{% endif %}
{% if ticket.sms_job.delivery_checked_at %}<div><dt>آخرین استعلام</dt><dd>{{ ticket.sms_job.delivery_checked_at|jalali_datetime }}</dd></div>{% endif %}
</dl>{% if ticket.sms_job.delivery_description %}<p class="gateway-technical-message">{{ ticket.sms_job.delivery_description }}</p>{% endif %}</details>
{% if ticket.sms_job.last_error %}<div class="sms-error-panel"><b>علت عدم ارسال</b><p>{{ ticket.sms_job.last_error }}</p></div>{% endif %}
{% if ticket.sms_retryable %}<form method="post" action="{% url 'staff_tickets:dispatch_retry' dispatch.public_id %}" class="inline-retry-form">{% csrf_token %}<input type="hidden" name="job_id" value="{{ ticket.sms_job.pk }}"><button type="submit">تلاش مجدد همین پیامک</button></form>{% endif %}{% endif %}
</td>
<td data-label="مشاهده لینک"><div class="engagement-state {% if ticket.magic_total_views %}has-engagement{% endif %}">{% include 'tickets/_dispatch_icon.html' with icon='eye' %}<div>{% if ticket.magic_total_views %}<strong>{{ ticket.magic_total_views }} بازدید</strong>{% if ticket.magic_last_used_at %}<small>آخرین بازدید {{ ticket.magic_last_used_at|jalali_datetime }}</small>{% endif %}{% elif ticket.magic_link_count %}<strong>مشاهده نشده</strong><small>{{ ticket.magic_active_count }} لینک فعال</small>{% else %}<strong>لینک صادر نشده</strong>{% endif %}</div></div></td>
<td data-label="پاسخ‌ها و زمان ثبت">
<p><b>پاسخ داده است: {{ ticket.response_count|yesno:'بله,خیر' }}</b></p>
{% if ticket.response_count %}<p>{{ ticket.response_count }} پاسخ</p><small>اولین پاسخ: {{ ticket.first_response_at|jalali_datetime_precise }}<br>آخرین پاسخ: {{ ticket.last_response_at|jalali_datetime_precise }}</small>
{% if responses_locked %}<p class="no-reply-state">متن و پیوست پاسخ تا بازگشایی محرمانه است.</p>
{% else %}{% for answer in ticket.report_customer_responses %}<article class="recipient-reply" aria-label="پاسخ {{ forloop.counter }}">
<time datetime="{{ answer.created_at|date:'c' }}">{{ answer.created_at|jalali_datetime_precise }}</time>
{% if answer.body %}<p>{{ answer.body|linebreaksbr }}</p>{% else %}<p>پاسخ دارای پیوست</p>{% endif %}
{% if answer.attachments.all %}<div class="dispatch-response-files">{% for file in answer.attachments.all %}<a href="{% url 'attachments:view' file.public_id %}" target="_blank" rel="noopener">{{ file.original_name }} · {{ file.file_size|filesizeformat }}<span class="dispatch-sr-only"> (باز کردن پیوست در پنجره جدید)</span></a>{% endfor %}</div>{% endif %}
</article>{% endfor %}{% endif %}
{% else %}<p class="no-reply-state">هنوز پاسخی ثبت نشده</p>{% endif %}
{% if response_control.mode != 'SEALED' %}<a class="dispatch-reply-link" href="{% url 'staff_tickets:detail' ticket.public_id %}" aria-label="مشاهده و پاسخ به {{ ticket.customer.customer_profile|default:ticket.customer.username }}" title="مشاهده و پاسخ">{% include 'tickets/_dispatch_icon.html' with icon='reply' %}</a>{% endif %}
</td>
</tr>
{% empty %}<tr><td colspan="4"><div class="dispatch-empty"><p>برای این ارسال تیکتی ثبت نشده است.</p></div></td></tr>{% endfor %}
</tbody></table></div>
<div class="dispatch-empty" data-recipient-empty hidden><h3>مخاطبی با این شرایط پیدا نشد</h3><p>عبارت جستجو یا فیلتر مخاطبان را تغییر دهید.</p><button type="button" class="dispatch-secondary" data-recipient-reset>نمایش همه مخاطبان</button></div>
</section>
{% if metrics.failed or can_resolve_dispatch %}<section class="dispatch-management" aria-label="اقدام‌های گروهی"><div><h2>اقدام‌های گروهی</h2><p>این اقدام‌ها روی چند مخاطب اثر می‌گذارند؛ برای پاسخ به یک نفر، تیکت او را باز کنید.</p></div><div class="dispatch-management-actions">
{% if metrics.failed %}<form method="post" action="{% url 'staff_tickets:dispatch_retry' dispatch.public_id %}">{% csrf_token %}<button class="dispatch-secondary" type="submit">تلاش مجدد {{ metrics.failed }} پیامک ناموفق</button></form>{% endif %}
{% if can_resolve_dispatch %}{% if open_ticket_count %}<form method="post" action="{% url 'staff_tickets:dispatch_resolve' dispatch.public_id %}" onsubmit="return confirm('با تأیید این عملیات، هر {{ open_ticket_count }} تیکت باز این ارسال گروهی یک‌جا حل‌شده می‌شوند. آیا ادامه می‌دهید؟')">{% csrf_token %}<button class="dispatch-secondary" type="submit">حل‌شدن همه تیکت‌ها ({{ open_ticket_count }})</button><small>پاسخ تازه مشتری می‌تواند تیکت مربوط به خودش را دوباره باز کند.</small></form>{% else %}<p class="dispatch-complete">{% include 'tickets/_dispatch_icon.html' with icon='check' %} رسیدگی این گروه پایان یافته است؛ همه تیکت‌ها حل یا بسته هستند.</p>{% endif %}{% endif %}
</div></section>{% endif %}
</div>
{% endblock %}
{% block extra_scripts %}<script defer src="{% static 'js/dispatch-workspace.js' %}?v={{ static_asset_version }}"></script>{% endblock %}