fix sms and ticket user frindly

This commit is contained in:
javad committed 2026-09-14 15:48:13 +03:30
1 parent 1ee83374fd
commit b8b3aba855
30 files changed
+2220 -119

No files matched your search

+138 -23
View File
@@ -3,29 +3,144 @@
{% block content %}
<link rel="stylesheet" href="{% static 'css/ticket-composer.css' %}?v={{ static_asset_version }}">
<link rel="stylesheet" href="{% static 'css/ticket-composer-v2.css' %}?v={{ static_asset_version }}">
<div class="page-head"><div><a class="back-link" href="{% url 'tickets:list' %}">→ بازگشت به درخواست‌ها</a><p class="page-kicker">ثبت درخواست جدید</p><h1>چه کمکی از ما برمی‌آید؟</h1><p>درخواست خود را کامل بنویسید تا همان بار اول پاسخ دقیق‌تری دریافت کنید.</p></div></div>
<div class="request-layout">
<section class="request-form card"><form method="post" enctype="multipart/form-data" data-ticket-composer data-unit-options-url="{% url 'tickets:new_options' %}" data-ticket-search-url="{% url 'tickets:new_search' %}" data-chat-coach data-chat-coach-key="customer-new-{{ user.public_id }}">{% csrf_token %}{{ form.non_field_errors }}
<section class="intent-finder" aria-labelledby="intent-finder-title"><span class="intent-icon">⌕</span><div class="intent-copy"><h2 id="intent-finder-title">موضوع درخواستتان چیست؟</h2><p>چند کلمه بنویسید؛ واحد و دسته‌بندی مناسب را پیشنهاد می‌دهیم.</p><label class="intent-search"><span class="sr-only">جست‌وجوی موضوع درخواست</span><input type="search" data-ticket-search autocomplete="off" placeholder="مثلاً فیش واریزی، موجودی کالا یا ارسال بار..."><kbd>جست‌وجو</kbd></label><div class="intent-results" data-ticket-search-results aria-live="polite"></div></div></section>
<details class="manual-routing" open><summary><span>انتخاب دستی واحد و دسته‌بندی</span><small>در صورت تمایل خودتان مسیر درخواست را مشخص کنید</small></summary><div class="manual-routing-body">
<div class="step-title"><span class="step-number">۱</span><div><h3>موضوع درخواست</h3><p class="muted">دسته‌بندی و یک عنوان کوتاه انتخاب کنید.</p></div></div>
<div class="routing-selectors" data-routing-selectors><div class="field">{{ form.unit.label_tag }}{{ form.unit }}{{ form.unit.errors }}</div><div class="field">{{ form.category.label_tag }}{{ form.category }}{{ form.category.errors }}</div></div>
<div class="field routing-subject">{{ form.subject.label_tag }}{{ form.subject }}{{ form.subject.errors }}<small>موضوع را به‌صورت دستی و دقیق وارد کنید.</small></div>
</div></details>
<div class="step-title"><span class="step-number">۲</span><div><h3>موقعیت این درخواست</h3><p class="muted">اختیاری است؛ موقعیت ذخیره‌شده پروفایل از قبل انتخاب شده و فقط در صورت نیاز آن را تغییر دهید.</p></div></div>
<section class="location-fields" data-location-picker data-locations-url="{% static 'data/iran-locations.json' %}">
<div class="grid gap-4 sm:grid-cols-2"><div class="field">{{ form.province.label_tag }}{{ form.province }}{{ form.province.errors }}</div><div class="field">{{ form.city.label_tag }}{{ form.city }}{{ form.city.errors }}</div></div>
<p class="location-hint" data-location-hint>نام استان را جست‌وجو و انتخاب کنید.</p>
<link rel="stylesheet" href="{% static 'css/customer-ticket-wizard.css' %}?v={{ static_asset_version }}">
<div class="ticket-wizard-page">
<nav class="ticket-wizard-topbar" aria-label="مسیر صفحه">
<a class="ticket-wizard-back" href="{% url 'tickets:list' %}">→ بازگشت به درخواست‌ها</a>
<span class="ticket-wizard-save"><i></i> اطلاعات تا زمان خروج از صفحه حفظ می‌شود</span>
</nav>
<header class="ticket-wizard-intro">
<p class="page-kicker">ثبت درخواست جدید</p>
<h1>چطور می‌توانیم کمک کنیم؟</h1>
<p>قدم‌به‌قدم پیش می‌رویم؛ در هر مرحله فقط اطلاعات ضروری همان بخش را می‌بینید.</p>
</header>
<ol class="ticket-wizard-progress" data-wizard-progress aria-label="مراحل ثبت درخواست">
<li class="is-active" data-wizard-progress-item="1"><span>۱</span><div><b>موضوع</b><small>مسیر درخواست</small></div></li>
<li data-wizard-progress-item="2"><span>۲</span><div><b>موقعیت</b><small>اختیاری</small></div></li>
<li data-wizard-progress-item="3"><span>۳</span><div><b>کارشناس</b><small>انتخاب مسئول</small></div></li>
<li data-wizard-progress-item="4"><span>۴</span><div><b>جزئیات</b><small>متن و فایل</small></div></li>
<li data-wizard-progress-item="5"><span>۵</span><div><b>بازبینی</b><small>تأیید نهایی</small></div></li>
</ol>
<form class="ticket-wizard-card" method="post" enctype="multipart/form-data" novalidate
data-ticket-composer data-ticket-wizard
data-unit-options-url="{% url 'tickets:new_options' %}"
data-ticket-search-url="{% url 'tickets:new_search' %}"
data-chat-coach data-chat-coach-key="customer-new-{{ user.public_id }}">
{% csrf_token %}
<section class="ticket-wizard-step is-active" data-wizard-step="1" aria-labelledby="wizard-step-1-title">
<header class="ticket-wizard-step-head">
<span class="ticket-wizard-symbol" aria-hidden="true">⌕</span>
<div><small>مرحله ۱ از ۵</small><h2 id="wizard-step-1-title">موضوع درخواستتان چیست؟</h2><p>چند کلمه بنویسید تا مسیر مناسب پیشنهاد شود، یا واحد و دسته‌بندی را دستی انتخاب کنید.</p></div>
</header>
<label class="ticket-intent-search">
<span class="sr-only">جست‌وجوی موضوع درخواست</span><i aria-hidden="true">⌕</i>
<input type="search" data-ticket-search autocomplete="off" placeholder="مثلاً فیش واریزی، موجودی کالا یا ارسال بار...">
<kbd>جست‌وجو</kbd>
</label>
<div class="intent-results ticket-intent-results" data-ticket-search-results aria-live="polite"></div>
<details class="ticket-manual-routing manual-routing" {% if form.is_bound and form.errors %}open{% endif %}>
<summary><span>انتخاب دستی واحد و دسته‌بندی</span><small>در صورت تمایل مسیر درخواست را خودتان مشخص کنید</small></summary>
<div class="ticket-manual-body manual-routing-body">
<div class="ticket-routing-grid" data-routing-selectors>
<div class="field">{{ form.unit.label_tag }}{{ form.unit }}{{ form.unit.errors }}</div>
<div class="field">{{ form.category.label_tag }}{{ form.category }}{{ form.category.errors }}</div>
</div>
</div>
</details>
<div class="field ticket-subject-field">
<label for="{{ form.subject.id_for_label }}">موضوع دقیق درخواست <em>*</em></label>
{{ form.subject }}{{ form.subject.errors }}
<small>این عنوان فقط توسط شما نوشته می‌شود و با تغییر واحد یا دسته‌بندی عوض نخواهد شد.</small>
</div>
<p class="ticket-wizard-validation" data-wizard-error="1" role="alert"></p>
<footer class="ticket-wizard-actions"><span>حدود یک دقیقه تا پایان</span><button class="primary" type="button" data-wizard-next>ادامه <b>←</b></button></footer>
</section>
<div class="step-title"><span class="step-number">۳</span><div><h3>انتخاب کارشناس</h3><p class="muted">اختیاری است؛ صف عمومی معمولاً سریع‌ترین انتخاب است.</p></div></div>
<fieldset class="expert-picker" data-expert-picker><legend class="sr-only">کارشناس دلخواه</legend><label class="expert-card" data-general-queue data-unit-id="{{ selected_unit.pk|default:'' }}"><input type="radio" name="selected_expert" value="" {% if not form.selected_expert.value %}checked{% endif %}><span class="expert-avatar">⌁</span><strong>صف عمومی {{ selected_unit.name|default:'واحد' }}</strong><small>اولین کارشناس آزاد همین واحد</small></label>{% for expert in form.fields.selected_expert.queryset %}<label class="expert-card"><input type="radio" name="selected_expert" value="{{ expert.pk }}" {% if form.selected_expert.value|stringformat:'s' == expert.pk|stringformat:'s' %}checked{% endif %}><img src="{% url 'tickets:expert_avatar' expert.user.public_id %}?v={{ expert.updated_at|date:'U' }}" alt="تصویر {{ expert.display_name }}" width="56" height="56" loading="lazy"><strong>{{ expert.display_name }}</strong><small>{{ expert.job_title|default:'کارشناس واحد' }}</small></label>{% empty %}<p class="muted expert-empty">کارشناس قابل انتخابی ثبت نشده؛ درخواست به صف عمومی می‌رود.</p>{% endfor %}</fieldset>{{ form.selected_expert.errors }}
<div class="step-title"><span class="step-number">۴</span><div><h3>شرح و مستندات</h3><p class="muted">متن، فایل یا پیام صوتی خود را اضافه کنید.</p></div></div>
<div class="field">{{ form.body.label_tag }}{{ form.body }}{{ form.body.errors }}</div>
<div class="upload-zone media-picker"><div class="media-picker-actions"><label class="media-picker-button gallery-button" for="ticket-gallery-input">▣ انتخاب عکس از گالری</label><label class="media-picker-button" for="ticket-file-input">+ انتخاب فایل</label></div><input id="ticket-gallery-input" type="file" accept="image/*" multiple data-gallery-input><input id="ticket-file-input" type="file" multiple data-document-input><span class="canonical-files">{{ form.files }}</span>{{ form.files.errors }}<small class="compression-note" data-compression-status>تصاویر بزرگ پیش از ارسال، روی گوشی شما بهینه و کم‌حجم می‌شوند.</small><div class="file-preview" data-file-preview></div></div>
<div class="voice-recorder" data-voice-recorder><div class="voice-head"><b>پیام صوتی</b><span class="badge" data-timer>00:00</span></div><div class="voice-actions"><button type="button" data-record>● شروع ضبط</button><button type="button" data-pause disabled>مکث</button><button type="button" data-stop disabled>توقف</button><button type="button" class="ghost" data-delete hidden>حذف</button></div><audio class="mt-3 w-full" controls hidden></audio></div>
<button class="submit-ticket" type="submit">ثبت و ارسال درخواست <span>←</span></button>
</form></section>
<aside class="request-aside"><div class="help-card"><h3>برای پاسخ سریع‌تر</h3><div class="help-list"><div class="help-item"><b>۱</b><span>نام محصول یا خدمت موردنظر را دقیق بنویسید.</span></div><div class="help-item"><b>۲</b><span>تعداد، شهر مقصد و زمان موردنظر را ذکر کنید.</span></div><div class="help-item"><b>۳</b><span>تصویر یا فایل مرتبط را همین‌جا ضمیمه کنید.</span></div></div></div></aside>
<section class="ticket-wizard-step" data-wizard-step="2" aria-labelledby="wizard-step-2-title">
<header class="ticket-wizard-step-head">
<span class="ticket-wizard-symbol" aria-hidden="true">⌖</span>
<div><small>مرحله ۲ از ۵ · اختیاری</small><h2 id="wizard-step-2-title">این درخواست مربوط به کجاست؟</h2><p>اگر موقعیت در پاسخ بهتر کارشناسان مؤثر است آن را انتخاب کنید؛ در غیر این صورت عبور کنید.</p></div>
</header>
<section class="ticket-location-fields" data-location-picker data-locations-url="{% static 'data/iran-locations.json' %}">
<div class="ticket-two-columns"><div class="field">{{ form.province.label_tag }}{{ form.province }}{{ form.province.errors }}</div><div class="field">{{ form.city.label_tag }}{{ form.city }}{{ form.city.errors }}</div></div>
<p class="location-hint" data-location-hint>نام استان را جست‌وجو و انتخاب کنید.</p>
</section>
<div class="ticket-soft-tip"><span>i</span><p><b>نیازی به انتخاب ندارید</b> این بخش کاملاً اختیاری است و می‌توانید مستقیماً ادامه دهید.</p></div>
<p class="ticket-wizard-validation" data-wizard-error="2" role="alert"></p>
<footer class="ticket-wizard-actions"><button class="secondary" type="button" data-wizard-prev>مرحله قبل</button><button class="primary" type="button" data-wizard-next>ادامه <b>←</b></button></footer>
</section>
<section class="ticket-wizard-step" data-wizard-step="3" aria-labelledby="wizard-step-3-title">
<header class="ticket-wizard-step-head">
<span class="ticket-wizard-symbol" aria-hidden="true">◎</span>
<div><small>مرحله ۳ از ۵ · اختیاری</small><h2 id="wizard-step-3-title">چه کسی درخواست را بررسی کند؟</h2><p>صف عمومی پیشنهاد ماست؛ درخواست به اولین کارشناس آزاد همان واحد می‌رسد.</p></div>
</header>
<fieldset class="expert-picker ticket-expert-picker" data-expert-picker>
<legend class="sr-only">کارشناس دلخواه</legend>
<label class="expert-card" data-general-queue data-unit-id="{{ selected_unit.pk|default:'' }}"><input type="radio" name="selected_expert" value="" {% if not form.selected_expert.value %}checked{% endif %}><span class="expert-avatar">⌁</span><strong>صف عمومی {{ selected_unit.name|default:'واحد' }}</strong><small>سریع‌ترین انتخاب · اولین کارشناس آزاد</small></label>
{% for expert in form.fields.selected_expert.queryset %}<label class="expert-card"><input type="radio" name="selected_expert" value="{{ expert.pk }}" {% if form.selected_expert.value|stringformat:'s' == expert.pk|stringformat:'s' %}checked{% endif %}><img src="{% url 'tickets:expert_avatar' expert.user.public_id %}?v={{ expert.updated_at|date:'U' }}" alt="تصویر {{ expert.display_name }}" width="56" height="56" loading="lazy"><strong>{{ expert.display_name }}</strong><small>{{ expert.job_title|default:'کارشناس واحد' }}</small></label>{% empty %}<p class="muted expert-empty">کارشناس قابل انتخابی ثبت نشده؛ درخواست به صف عمومی می‌رود.</p>{% endfor %}
</fieldset>
{{ form.selected_expert.errors }}
<p class="ticket-wizard-validation" data-wizard-error="3" role="alert"></p>
<footer class="ticket-wizard-actions"><button class="secondary" type="button" data-wizard-prev>مرحله قبل</button><button class="primary" type="button" data-wizard-next>ادامه <b>←</b></button></footer>
</section>
<section class="ticket-wizard-step" data-wizard-step="4" aria-labelledby="wizard-step-4-title">
<header class="ticket-wizard-step-head">
<span class="ticket-wizard-symbol" aria-hidden="true">≡</span>
<div><small>مرحله ۴ از ۵</small><h2 id="wizard-step-4-title">جزئیات را برای کارشناس بنویسید</h2><p>شرح متنی، فایل یا پیام صوتی اضافه کنید تا کارشناس مسئله را دقیق‌تر بررسی کند.</p></div>
</header>
<div class="field ticket-body-field">{{ form.body.label_tag }}{{ form.body }}{{ form.body.errors }}<small><span data-body-count>۰</span> نویسه</small></div>
<div class="upload-zone media-picker ticket-attachment-zone">
<header class="ticket-attachment-head"><span>+</span><div><b>عکس و فایل‌های مرتبط</b><small>تصاویر بزرگ پیش از ارسال روی دستگاه شما بهینه می‌شوند.</small></div></header>
<div class="media-picker-actions ticket-attachment-actions"><label class="media-picker-button gallery-button" for="ticket-gallery-input">▣ انتخاب عکس از گالری</label><label class="media-picker-button" for="ticket-file-input">+ انتخاب فایل</label></div>
<input id="ticket-gallery-input" type="file" accept="image/*" multiple data-gallery-input><input id="ticket-file-input" type="file" multiple data-document-input>
<span class="canonical-files">{{ form.files }}</span>{{ form.files.errors }}
<small class="compression-note" data-compression-status>فایل‌های انتخاب‌شده پیش از ثبت قابل مشاهده و حذف هستند.</small>
<div class="file-preview" data-file-preview aria-live="polite"></div>
</div>
<section class="ticket-voice-recorder" data-ticket-voice-recorder aria-labelledby="ticket-voice-title">
<header class="ticket-voice-head"><span class="ticket-voice-icon" aria-hidden="true">◉</span><div><b id="ticket-voice-title">پیام صوتی</b><small data-voice-status aria-live="polite">برای توضیح سریع‌تر، صدای خود را ضبط کنید.</small></div><span class="ticket-voice-timer" data-voice-timer>۰۰:۰۰</span></header>
<div class="ticket-voice-idle" data-voice-idle><button class="ticket-voice-start" type="button" data-voice-record><span>●</span> شروع ضبط صدا</button><small>پس از اجازه‌دادن به میکروفن، ضبط آغاز می‌شود.</small></div>
<div class="ticket-voice-live" data-voice-live hidden><div class="ticket-voice-wave" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div><div class="ticket-voice-controls"><button type="button" data-voice-pause>مکث</button><button class="ticket-voice-stop" type="button" data-voice-stop>■ توقف و ذخیره</button><button class="ticket-voice-cancel" type="button" data-voice-cancel>لغو</button></div></div>
<div class="ticket-voice-preview" data-voice-preview hidden><div class="ticket-voice-ready"><span>✓</span><p><b>پیام صوتی آماده است</b><small data-voice-meta>قابل پخش و بررسی پیش از ارسال</small></p></div><audio controls preload="metadata" data-voice-audio></audio><div class="ticket-voice-preview-actions"><button type="button" data-voice-rerecord>ضبط مجدد</button><button type="button" data-voice-delete>حذف پیام صوتی</button></div></div>
<p class="ticket-voice-note"><span>ⓘ</span> با رفتن به مرحله بعد، ضبط فعال به‌صورت خودکار متوقف و ذخیره می‌شود.</p>
<p class="ticket-voice-error" data-voice-error role="alert" hidden></p>
</section>
<div class="ticket-server-errors" data-step-server-errors>{% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %}</div>
<p class="ticket-wizard-validation" data-wizard-error="4" role="alert"></p>
<footer class="ticket-wizard-actions"><button class="secondary" type="button" data-wizard-prev>مرحله قبل</button><button class="primary" type="button" data-wizard-next>مشاهده پیش‌نمایش <b>←</b></button></footer>
</section>
<section class="ticket-wizard-step ticket-review-step" data-wizard-step="5" aria-labelledby="wizard-step-5-title">
<header class="ticket-wizard-step-head"><span class="ticket-wizard-symbol is-success" aria-hidden="true">✓</span><div><small>مرحله ۵ از ۵</small><h2 id="wizard-step-5-title">همه‌چیز آماده ارسال است</h2><p>اطلاعات را یک‌بار مرور کنید؛ هر بخش را می‌توانید جداگانه ویرایش کنید.</p></div></header>
<div class="ticket-review-callout"><span>✓</span><div><b>یک مرور کوتاه پیش از ارسال</b><small>اگر موردی درست نیست، همان ردیف را ویرایش کنید؛ اطلاعات مراحل دیگر حفظ می‌شود.</small></div></div>
<div class="ticket-review-list">
<section class="ticket-review-row"><span class="ticket-review-icon">۱</span><div class="ticket-review-content"><small>موضوع و مسیر رسیدگی</small><h3 data-review-subject>—</h3><p><span data-review-unit>—</span><i>←</i><span data-review-category>—</span></p></div><button type="button" data-wizard-edit="1"><span>✎</span> ویرایش</button></section>
<section class="ticket-review-row"><span class="ticket-review-icon">۲</span><div class="ticket-review-content"><small>موقعیت</small><h3 data-review-location>ثبت نشده</h3><p>اختیاری</p></div><button type="button" data-wizard-edit="2"><span>✎</span> ویرایش</button></section>
<section class="ticket-review-row"><span class="ticket-review-icon">۳</span><div class="ticket-review-content"><small>نحوه ارجاع</small><h3 data-review-expert>صف عمومی</h3><p>در همان واحد سازمانی</p></div><button type="button" data-wizard-edit="3"><span>✎</span> ویرایش</button></section>
<section class="ticket-review-row ticket-review-details"><span class="ticket-review-icon">۴</span><div class="ticket-review-content"><small>شرح و پیوست‌ها</small><p class="ticket-review-body" data-review-body>—</p><div class="ticket-review-files" data-review-files></div><audio class="ticket-review-audio" controls hidden data-review-audio></audio></div><button type="button" data-wizard-edit="4"><span>✎</span> ویرایش</button></section>
</div>
<p class="ticket-wizard-validation" data-wizard-error="5" role="alert"></p>
<footer class="ticket-wizard-actions ticket-final-actions"><button class="secondary" type="button" data-wizard-prev>مرحله قبل</button><button class="primary" type="submit" data-final-submit>ثبت و ارسال نهایی <b>←</b></button></footer>
</section>
</form>
</div>
<script src="{% static 'vendor/compressor.min.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/location-picker.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/ticket-unit-options.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/ticket-intent-search.js' %}?v={{ static_asset_version }}"></script><script src="{% static 'js/attachment-uploader.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'vendor/compressor.min.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'js/location-picker.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'js/ticket-unit-options.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'js/ticket-intent-search.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'js/attachment-uploader.js' %}?v={{ static_asset_version }}"></script>
<script src="{% static 'js/customer-ticket-wizard.js' %}?v={{ static_asset_version }}"></script>
{% endblock %}