Files
khatibi/admin_panel/templates/admin_panel/customerActionInc.html
T
2026-10-22 14:01:08 +03:30

975 lines
49 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.
{% load custom_tags %}
{% load static %}
{% with url_name=request.resolver_match.url_name %}
<div id="action" class="column is-12-mobile {% if url_name == 'customerAction' %}is-12-desktop{% else %} is-6-desktop {% endif %}">
{% endwith %}
{% if customer %}
<div class="box mb-5 has-background-white-ter" style="border: 1px solid #e0e0e0; box-shadow: none; border-radius: 6px;">
<form method="GET" action="{% url 'admin_panel:customerAction' customer.id %}">
<div class="columns is-mobile is-multiline is-vcentered m-0">
<div class="column is-4-desktop is-12-mobile pb-0">
<div class="field">
<label class="label is-small">نوع اقدام</label>
<div class="control">
<div class="select is-fullwidth is-small">
<select name="actionType">
<option value="all" {% if filter_type == 'all' %}selected{% endif %}>همه موارد</option>
<option value="bazdid" {% if filter_type == 'bazdid' %}selected{% endif %}>بازدید</option>
<option value="nasb" {% if filter_type == 'nasb' %}selected{% endif %}>نصب</option>
<option value="vosol" {% if filter_type == 'vosol' %}selected{% endif %}>وصول مطالبات</option>
<option value="hamahangi" {% if filter_type == 'hamahangi' %}selected{% endif %}>هماهنگی</option>
<option value="inCall" {% if filter_type == 'inCall' %}selected{% endif %}>تماس ورودی</option>
<option value="outCall" {% if filter_type == 'outCall' %}selected{% endif %}>تماس خروجی</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-4-desktop is-12-mobile pb-0">
<div class="field">
<label class="label is-small">وضعیت انجام</label>
<div class="control">
<div class="select is-fullwidth is-small">
<select name="status">
<option value="all" {% if filter_status == 'all' %}selected{% endif %}>همه وضعیت‌ها</option>
<option value="waite" {% if filter_status == 'waite' %}selected{% endif %}>در انتظار (باز)</option>
<option value="finish" {% if filter_status == 'finish' %}selected{% endif %}>انجام شده</option>
<option value="cancell" {% if filter_status == 'cancell' %}selected{% endif %}>لغو شده</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-4-desktop is-12-mobile mt-4 has-text-left pb-0">
<button type="submit" class="button is-info is-small px-4">اعمال فیلتر</button>
<a href="{% url 'admin_panel:customerAction' customer.id %}" class="button is-light is-small">حذف فیلتر</a>
</div>
</div>
</form>
</div>
<span id="showActionBox" class="button is-info mb-5">ایجاد وظیفه جدید</span>
{% endif %}
{% if not actions and not actionPr %}
<div class="notification is-warning is-light has-text-centered">
هیچ اقدامی با مشخصات انتخاب‌شده یافت نشد!
</div>
{% endif %}
{% for action in actions %}
<div customer_id='{{ action.customer.id }}' action_id='{{ action.id }}'
customer_hold='{% if action.customer.visit_hold_until %}{{ action.customer.visit_hold_until|date:"c" }}{% endif %}'
class="columns is-mobile is-multiline is-gapless actionBox {{ bgBox }}">
<div class="column is-12-mobile is-12-desktop actionBoxDesc actionBoxOpenB">
<div class="pr-4 pl-4 pb-4 pt-4 ax-cardrow">
<span class="ax-cardrow-main">
{% ifequal action.status 'finish' %}
<img class="" style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/check.png' %}">
{% endifequal %}
{% ifequal action.status 'waite' %}
<img style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/time1.png' %}">
{% endifequal %}
{% ifequal action.status 'cancell' %}
<img style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/cancell.png' %}">
{% endifequal %}
<span class="is-bazdid">{% ifequal action.actionType 'nasb' %}نصب{% endifequal %}{% ifequal action.actionType 'bazdid' %}بازدید{% endifequal %}{% ifequal action.actionType 'vosol' %}وصول مطالبات{% endifequal %}{% ifequal action.actionType 'hamahangi' %}هماهنگی{% endifequal %}{% ifequal action.actionType 'inCall' %}تماس ورودی{% endifequal %}{% ifequal action.actionType 'outCall' %}تماس خروجی{% endifequal %}</span>
<span class="pr-3">{{ action.desc }}</span>
</span>
{% if show_delete %}
{% if action.nazarDT %}
<span class="ax-del is-locked" title="این اقدام دارای نظرسنجی ثبت‌شده است و قابل حذف نیست">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5zm-3 8V6a3 3 0 0 1 6 0v3H9z"/></svg>
<span class="ax-del-text">نظرسنجی</span>
</span>
{% else %}
<form method="post" class="ax-del-form"
action="{% url 'admin_panel:customerActionDelete' action.customer.id action.id %}">
{% csrf_token %}
<button type="submit" class="ax-del is-danger"
title="حذف این اقدام"
data-type="{% ifequal action.actionType 'nasb' %}نصب{% endifequal %}{% ifequal action.actionType 'bazdid' %}بازدید{% endifequal %}{% ifequal action.actionType 'vosol' %}وصول مطالبات{% endifequal %}{% ifequal action.actionType 'hamahangi' %}هماهنگی{% endifequal %}{% ifequal action.actionType 'inCall' %}تماس ورودی{% endifequal %}{% ifequal action.actionType 'outCall' %}تماس خروجی{% endifequal %}"
onclick="return confirm('این «' + (this.dataset.type || 'اقدام') + '» حذف شود؟\n\nاقدام از فهرست انتظار/وظایف خارج می‌شود و پیامک ارسال‌نشدهٔ مرتبط لغو می‌گردد. سابقه برای حسابرسی باقی می‌ماند.');">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 2h4v2H4V5h4l1-2zm-3 6h12l-1 12a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L6 9zm4 3v8h2v-8h-2zm4 0v8h2v-8h-2z"/></svg>
<span class="ax-del-text">حذف</span>
</button>
</form>
{% endif %}
{% endif %}
</div>
</div>
<div class="column is-12-mobile is-12-desktop actionBoxOpenB">
<div class="pr-4 pl-4 pb-4 pt-4 actionBoxBorder">
<span class="is-result">نتیجه</span>
<span>{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}</span>
</div>
</div>
<div class="column is-4-mobile is-4-desktop">
<div class="has-text-centered p-3">
<span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div>
</div>
<div class="column is-8-mobile is-4-desktop">
<div class="has-text-centered actionBoxFtBox p-3">
<a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span>
</a>
</div>
</div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile">
<div class="has-text-centered p-3">
<span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div>
</div>
</div>
{% endfor %}
{% if actionPr %}
<hr>
<p style="text-align: center; margin-bottom: 20px; color: #d90000; font-size: 18px;"> کارهای مانده از روزهای قبل</p>
{% for action in actionPr %}
<div customer_id='{{ action.customer.id }}' action_id='{{ action.id }}'
customer_hold='{% if action.customer.visit_hold_until %}{{ action.customer.visit_hold_until|date:"c" }}{% endif %}'
class="columns is-mobile is-multiline is-gapless actionBox {{ bgBox }}">
<div class="column is-12-mobile is-12-desktop actionBoxDesc actionBoxOpenB">
<div class="pr-4 pl-4 pb-4 pt-4">
{% ifequal action.status 'finish' %}
<img class="" style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/check.png' %}">
{% endifequal %}
{% ifequal action.status 'waite' %}
<img style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/time1.png' %}">
{% endifequal %}
{% ifequal action.status 'cancell' %}
<img style="width: 20px; height: 20px;" src="{% static 'admin_panel/icons/cancell.png' %}">
{% endifequal %}
<span class="is-bazdid">{% ifequal action.actionType 'nasb' %}نصب{% endifequal %}{% ifequal action.actionType 'bazdid' %}بازدید{% endifequal %}{% ifequal action.actionType 'vosol' %}وصول مطالبات{% endifequal %}{% ifequal action.actionType 'hamahangi' %}هماهنگی{% endifequal %}{% ifequal action.actionType 'inCall' %}تماس ورودی{% endifequal %}{% ifequal action.actionType 'outCall' %}تماس خروجی{% endifequal %}</span>
<span class="pr-3">{{ action.desc }}</span>
</div>
</div>
<div class="column is-12-mobile is-12-desktop actionBoxOpenB">
<div class="pr-4 pl-4 pb-4 pt-4 actionBoxBorder">
<span class="is-result">نتیجه</span>
<span>{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}</span>
</div>
</div>
<div class="column is-4-mobile is-4-desktop">
<div class="has-text-centered p-3">
<span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div>
</div>
<div class="column is-8-mobile is-4-desktop">
<div class="has-text-centered actionBoxFtBox p-3">
<a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span>
</a>
</div>
</div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile">
<div class="has-text-centered p-3">
<span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
<style>
/* ===== دکمهٔ «حذف» روی کارت اقدام (mobile first) ===== */
.ax-cardrow {
display: flex; align-items: flex-start; justify-content: space-between;
gap: .5rem; flex-wrap: nowrap;
}
.ax-cardrow-main { flex: 1 1 auto; min-width: 0; }
.ax-del-form { margin: 0; flex: 0 0 auto; }
.ax-del {
display: inline-flex; align-items: center; gap: .25rem;
padding: .2rem .45rem; border-radius: .375rem; border: 1px solid #e5e7eb;
background: #fff; color: #6b7280; font-size: .68rem; font-weight: 700;
cursor: pointer; line-height: 1.5; white-space: nowrap;
transition: background-color .15s ease, border-color .15s ease,
color .15s ease, box-shadow .15s ease;
}
.ax-del svg { width: 13px; height: 13px; fill: currentColor; flex: 0 0 auto; }
.ax-del:hover { background: #fef2f2; border-color: #fecaca; color: #b91c1c; }
.ax-del:focus-visible {
outline: 2px solid #fca5a5; outline-offset: 1px;
}
.ax-del.is-danger { border-color: #fee2e2; color: #dc2626; background: #fff; }
.ax-del.is-danger:hover {
background: #dc2626; border-color: #dc2626; color: #fff;
box-shadow: 0 1px 6px rgba(220, 38, 38, .25);
}
/* «نظرسنجی دارد» ⇒ دکمه غیرفعال (قاعدهٔ محصولی) */
.ax-del.is-locked {
cursor: not-allowed; color: #9ca3af; background: #f9fafb;
border-color: #e5e7eb;
}
.ax-del.is-locked:hover { background: #f9fafb; border-color: #e5e7eb; color: #9ca3af; }
@media (max-width: 480px) {
.ax-del-text { display: none; } /* در موبایل فقط آیکون، برای صرفه‌جویی فضا */
.ax-del { padding: .25rem; }
}
/* ===== مودال «ایجاد/ویرایش وظیفه» — mobile first =====
همهٔ کلاس‌ها با پیشوند ax- هستند تا با سایر بخش‌ها تداخل نکنند. */
#ActionBox .ax-card {
width: calc(100% - 1rem); max-width: 44rem;
max-height: calc(100vh - 1rem); display: flex; flex-direction: column;
border-radius: .75rem; overflow: hidden;
}
#ActionBox .ax-head {
display: flex; align-items: center; justify-content: space-between;
gap: .5rem; padding: .75rem 1rem; flex: 0 0 auto;
background: #f8fafc; border-bottom: 1px solid #e5e7eb;
}
#ActionBox .ax-title { font-size: .95rem; font-weight: 800; color: #0f172a; margin: 0; }
#ActionBox .ax-sub { font-size: .75rem; color: #64748b; margin: .1rem 0 0; }
#ActionBox .ax-body {
padding: .75rem 1rem; overflow-y: auto; -webkit-overflow-scrolling: touch;
flex: 1 1 auto;
}
#ActionBox .ax-foot {
display: flex; gap: .5rem; justify-content: flex-start;
padding: .6rem 1rem; flex: 0 0 auto;
background: #f8fafc; border-top: 1px solid #e5e7eb;
}
#ActionBox .ax-foot .button { margin: 0 !important; min-height: 2.6rem; flex: 1 1 0; }
@media (min-width: 768px) {
#ActionBox .ax-foot { justify-content: flex-start; }
#ActionBox .ax-foot .button { flex: 0 0 auto; min-width: 7rem; }
}
/* بخش‌ها */
#ActionBox .ax-section { padding: .6rem 0; border-bottom: 1px dashed #eef2f6; }
#ActionBox .ax-section:last-of-type { border-bottom: 0; }
#ActionBox .ax-legend {
display: flex; align-items: center; gap: .4rem;
font-size: .78rem; font-weight: 800; color: #0e7490; margin: 0 0 .5rem;
}
#ActionBox .ax-step {
display: inline-flex; align-items: center; justify-content: center;
width: 1.35rem; height: 1.35rem; border-radius: 999px;
background: #33b5d3; color: #fff; font-size: .7rem; font-weight: 800;
}
/* شبکهٔ فیلدها: موبایل تک‌ستونه، دسکتاپ دوستونه */
#ActionBox .ax-grid { display: grid; grid-template-columns: 1fr; gap: .6rem; }
@media (min-width: 768px) {
#ActionBox .ax-grid { grid-template-columns: 1fr 1fr; }
#ActionBox .ax-span-2 { grid-column: 1 / -1; }
}
#ActionBox .ax-label {
font-size: .74rem !important; font-weight: 700; color: #475569; margin-bottom: .25rem;
}
#ActionBox .ax-help {
font-size: .7rem; color: #94a3b8; margin-top: .2rem; line-height: 1.5;
}
/* هدف لمسی بزرگ‌تر روی موبایل */
#ActionBox .input, #ActionBox .select select, #ActionBox .textarea {
font-size: .85rem;
}
@media (max-width: 767px) {
#ActionBox .input, #ActionBox .select select { min-height: 2.6rem; }
#ActionBox .textarea { min-height: 4.5rem; }
}
#ActionBox .select, #ActionBox .select select { width: 100%; }
/* ردیف تیک «انجام شده» */
#ActionBox .ax-check {
display: flex; align-items: center; gap: .5rem;
padding: .55rem .7rem; border: 1px solid #e5e7eb; border-radius: .5rem;
background: #fff; cursor: pointer; margin: 0;
}
#ActionBox .ax-check input { width: 1.05rem; height: 1.05rem; }
/* هشدار «آیا هماهنگی را انجام دادی؟» — فقط برای هماهنگیِ تیک‌خورده */
#ActionBox .ax-warn {
margin-top: .5rem; padding: .6rem .7rem;
border: 1px solid #fcd34d; border-radius: .5rem; background: #fffbeb;
color: #92400e; font-size: .78rem; line-height: 1.75;
}
#ActionBox .ax-warn .ax-warn-q { font-weight: 800; color: #78350f; margin: 0 0 .15rem; }
#ActionBox .ax-warn .ax-warn-hint { margin: 0; }
#ActionBox .ax-warn .ax-warn-actions {
display: none; gap: .4rem; margin-top: .55rem; flex-wrap: wrap;
}
#ActionBox .ax-warn .ax-warn-actions .button { margin: 0 !important; flex: 1 1 auto; }
@media (min-width: 768px) {
#ActionBox .ax-warn .ax-warn-actions .button { flex: 0 0 auto; }
}
/* وقتی ارسال متوقف شده تا کاربر تأیید کند */
#ActionBox .ax-warn.is-confirming {
border-color: #f59e0b; box-shadow: 0 0 0 3px #fde68a66; background: #fef3c7;
}
#ActionBox .ax-warn.is-confirming .ax-warn-actions { display: flex; }
#ActionBox .ax-warn:focus { outline: 2px solid #f59e0b; outline-offset: 2px; }
/* اعلان «تاریخ قرار عقب‌تر» — کلاس مستقل تا به CSS پنل «انجام شد» وابسته نباشد */
#ActionBox .ax-holdback {
margin-top: .5rem; padding: .6rem .7rem;
border: 1px solid #fcd34d; border-radius: .5rem; background: #fffbeb;
color: #92400e; font-size: .78rem; line-height: 1.75;
}
#ActionBox .ax-holdback.is-blocking {
border-color: #f59e0b; box-shadow: 0 0 0 3px #fde68a66; background: #fef3c7;
}
#ActionBox .ax-holdback .ax-hb-title { font-weight: 800; color: #78350f; margin: 0 0 .15rem; }
#ActionBox .ax-holdback .ax-hb-current { margin: 0 0 .4rem; }
#ActionBox .ax-holdback .ax-hb-current b { font-weight: 800; }
#ActionBox .ax-holdback .ax-hb-alt { margin: 0; }
#ActionBox .ax-holdback .ax-hb-check {
display: flex; align-items: flex-start; gap: .5rem; margin: 0;
padding: .5rem .6rem; border-radius: .45rem; background: #fff;
border: 1px solid #fbbf24; cursor: pointer; font-weight: 700; color: #7c2d12;
}
#ActionBox .ax-holdback .ax-hb-check input {
width: 1.05rem; height: 1.05rem; margin-top: .15rem; flex: 0 0 auto;
}
#ActionBox .ax-holdback:focus { outline: 2px solid #f59e0b; outline-offset: 2px; }
#ActionBox .ax-help-toggle {
display: flex; align-items: center; gap: .45rem; width: 100%;
padding: .55rem .7rem; border: 1px solid #bae6fd; border-radius: .5rem;
background: #f0f9ff; color: #0369a1; font-size: .78rem; font-weight: 700;
cursor: pointer; text-align: right; line-height: 1.4;
}
#ActionBox .ax-help-toggle:hover { background: #e0f2fe; }
#ActionBox .ax-help-toggle .ax-ico {
display: inline-flex; align-items: center; justify-content: center;
width: 1.25rem; height: 1.25rem; border-radius: 999px; flex: 0 0 auto;
background: #0284c7; color: #fff; font-size: .72rem; font-weight: 800;
}
#ActionBox .ax-help-toggle .ax-chev {
margin-inline-start: auto; transition: transform .15s ease; font-size: .8rem;
}
#ActionBox .ax-help-toggle[aria-expanded="true"] .ax-chev { transform: rotate(180deg); }
#ActionBox .ax-help-panel {
margin-top: .4rem; padding: .7rem .8rem; border: 1px solid #e0f2fe;
border-radius: .5rem; background: #fbfeff; font-size: .76rem; color: #334155;
line-height: 1.85;
}
#ActionBox .ax-help-panel h4 {
font-size: .78rem; font-weight: 800; color: #0f172a; margin: .6rem 0 .25rem;
}
#ActionBox .ax-help-panel h4:first-child { margin-top: 0; }
#ActionBox .ax-help-panel ul { margin: 0; padding-inline-start: 1.1rem; }
#ActionBox .ax-help-panel li { margin-bottom: .2rem; }
#ActionBox .ax-help-panel .ax-eg {
display: block; margin-top: .35rem; padding: .5rem .6rem;
background: #fff; border: 1px dashed #bae6fd; border-radius: .4rem;
}
</style>
{% if customer %}
<form id="ActionBox" class="modal" method="post" action="{% url 'admin_panel:customerNewAction' customer.id %}"
data-customer-hold="{% if customer.visit_hold_until %}{{ customer.visit_hold_until|date:'c' }}{% endif %}">
{% else %}
<form id="ActionBox" class="modal" method="post" action="">
{% endif %}
{% csrf_token %}
{# تأیید صریح کاربر برای عقب‌بردن تاریخ قرار؛ فقط با زدن دکمهٔ تأیید پر می‌شود. #}
<input type="hidden" name="force_hold" id="forceHold" value="">
<div class="modal-background closeActionBox"></div>
<div class="modal-card ax-card">
<header class="modal-card-head ax-head">
<div>
<p class="ax-title">ایجاد وظیفه</p>
{% if customer %}<p class="ax-sub">{{ customer.name_family }}</p>{% endif %}
</div>
<button type="button" class="delete closeActionBox" aria-label="بستن"></button>
</header>
<section class="modal-card-body ax-body">
{# ---------- گام ۱: نوع اقدام و مسئول ---------- #}
<div class="ax-section">
<p class="ax-legend"><span class="ax-step">۱</span> نوع اقدام و مسئول</p>
<div class="ax-grid">
<div class="ax-field">
<label class="label ax-label" for="actionType">نوع اقدام</label>
<div class="select">
<select id="actionType" required name="actionType">
<option value="">انتخاب کنید</option>
<option value="bazdid">بازدید</option>
<option value="nasb">نصب</option>
<option value="vosol">وصول مطالبات</option>
<option value="hamahangi">هماهنگی</option>
<option value="inCall">تماس ورودی</option>
<option value="outCall">تماس خروجی</option>
</select>
</div>
</div>
<div class="ax-field">
<label class="label ax-label" for="userID">توسط</label>
<div class="select">
<select id="userID" required name="userID">
<option value="">انتخاب کنید</option>
{% if perms.admin_panel.ViewStore %}
{% for auser in allUsers %}
<option value="{{ auser.id }}">{{ auser.first_name }} {{ auser.last_name }}</option>
{% endfor %}
{% else %}
<option value="{{ request.user.id }}">{{ request.user.first_name }} {{ request.user.last_name }}</option>
<option value="6">خدمات پس از فروش</option>
{% endif %}
</select>
</div>
</div>
</div>
</div>
{# ---------- گام ۲: زمان‌بندی ---------- #}
<div class="ax-section">
<p class="ax-legend"><span class="ax-step">۲</span> زمان‌بندی</p>
<div class="ax-grid">
<div class="ax-field">
<label class="label ax-label">تاریخ و ساعتی که کار باید انجام شود</label>
<input type="text" class="input dateTimePickerAnjam" autocomplete="off">
<input id="dateTimeAnjam" name="dateTimeAnjam" required type="text" class="input is-hidden dateTimePickerAnjam-alt">
<p class="ax-help">تاریخ انجام این وظیفه (ثبت در «لیست وظایف» همان روز).</p>
</div>
{# فیلد «تا تاریخ پیگیری نشود» فقط برای «هماهنگی» #}
<div class="ax-field showWhenCoordination is-hidden">
<label class="label ax-label">تا تاریخ پیگیری نشود</label>
<input type="text" class="input dateTimePickerHoldUntil" autocomplete="off">
<input name="visitHoldUntil" id="visitHoldUntil" type="text" class="input is-hidden dateTimePickerHoldUntil-alt">
<p class="ax-help">تا پایان این روز، مشتری در «انتظار بازدید» دیده نمی‌شود. خالی = ۱۴ روز.</p>
</div>
</div>
{# اعلان «تاریخ عقب‌تر» — عرض کامل، بیرون از شبکهٔ دوستونه #}
<div class="ax-holdback is-hidden" id="holdBackwardNotice" tabindex="-1"
role="alert" aria-live="polite">
<p class="ax-hb-title">این تاریخ از قرار فعلی عقب‌تر است</p>
<p class="ax-hb-current">قرار فعلی: <b id="hbCurrent">—</b></p>
<label class="ax-hb-check is-hidden" id="hbForceRow" for="forceHoldCheck">
<input type="checkbox" id="forceHoldCheck">
<span>تاریخ قرار را عقب ببر</span>
</label>
<p class="ax-hb-alt is-hidden" id="hbCreateHint">
این تاریخ ذخیره نمی‌شود. برای تغییر قرار، وظیفهٔ هماهنگی فعلی را ویرایش کنید.
</p>
</div>
{# راهنمای بازشونده — فقط برای هماهنگی #}
<div class="ax-help-wrap showWhenCoordination is-hidden" style="margin-top: .6rem;">
<button type="button" class="ax-help-toggle" id="holdHelpToggle"
aria-expanded="false" aria-controls="holdHelpPanel">
<span class="ax-ico" aria-hidden="true">!</span>
<span>راهنمای «هماهنگی» و «تا تاریخ پیگیری نشود»</span>
<span class="ax-chev" aria-hidden="true">▾</span>
</button>
<div class="ax-help-panel is-hidden" id="holdHelpPanel">
<h4>این تاریخ چه کاری می‌کند؟</h4>
<p>می‌گوید «تا این تاریخ با این مشتری تماس نگیرید». در این بازه، مشتری از فهرست
<span class="has-text-weight-bold">انتظار بازدید</span> پنهان می‌شود و روز بعد از آن
تاریخ <span class="has-text-weight-bold">خودکار</span> برمی‌گردد. تاریخ تا
<span class="has-text-weight-bold">پایان همان روز</span> معتبر است.</p>
<h4>مثال</h4>
<div class="ax-eg">
امروز ۱ آذر است. مشتری می‌گوید «سه هفتهٔ دیگر زنگ بزنید».<br>
نوع = <b>هماهنگی</b> ← تیک «این کار انجام شده» ← «تا تاریخ پیگیری نشود» = <b>۲۲ آذر</b>.<br>
نتیجه: از ۱ تا ۲۲ آذر مشتری در فهرست نیست؛ از ۲۳ آذر خودکار برمی‌گردد.
</div>
<h4>نکات مهم</h4>
<ul>
<li><b>خالی + تیک</b> ← سیستم خودش ۱۴ روز می‌گذارد.</li>
<li><b>خالی + بدون تیک</b> ← هیچ تعویقی ثبت نمی‌شود؛ مشتری در فهرست می‌ماند.</li>
<li><b>فقط تمدید می‌کند، کوتاه نمی‌کند:</b> اگر قرار ۲۲ آذر باشد و همکار دیگری ۳۰ آذر بگذارد،
قرار به ۳۰ آذر می‌رود؛ تاریخ‌های عقب‌تر نادیده گرفته می‌شوند تا قرارِ دورتر از بین نرود.
اگر خودتان می‌خواهید قرار را عقب ببرید، وظیفه را <b>ویرایش</b> کنید و
گزینهٔ <b>«تاریخ قرار را عقب ببر»</b> را تیک بزنید
(در «ایجاد وظیفهٔ تازه» این گزینه وجود ندارد).</li>
<li><b>حذف یا لغو هماهنگی</b> ← قرار بی‌اثر می‌شود و مشتری بلافاصله برمی‌گردد.</li>
<li><b>سقف ۶ ماه</b> است؛ بیشتر از آن با پیام خطا رد می‌شود.</li>
<li>این فیلد <b>تاریخ بازدید پرسنل نیست</b> و <b>بازدید حساب نمی‌شود</b>؛
تاریخ آخرین بازدید و دورهٔ ۹ ماههٔ پیامک دست نمی‌خورد.
برای اعزام پرسنل از اقدام نوع <b>بازدید</b> استفاده کنید.</li>
<li>در مدت تعویق، <b>پیامک یادآوری ۹ ماههٔ بازدید</b> ارسال نمی‌شود.</li>
</ul>
</div>
</div>
</div>
{# ---------- گام ۳: توضیحات ---------- #}
<div class="ax-section">
<p class="ax-legend"><span class="ax-step">۳</span> توضیحات و وضعیت</p>
<div class="ax-grid">
<div class="ax-field ax-span-2">
<label class="label ax-label" for="desc">توضیحات برای یادآوری حین انجام کار</label>
<textarea id="desc" required name="desc" class="textarea"
placeholder="مثلا: با مشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
</div>
<div class="ax-field ax-span-2">
<label class="ax-check">
<input name="is_done" id="checkIsDone" type="checkbox">
<span>این کار انجام شده است</span>
</label>
{# هشدار/تأیید: فقط برای «هماهنگی» با تیکِ «انجام شده» #}
<div class="ax-warn is-hidden" id="holdDoneWarning" tabindex="-1"
role="alert" aria-live="polite">
<p class="ax-warn-q">آیا مطمئن هستی که هماهنگی را انجام دادی؟</p>
<p class="ax-warn-hint">اگر هنوز هماهنگی نکردی، این تیک را بردار.</p>
<div class="ax-warn-actions">
<button type="button" id="holdConfirmYes" class="button is-warning is-small">
بله، هماهنگی انجام شد
</button>
<button type="button" id="holdConfirmNo" class="button is-light is-small">
نه، تیک را بردار
</button>
</div>
</div>
</div>
<div class="ax-field showWhenDone is-hidden">
<label class="label ax-label" for="FinishDateTime">تاریخ و ساعت اتمام کار</label>
<input id="FinishDateTime" type="text" class="input dateTimePickerFinish" autocomplete="off">
<input name="FinishDateTime" type="text" class="input dateTimePickerFinish-alt is-hidden">
<p class="ax-help">خالی بماند، زمان ثبت در نظر گرفته می‌شود.</p>
</div>
<div class="ax-field showWhenDone is-hidden">
<label class="label ax-label" for="actionResult">نتیجه</label>
<textarea name="result" id="actionResult" class="textarea"
placeholder="نتیجهٔ کار یا پیگیری انجام‌شده را بنویسید"></textarea>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot ax-foot">
<button type="button" class="button closeActionBox">انصراف</button>
<input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/>
</footer>
</div>
</form>
<script>
// وضعیت مودال:
// isEditMode ⇒ در حال ویرایش یک اقدام موجود (نه ایجاد)
// holdAutoChecked ⇒ تیک «انجام شده» را خودِ سیستم برای هماهنگی زده است
// holdConfirmed ⇒ کاربر تأیید کرده که هماهنگی انجام شده است
var isEditMode = false;
var holdAutoChecked = false;
var holdConfirmed = false;
// لایهٔ دوم محافظت: فرم «حذف» داخل کارتِ قابل‌کلیک است؛ رویداد را همان‌جا
// متوقف می‌کنیم تا به هندلرِ بازکردن مودالِ ویرایش نرسد.
// بایند «مستقیم» (نه delegated) چون این بلوک با هر ردیف تکرار می‌شود؛
// نام‌فضا (axdel) + off مانع از انباشته‌شدن هندلرها روی ردیف‌های تکراری می‌شود.
$('.ax-del-form, .ax-del')
.off('.axdel')
.on('click.axdel mousedown.axdel', function (e) {
e.stopPropagation();
});
// پیکر «تا تاریخ پیگیری نشود» را ریست می‌کند.
// نکته: pDatepicker یک Model برمی‌گرداند؛ اگر سلکتور هیچ المانی پیدا نکند
// مقدار undefined می‌شود. بدون این محافظ، یک ناهم‌خوانی نام کلاس باعث
// TypeError می‌شد و بقیهٔ منطقِ هندلر (پنهان‌کردن فیلدها، برداشتن تیک خودکار)
// اجرا نمی‌شد.
// پرکردن پیکر «تا تاریخ پیگیری نشود» از مقدار ذخیره‌شدهٔ مشتری.
// بدون این، هنگام بازکردن مجدد یک اقدام، فیلد همیشه خالی دیده می‌شد
// (مقدار در دیتابیس بود ولی مودال آن را بارگذاری نمی‌کرد).
function seedHoldPicker(isoValue) {
var box = $("#visitHoldUntil");
if (!isoValue) {
resetHoldPicker();
return;
}
var ms = new Date(isoValue).getTime();
if (isNaN(ms)) {
resetHoldPicker();
return;
}
if (dateTimePickerHoldUntil && typeof dateTimePickerHoldUntil.setDate === 'function') {
dateTimePickerHoldUntil.setDate(ms);
}
box.val(String(ms));
}
function resetHoldPicker() {
if (dateTimePickerHoldUntil && typeof dateTimePickerHoldUntil.setDate === 'function') {
dateTimePickerHoldUntil.setDate();
}
$("#visitHoldUntil").val('');
}
$(".closeActionBox").click(function(){
$("#ActionBox").removeClass("is-active");
});
$("#showActionBox").click(function(){
isEditMode = false;
holdAutoChecked = false;
holdConfirmed = false;
$("#holdDoneWarning").addClass("is-hidden").removeClass("is-confirming");
$("#ActionBox").addClass("is-active");
{% if customer %}
var NewActionUrl = "{% url 'admin_panel:customerNewAction' customer.id %}";
$("#ActionBox").prop("action", NewActionUrl);
{% endif %}
$("#actionType").val('');
$("#desc").val('');
$("#userID").val('');
$("#checkIsDone").prop('checked', false);
$(".showWhenDone").addClass("is-hidden");
$("#actionResult").val('');
dateTimePickerAnjam.setDate();
dateTimePickerFinish.setDate();
resetHoldPicker();
syncHoldBackward();
$(".showWhenCoordination").addClass("is-hidden");
closeHoldHelp();
$("#sabtBtn05").val("ثبت");
});
// نمایش/پنهان‌کردن فیلدهای «انجام شده».
function syncDoneFields() {
$(".showWhenDone").toggleClass("is-hidden", !$("#checkIsDone").prop("checked"));
}
// هشدار «آیا هماهنگی را انجام دادی؟» فقط برای هماهنگیِ تیک‌خورده نمایش داده می‌شود.
function syncHoldWarning() {
var active = ($("#actionType").val() === 'hamahangi') && $("#checkIsDone").prop("checked");
$("#holdDoneWarning").toggleClass("is-hidden", !active);
if (!active) {
$("#holdDoneWarning").removeClass("is-confirming");
holdConfirmed = false;
}
}
$("#checkIsDone").change(function() {
// تغییر دستی کاربر ⇒ دیگر «تیک خودکار» نیست.
holdAutoChecked = false;
holdConfirmed = false;
syncDoneFields();
syncHoldWarning();
});
// تأیید یا ردِ هشدار در لحظهٔ ثبت
function showHoldConfirm() {
var box = $("#holdDoneWarning");
box.removeClass("is-hidden").addClass("is-confirming");
if (box[0].scrollIntoView) {
box[0].scrollIntoView({ block: 'center', behavior: 'smooth' });
}
box.trigger('focus');
}
$("#holdConfirmYes").click(function() {
holdConfirmed = true;
// submit مستقیم روی المان فرم؛ اعتبارسنجی HTML5 در تلاش اول انجام شده است.
$("#ActionBox")[0].submit();
});
$("#holdConfirmNo").click(function() {
holdConfirmed = false;
holdAutoChecked = false;
$("#checkIsDone").prop("checked", false);
$("#holdDoneWarning").addClass("is-hidden").removeClass("is-confirming");
syncDoneFields();
$("#checkIsDone").trigger('focus');
});
// ══════════ عقب‌بردن تاریخ قرار ══════════
// قاعدهٔ سیستم: تاریخ «تا تاریخ پیگیری نشود» فقط رو به جلو می‌رود تا قرارِ
// دورتری که ثبت شده از بین نرود. در «ویرایش» اگر کاربر خودش بخواهد عقب ببرد،
// قرار فعلی را می‌بیند و با تیک زدن اجازهٔ صریح می‌دهد. در «ایجادِ» وظیفهٔ تازه
// این اجازه داده نمی‌شود؛ چون کاربر قصد اصلاح قرارِ موجود را ندارد و ممکن است
// ناخواسته قولِ ثبت‌شدهٔ قبلی را کوتاه کند.
function holdCurrentMs() {
var iso = ($("#ActionBox").attr('data-customer-hold') || '').trim();
if (!iso) { return null; }
var ms = new Date(iso).getTime();
return isNaN(ms) ? null : ms;
}
// مقدار پیکر در فیلد مخفی به‌صورت timestamp میلی‌ثانیه‌ای است؛ اگر پیکر
// تاریخ شمسی متنی گذاشت، همان را هم می‌پذیریم.
function holdTargetMs() {
var raw = ($("#visitHoldUntil").val() || '').trim();
if (!raw) { return null; }
if (/^\d+$/.test(raw)) {
var n = parseInt(raw, 10);
return isNaN(n) ? null : n;
}
var parsed = Date.parse(raw.replace(/\//g, '-'));
return isNaN(parsed) ? null : parsed;
}
function sameLocalDay(a, b) {
var da = new Date(a), db = new Date(b);
return da.getFullYear() === db.getFullYear()
&& da.getMonth() === db.getMonth()
&& da.getDate() === db.getDate();
}
function jalaliDay(ms) {
if (ms === null) { return '—'; }
try {
if (typeof persianDate !== 'undefined') {
return new persianDate(ms).format('YYYY/MM/DD');
}
} catch (err) { /* کتابخانه در دسترس نیست ⇒ تاریخ میلادی */ }
var d = new Date(ms);
return d.getFullYear() + '/' + (d.getMonth() + 1) + '/' + d.getDate();
}
// آیا تاریخ انتخابی از قرار فعلی عقب‌تر است؟ (مستقل از تیک کاربر)
function holdIsBackward() {
if ($("#actionType").val() !== 'hamahangi') { return false; }
var current = holdCurrentMs(), target = holdTargetMs();
if (current === null || target === null) { return false; }
if (sameLocalDay(current, target)) { return false; }
return target < current;
}
// به‌روزرسانی اعلان (و اجازهٔ عقب‌بردن فقط در ویرایش) — با هر تغییر تاریخ/نوع.
function syncHoldBackward() {
var notice = $("#holdBackwardNotice");
var needed = holdIsBackward();
var canForce = needed && isEditMode;
// تیک فقط وقتی معنا دارد که عقب‌بردن مجاز باشد؛ در غیر این صورت پاک می‌شود.
$("#hbForceRow").toggleClass("is-hidden", !canForce);
$("#hbCreateHint").toggleClass("is-hidden", !needed || canForce);
if (!canForce) {
$("#forceHoldCheck").prop('checked', false);
$("#forceHold").val('');
}
if (!needed) {
notice.addClass("is-hidden").removeClass("is-blocking");
return;
}
$("#hbCurrent").text(jalaliDay(holdCurrentMs()));
notice.removeClass("is-hidden");
}
// تیک «عقب ببر» ⇒ فیلد مخفی force_hold برای سرور.
$("#forceHoldCheck").on('change', function() {
var checked = $(this).prop('checked');
$("#forceHold").val(checked ? '1' : '');
$("#holdBackwardNotice").toggleClass("is-blocking", !checked);
});
// هر تغییر تاریخ/نوع اقدام ⇒ وضعیت دوباره محاسبه می‌شود.
$("#ActionBox").on('change input', "#visitHoldUntil, #actionType",
function() { syncHoldBackward(); });
// پیکر تاریخ هم بعد از انتخاب، فیلد مخفی را پر می‌کند.
$(document).on('change', ".dateTimePickerHoldUntil, .dateTimePickerHoldUntil-alt",
function() { syncHoldBackward(); });
// گرفتن تأیید پیش از ثبتِ هماهنگیِ «انجام‌شده» (فقط در حالت ایجاد، نه ویرایش).
$("#ActionBox").on('submit', function(event) {
// (۱) در ویرایش: تاریخ عقب‌تر از قرار فعلی ⇒ تا تیک تأیید نزده، ارسال نمی‌شود.
// در «ایجاد»، وظیفه ذخیره می‌شود و فقط تاریخ قرار عقب نمی‌رود؛ سرور
// همین را با پیام روشن به کاربر می‌گوید، پس ارسال بلاک نمی‌شود.
if (isEditMode && holdIsBackward() && !$("#forceHoldCheck").prop('checked')) {
event.preventDefault();
var notice = $("#holdBackwardNotice").removeClass("is-hidden").addClass("is-blocking");
if (notice[0].scrollIntoView) {
notice[0].scrollIntoView({ block: 'center', behavior: 'smooth' });
}
notice.trigger('focus');
return;
}
// (۲) هماهنگیِ «انجام‌شده»: تأیید یک‌بارهٔ انجام کار.
if (isEditMode || holdConfirmed) {
return; // ویرایش، یا تأیید قبلی ⇒ ارسال آزاد
}
if ($("#actionType").val() !== 'hamahangi') {
return; // سایر انواع اقدام دست‌نخورده
}
if (!$("#checkIsDone").prop("checked")) {
return; // هماهنگیِ انجام‌نشده ⇒ بدون تعویق
}
event.preventDefault();
showHoldConfirm();
});
$(".actionBoxOpenB").click(function(event){
// کلیک روی کنترل‌های «حذف» نباید مودال ویرایش را باز کند.
// (دکمه داخل کارت است و رویداد به والد بالا می‌رفت.)
if ($(event.target).closest('.ax-del-form, .ax-del').length) {
return;
}
isEditMode = true;
$("#sabtBtn05").val("ویرایش");
var actionEditUrl = "{% url 'admin_panel:customerEditAction' 1111 2222 %}";
var row = $(this).parent();
var action_id = row.attr("action_id");
var customer_id = row.attr("customer_id");
var customerHold = row.attr("customer_hold") || "";
actionEditUrl = actionEditUrl.replace('1111', customer_id);
actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#ActionBox").prop("action", actionEditUrl);
$.getJSON('/panel/ActionJson/' + action_id, function(data) {
ActionParseData = JSON.parse(data);
console.log(ActionParseData);
var actionDateTime0 = new Date(ActionParseData[0]['fields']['actionDateTime']);
var finishDateTime = null;
if(ActionParseData[0]['fields']['finishDateTime'] != null){
finishDateTime = Math.floor( new Date(ActionParseData[0]['fields']['finishDateTime']).getTime() );
}
actionDateTime0 = Math.floor(actionDateTime0.getTime());
dateTimePickerAnjam.setDate(actionDateTime0);
if(finishDateTime != null){
dateTimePickerFinish.setDate(finishDateTime);
}
$("#ActionBox").addClass("is-active");
$("#actionType").val(ActionParseData[0]['fields']['actionType']);
// در ویرایش، تیکِ خودکار و تأیید نباید درگیر شوند.
holdAutoChecked = false;
holdConfirmed = false;
// فیلد «تا تاریخ پیگیری نشود» با نوع اقدام هم‌راستا شود (در حالت ویرایش هم).
closeHoldHelp();
toggleCoordinationField(ActionParseData[0]['fields']['actionType']);
// مقدار ذخیره‌شدهٔ «قرار بعدی» را در همان فیلد نشان بده (بعد از toggle،
// چون toggle برای انواع غیرهماهنگی فیلد را پاک می‌کند).
seedHoldPicker(customerHold);
syncHoldBackward();
$("#desc").val(ActionParseData[0]['fields']['desc']);
$("#userID").val(ActionParseData[0]['fields']['forUser']);
if(ActionParseData[0]['fields']['status'] == 'finish'){
$("#checkIsDone").prop('checked', true);
$(".showWhenDone").removeClass("is-hidden");
} else {
$("#checkIsDone").prop('checked', false);
$(".showWhenDone").addClass("is-hidden");
}
$("#actionResult").val(ActionParseData[0]['fields']['result']);
// هشدار «هماهنگی انجام شد» در ویرایش هم صرفاً نمایش داده می‌شود (بدون تأیید).
syncHoldWarning();
});
});
var dateTimePickerFinish = $(".dateTimePickerFinish").pDatepicker({
altField: '.dateTimePickerFinish-alt',
initialValue: true,
initialValueType: 'persian',
format: 'YYYY/MM/DD HH:mm',
calendar:{
persian: {
locale: 'fa'
}
},
timePicker: {
enabled: true,
second:{
enabled: false,
}
},
});
var dateTimePickerHoldUntil = $(".dateTimePickerHoldUntil").pDatepicker({
altField: '.dateTimePickerHoldUntil-alt',
initialValue: false,
format: 'YYYY/MM/DD',
calendar:{
persian: {
locale: 'fa'
}
},
timePicker: {
enabled: false,
second:{
enabled: false,
}
},
// بعد از انتخاب تاریخ، وضعیت «عقب‌تر بودن» فوراً بازمحاسبه شود
// (پیکر مقدار altField را برنامه‌نویسی‌شده می‌نویسد و ممکن است change رخ ندهد).
onSelect: function () {
setTimeout(function () { syncHoldBackward(); }, 0);
},
});
// اگر پیکر ساخته نشد (مثلاً نام کلاس در قالب عوض شده باشد) هشدار بده؛
// این کار جلوی خطاهای خاموش مثل «تقویم باز نمی‌شود» را می‌گیرد.
if (!dateTimePickerHoldUntil) {
console.warn('[ActionBox] pDatepicker برای «تا تاریخ پیگیری نشود» ساخته نشد؛ ' +
'کلاس .dateTimePickerHoldUntil را در قالب بررسی کنید.');
}
// فیلد «تا تاریخ پیگیری نشود» فقط برای اقدام «هماهنگی» نمایش داده می‌شود.
function toggleCoordinationField(actionType) {
var isCoordination = (actionType === 'hamahangi');
$(".showWhenCoordination").toggleClass("is-hidden", !isCoordination);
if (!isCoordination) {
resetHoldPicker();
closeHoldHelp(); // راهنما هم بسته شود تا در اقدام بعدی باز نماند
}
}
// ---- راهنمای بازشوندهٔ «هماهنگی» ----
function closeHoldHelp() {
$("#holdHelpPanel").addClass("is-hidden");
$("#holdHelpToggle").attr("aria-expanded", "false");
}
$("#holdHelpToggle").click(function(){
var expanded = $(this).attr("aria-expanded") === "true";
$("#holdHelpPanel").toggleClass("is-hidden", expanded);
$(this).attr("aria-expanded", expanded ? "false" : "true");
});
$("select#actionType").on('change', function(){
var type = $(this).val();
toggleCoordinationField(type);
if (type === 'hamahangi') {
// پیش‌فرض: «هماهنگی» یعنی همین حالا تماس گرفته شد ⇒ تیک «انجام شده».
// هشدار و تأیید، جلوی ثبتِ ناخواسته را می‌گیرد.
if (!$("#checkIsDone").prop("checked")) {
$("#checkIsDone").prop("checked", true);
holdAutoChecked = true;
}
} else if (holdAutoChecked) {
// تیک را خودمان زده بودیم ⇒ با تغییر نوع برمی‌داریم (به‌ویژه برای «بازدید»).
$("#checkIsDone").prop("checked", false);
holdAutoChecked = false;
}
holdConfirmed = false;
syncDoneFields();
syncHoldWarning();
});
var dateTimePickerAnjam = $(".dateTimePickerAnjam").pDatepicker({
altField: '.dateTimePickerAnjam-alt',
initialValue: true,
initialValueType: 'persian',
format: 'YYYY/MM/DD HH:mm',
calendar:{
persian: {
locale: 'fa'
}
},
timePicker: {
enabled: true,
second:{
enabled: false,
}
},
});
</script>