voip click2calll
This commit is contained in:
1 parent
cb92abbb41
commit
38b1cf06df
18 files changed
+543
-26
No files matched your search
@@ -0,0 +1,17 @@
|
||||
{% if voip_call_methods and customer.mobile %}
|
||||
<div class="voip-call" data-voip-call data-call-url="{% url 'staff_tickets:contact_call' customer.public_id %}">
|
||||
<button type="button" class="voip-call-trigger" data-voip-call-trigger aria-expanded="false" aria-haspopup="menu">
|
||||
<span class="voip-call-trigger-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M6.6 10.8c1.7 3.3 3.3 4.9 6.6 6.6l2.2-2.2c.3-.3.7-.4 1.1-.3 1.2.4 2.5.6 3.8.6.6 0 1 .4 1 1V20c0 .6-.4 1-1 1C10.8 21 3 13.2 3 3.7c0-.6.4-1 1-1h3.5c.6 0 1 .4 1 1 0 1.3.2 2.6.6 3.8.1.4 0 .8-.3 1.1l-2.2 2.2Z"/></svg></span>
|
||||
<span class="voip-call-trigger-copy"><b>تماس</b><small>ارتباط با مخاطب</small></span>
|
||||
<span class="voip-call-chevron" aria-hidden="true">⌄</span>
|
||||
</button>
|
||||
<div class="voip-call-menu" data-voip-call-menu role="menu" aria-label="انتخاب روش تماس" hidden>
|
||||
<header><span aria-hidden="true">☎</span><div><b>شروع تماس</b><small>روش ارتباط کارشناس را انتخاب کنید</small></div></header>
|
||||
<div class="voip-call-options">
|
||||
{% if 'ip_phone' in voip_call_methods %}<button type="button" role="menuitem" data-voip-method="ip_phone"><span class="voip-method-icon" aria-hidden="true">⌁</span><span><b>تلفن داخلی</b><small>تماس از داخلی ثبتشده کارشناس</small></span><span class="voip-method-arrow" aria-hidden="true">←</span></button>{% endif %}
|
||||
{% if 'mobile' in voip_call_methods %}<button type="button" role="menuitem" data-voip-method="mobile"><span class="voip-method-icon is-mobile" aria-hidden="true">▯</span><span><b>موبایل کارشناس</b><small>اتصال تماس به شماره VOIP کارشناس</small></span><span class="voip-method-arrow" aria-hidden="true">←</span></button>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<small class="voip-call-status" data-voip-call-status role="status" aria-live="polite"></small>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,7 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends 'base.html' %}{% load static %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
{% block content %}<div class="operations-page operations-contacts">
|
||||
<section class="operations-hero"><div><p class="eyebrow">مرکز یکپارچه ارتباطات</p><h1>مخاطبان و مشتریان</h1><p>پرونده ارتباطی مشتریان، تأمینکنندگان و باربریها همراه با گروهها و وضعیت تیکتها در یک نما.</p></div><div class="operations-hero-actions"><a class="button button-secondary" href="{% url 'staff_tickets:groups' %}">مدیریت گروهها</a>{% if can_add_contact %}<a class="button" href="{% url 'staff_tickets:contact_create' %}">+ مخاطب جدید</a>{% endif %}</div></section>
|
||||
<section class="operations-kpis" aria-label="آمار مخاطبان"><article><span class="kpi-icon">◎</span><div><b>{{ metrics.total }}</b><small>نتیجه قابل مشاهده</small></div></article><article><span class="kpi-icon is-blue">♙</span><div><b>{{ metrics.customers }}</b><small>مشتری</small></div></article><article><span class="kpi-icon is-amber">▣</span><div><b>{{ metrics.suppliers }}</b><small>تأمینکننده</small></div></article><article><span class="kpi-icon is-violet">⇄</span><div><b>{{ metrics.carriers }}</b><small>باربری</small></div></article><article><span class="kpi-icon is-coral">!</span><div><b>{{ metrics.needs_action }}</b><small>تیکت نیازمند اقدام</small></div></article></section>
|
||||
<section class="operations-panel"><form method="get" class="contact-filter-bar"><label class="contact-search"><span>⌕</span><input type="search" name="q" value="{{ request.GET.q }}" placeholder="جستوجو با نام، سازمان یا شماره همراه"></label><label><span>نوع مخاطب</span><select name="stakeholder_type"><option value="">همه انواع</option>{% for value,label in stakeholder_types %}<option value="{{ value }}" {% if request.GET.stakeholder_type == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></label><label><span>گروه</span><select name="group"><option value="">همه گروهها</option>{% for group in groups %}<option value="{{ group.pk }}" {% if request.GET.group == group.pk|stringformat:'s' %}selected{% endif %}>{{ group.name }} ({{ group.contact_count }})</option>{% endfor %}</select></label><label><span>استان</span><select name="province"><option value="">همه استانها</option>{% for province in provinces %}<option value="{{ province }}" {% if request.GET.province == province %}selected{% endif %}>{{ province }}</option>{% endfor %}</select></label><button type="submit">اعمال فیلتر</button>{% if request.GET %}<a class="filter-reset" href="{% url 'staff_tickets:contacts' %}">پاککردن</a>{% endif %}</form>
|
||||
<div class="contact-result-meta"><div><b>{{ contacts|length }} مخاطب</b><small>مرتبشده بر اساس تیکتهای فعال و آخرین ثبت</small></div><a href="{% url 'staff_tickets:groups' %}">{{ groups|length }} گروه قابل دسترس ←</a></div><div class="contact-card-grid">{% for contact in contacts %}{% with profile=contact.customer_profile %}<article class="contact-card"><header><span class="contact-avatar">{{ profile.first_name|default:contact.local_mobile|slice:':1' }}</span><div class="contact-identity"><h2>{{ profile|default:contact.local_mobile }}</h2><p>{{ profile.organization_name|default:profile.get_stakeholder_type_display }}</p></div><span class="contact-type type-{{ profile.stakeholder_type|lower }}">{{ profile.get_stakeholder_type_display }}</span></header><div class="contact-meta"><span dir="ltr">☎ {{ contact.local_mobile }}</span><span>⌖ {% if profile.city or profile.province %}{{ profile.city }}{% if profile.city and profile.province %}، {% endif %}{{ profile.province }}{% else %}موقعیت ثبت نشده{% endif %}</span></div><div class="contact-groups">{% for group in profile.visible_groups %}<span>{{ group.name }}</span>{% empty %}<small>بدون گروه قابل مشاهده</small>{% endfor %}</div><div class="contact-ticket-stats"><span><b>{{ contact.ticket_count }}</b>کل تیکت</span><span><b>{{ contact.active_count }}</b>فعال</span><span class="needs-action"><b>{{ contact.waiting_staff_count }}</b>نیازمند اقدام</span><span><b>{{ contact.resolved_count }}</b>مختومه</span></div><footer><a href="{% url 'staff_tickets:contact_edit' contact.public_id %}">ویرایش پرونده</a>{% if staff_operations_access.create_ticket %}<a class="primary-action" href="{% url 'staff_tickets:create' %}?customer={{ contact.public_id }}">+ ثبت تیکت</a>{% endif %}</footer></article>{% endwith %}{% empty %}<div class="empty-state contact-empty"><span>⌕</span><h2>مخاطبی پیدا نشد</h2><p>فیلترها را تغییر دهید یا مخاطب جدیدی ثبت کنید.</p>{% if can_add_contact %}<a class="button" href="{% url 'staff_tickets:contact_create' %}">ثبت مخاطب جدید</a>{% endif %}</div>{% endfor %}</div></section></div>{% endblock %}
|
||||
<div class="contact-result-meta"><div><b>{{ contacts|length }} مخاطب</b><small>مرتبشده بر اساس تیکتهای فعال و آخرین ثبت</small></div><a href="{% url 'staff_tickets:groups' %}">{{ groups|length }} گروه قابل دسترس ←</a></div><div class="contact-card-grid">{% for contact in contacts %}{% with profile=contact.customer_profile %}<article class="contact-card"><header><span class="contact-avatar">{{ profile.first_name|default:contact.local_mobile|slice:':1' }}</span><div class="contact-identity"><h2>{{ profile|default:contact.local_mobile }}</h2><p>{{ profile.organization_name|default:profile.get_stakeholder_type_display }}</p></div><span class="contact-type type-{{ profile.stakeholder_type|lower }}">{{ profile.get_stakeholder_type_display }}</span></header><div class="contact-meta"><span dir="ltr">☎ {{ contact.local_mobile }}</span><span>⌖ {% if profile.city or profile.province %}{{ profile.city }}{% if profile.city and profile.province %}، {% endif %}{{ profile.province }}{% else %}موقعیت ثبت نشده{% endif %}</span></div><div class="contact-groups">{% for group in profile.visible_groups %}<span>{{ group.name }}</span>{% empty %}<small>بدون گروه قابل مشاهده</small>{% endfor %}</div><div class="contact-ticket-stats"><span><b>{{ contact.ticket_count }}</b>کل تیکت</span><span><b>{{ contact.active_count }}</b>فعال</span><span class="needs-action"><b>{{ contact.waiting_staff_count }}</b>نیازمند اقدام</span><span><b>{{ contact.resolved_count }}</b>مختومه</span></div><footer><a href="{% url 'staff_tickets:contact_edit' contact.public_id %}">ویرایش پرونده</a>{% include 'tickets/_voip_call_button.html' with customer=contact %}{% if staff_operations_access.create_ticket %}<a class="primary-action" href="{% url 'staff_tickets:create' %}?customer={{ contact.public_id }}">+ ثبت تیکت</a>{% endif %}</footer></article>{% endwith %}{% empty %}<div class="empty-state contact-empty"><span>⌕</span><h2>مخاطبی پیدا نشد</h2><p>فیلترها را تغییر دهید یا مخاطب جدیدی ثبت کنید.</p>{% if can_add_contact %}<a class="button" href="{% url 'staff_tickets:contact_create' %}">ثبت مخاطب جدید</a>{% endif %}</div>{% endfor %}</div></section></div>{% endblock %}
|
||||
{% block extra_scripts %}<script defer src="{% static 'js/voip-call.js' %}?v={{ static_asset_version }}"></script>{% endblock %}
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="ticket-workspace staff-ticket-workspace" data-conversation-workspace data-poll-url="{% url 'staff_tickets:poll' ticket.public_id %}" data-poll-version="{{ poll_version }}" data-poll-cursor="{{ poll_cursor }}" data-draft-key="ticket-{{ ticket.public_id }}-staff" data-reply-result="{{ request.GET.reply_result|default:'' }}">
|
||||
<header class="ticket-workspace-head">
|
||||
<div class="ticket-title"><a class="workspace-back" href="{% url 'staff_tickets:list' %}" aria-label="بازگشت">→</a><div><span class="ticket-number" dir="ltr">{{ ticket.ticket_number }}</span><h1>{{ ticket.subject }}</h1></div></div>
|
||||
<div class="ticket-head-actions"><span class="badge" data-ticket-status>{{ ticket.get_status_display }}</span>{% if can_claim %}<form method="post" action="{% url 'staff_tickets:claim' ticket.public_id %}">{% csrf_token %}<button type="submit">اختصاص به من</button></form>{% endif %}<button type="button" class="icon-button" data-toggle-panel="ticket-actions" aria-label="عملیات تیکت">⚙</button><button type="button" class="icon-button" data-toggle-panel="customer-info" aria-label="اطلاعات مشتری">◉</button></div>
|
||||
<div class="ticket-head-actions"><span class="badge" data-ticket-status>{{ ticket.get_status_display }}</span>{% include 'tickets/_voip_call_button.html' with customer=ticket.customer %}{% if can_claim %}<form method="post" action="{% url 'staff_tickets:claim' ticket.public_id %}">{% csrf_token %}<button type="submit">اختصاص به من</button></form>{% endif %}<button type="button" class="icon-button" data-toggle-panel="ticket-actions" aria-label="عملیات تیکت">⚙</button><button type="button" class="icon-button" data-toggle-panel="customer-info" aria-label="اطلاعات مشتری">◉</button></div>
|
||||
</header>
|
||||
{% if ticket.group_dispatch %}
|
||||
<section class="ticket-dispatch-context" aria-label="اطلاعات ارسال گروهی">
|
||||
@@ -42,5 +42,5 @@
|
||||
</div>
|
||||
</div>
|
||||
{% include 'tickets/_image_viewer.html' %}
|
||||
<script src="{% static 'vendor/compressor.min.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/attachment-uploader.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/image-viewer.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/conversation.js' %}?v={{ static_asset_version }}"></script>
|
||||
<script src="{% static 'vendor/compressor.min.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/attachment-uploader.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/image-viewer.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/conversation.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/voip-call.js' %}?v={{ static_asset_version }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}{% load static %}
|
||||
{% block title %}{{ page_title }}{% endblock %}
|
||||
{% block content %}<div class="operations-page operations-contact-form" data-location-picker data-locations-url="{% static 'data/iran-locations.json' %}">
|
||||
<section class="operations-hero"><div><p class="eyebrow">پرونده ارتباطی</p><h1>{{ form_title }}</h1><p>{{ form_help }}</p></div><div class="operations-hero-actions"><a class="button button-secondary" href="{% url 'staff_tickets:contacts' %}">بازگشت به مخاطبان</a></div></section>
|
||||
<section class="operations-hero"><div><p class="eyebrow">پرونده ارتباطی</p><h1>{{ form_title }}</h1><p>{{ form_help }}</p></div><div class="operations-hero-actions">{% if call_customer %}{% include 'tickets/_voip_call_button.html' with customer=call_customer %}{% endif %}<a class="button button-secondary" href="{% url 'staff_tickets:contacts' %}">بازگشت به مخاطبان</a></div></section>
|
||||
<form method="post" class="contact-editor" data-disable-on-submit>{% csrf_token %}
|
||||
{% if form.non_field_errors %}<div class="create-form-errors" role="alert">{{ form.non_field_errors }}</div>{% endif %}
|
||||
<section class="editor-section"><header><span>۱</span><div><h2>هویت و راه ارتباطی</h2><p>اطلاعات پایه مخاطب برای شناسایی و ارسال پیامک</p></div></header><div class="editor-grid"><label><span>{{ form.mobile.label }} <em>*</em></span>{{ form.mobile }}{{ form.mobile.errors }}<small>شماره معتبر به شکل 09xxxxxxxxx</small></label><label><span>{{ form.stakeholder_type.label }} <em>*</em></span>{{ form.stakeholder_type }}{{ form.stakeholder_type.errors }}</label><label><span>{{ form.first_name.label }} <em>*</em></span>{{ form.first_name }}{{ form.first_name.errors }}</label><label><span>{{ form.last_name.label }}</span>{{ form.last_name }}{{ form.last_name.errors }}</label><label class="editor-wide"><span>{{ form.organization_name.label }}</span>{{ form.organization_name }}{{ form.organization_name.errors }}</label></div></section>
|
||||
@@ -9,4 +9,4 @@
|
||||
<section class="editor-section"><header><span>۳</span><div><h2>گروهبندی و دسترسی</h2><p>با یک کلیک عضویت مخاطب را در گروههای قابل مدیریت اضافه یا حذف کنید</p></div></header><fieldset class="group-selector" data-contact-group-selector><legend>{{ form.groups.label }} <em>*</em></legend><div class="group-selector-tools"><label><span class="sr-only">جستوجوی گروه</span><input type="search" placeholder="جستوجوی نام گروه یا واحد…" autocomplete="off" data-group-search></label><span class="group-selection-count" data-group-selection-count aria-live="polite">۰ گروه انتخاب شده</span><button type="button" data-clear-groups>پاککردن انتخابها</button></div><div class="group-choice-list">{{ form.groups }}</div>{{ form.groups.errors }}<small>{{ form.groups.help_text }}</small><p class="group-empty" data-group-empty hidden>گروهی با این عبارت پیدا نشد.</p></fieldset></section>
|
||||
<footer class="editor-actions"><button type="submit">ذخیره مخاطب</button><a href="{% url 'staff_tickets:contacts' %}">انصراف</a><span>اطلاعات ورود یا رمز عبور برای مخاطب ساخته نمیشود.</span></footer>
|
||||
</form></div>{% endblock %}
|
||||
{% block extra_scripts %}<script defer src="{% static 'js/location-picker.js' %}?v={{ static_asset_version }}"></script><script defer src="{% static 'js/contact-group-selector.js' %}?v={{ static_asset_version }}"></script>{% endblock %}
|
||||
{% block extra_scripts %}<script defer src="{% static 'js/location-picker.js' %}?v={{ static_asset_version }}"></script><script defer src="{% static 'js/contact-group-selector.js' %}?v={{ static_asset_version }}"></script><script defer src="{% static 'js/voip-call.js' %}?v={{ static_asset_version }}"></script>{% endblock %}
|
||||
Reference in new issue
Block a user