improve bazdid

This commit is contained in:
javad committed 2026-10-21 10:50:42 +03:30
1 parent 2aee85593f
commit 534a84f977
12 files changed
+2305 -339

No files matched your search

+334 -156
View File
@@ -2,109 +2,379 @@
{% load custom_tags %}
{% block bodyPage %}
<div class="">
<div class="columns m-0">
<style>
/* ===================================================================
صفحهٔ انتظار بازدید — mobile first
مقیاس فاصله: ۰٫۲۵rem (۴px) — همهٔ فاصله‌ها مضرب همین واحد است.
=================================================================== */
.ez-wrap { padding: .5rem; }
@media (min-width: 769px) { .ez-wrap { padding: 1rem 1.25rem; } }
<div class="column is-12">
<div class="buttons">
<a href="#" id="openSearchBox" class="button is-info is-outlined is-rounded mr-0">جستجو</a>
/* ---------- نوار آمار ---------- */
.ez-stats {
display: grid; grid-auto-flow: column; grid-auto-columns: minmax(8.5rem, 1fr);
gap: .5rem; overflow-x: auto; padding-bottom: .25rem;
-webkit-overflow-scrolling: touch;
}
@media (min-width: 1024px) {
.ez-stats { grid-auto-flow: row; grid-template-columns: repeat(5, 1fr); overflow: visible; }
}
.ez-stat {
display: block; border: 1px solid #e2e8f0; border-radius: .625rem;
background: #fff; padding: .55rem .7rem; text-decoration: none; color: #334155;
transition: border-color .15s ease, background-color .15s ease;
}
.ez-stat:hover { border-color: #94a3b8; }
.ez-stat .ez-num {
display: block; font-size: 1.2rem; font-weight: 800; line-height: 1.1;
font-variant-numeric: tabular-nums;
}
.ez-stat .ez-lbl { display: block; margin-top: .15rem; font-size: .7rem; color: #64748b; }
.ez-stat.is-active {
border-color: #33b5d3; background: #f1fbff; box-shadow: inset 0 -3px 0 #33b5d3;
}
.ez-stat.is-active .ez-lbl { color: #0e7490; font-weight: 700; }
/* ---------- تفکیک تأخیر ---------- */
.ez-legend {
display: flex; flex-wrap: wrap; gap: .25rem .9rem;
margin: .6rem 0 .5rem; font-size: .72rem; color: #64748b;
}
.ez-legend b { font-variant-numeric: tabular-nums; color: #334155; }
/* ---------- ابزارک‌ها ---------- */
.ez-toolbar {
display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0 0 .75rem;
}
.ez-toolbar .button { margin: 0 !important; }
.ez-seg {
display: inline-flex; border: 1px solid #e2e8f0; border-radius: .5rem; overflow: hidden;
}
.ez-seg a {
padding: .3rem .6rem; font-size: .72rem; color: #475569; text-decoration: none;
background: #fff; border-inline-end: 1px solid #e2e8f0; white-space: nowrap;
}
.ez-seg a:last-child { border-inline-end: 0; }
.ez-seg a:hover { background: #f1f5f9; }
.ez-seg a.is-active { background: #33b5d3; color: #fff; font-weight: 700; }
/* ---------- جدول ---------- */
.ez-table { width: 100%; margin: 0; font-size: .82rem; border-collapse: collapse; }
@media (min-width: 900px) { .ez-table { table-layout: fixed; } }
.ez-table thead th {
position: sticky; top: 0; z-index: 2; background: #33b5d3; color: #06323d;
font-size: .72rem; font-weight: 700; padding: .5rem .35rem; white-space: nowrap;
border: 0; border-bottom: 1px solid #22a5c1;
}
.ez-table tbody td {
padding: .45rem .3rem; vertical-align: middle; text-align: center;
line-height: 1.4; border: 0; border-bottom: 1px solid #eef2f6;
overflow-wrap: anywhere;
}
/* تاریخ‌ها و شماره‌ها هرگز به دو خط نشکنند */
.ez-table tbody td.ez-nowrap { white-space: nowrap; }
.ez-table tbody tr:hover td { background: #f8fbfd; }
/* شمارندهٔ ردیف: به‌جای یک ستون کامل، چیپ کنار نام */
.ez-idx {
display: inline-block; min-width: 1.55rem; margin-inline-end: .3rem;
padding: .05rem .25rem; border-radius: .3rem; background: #f1f5f9;
color: #64748b; font-size: .68rem; font-weight: 700;
font-variant-numeric: tabular-nums; text-align: center;
}
.ez-name { font-weight: 700; font-size: .84rem; color: #0f172a; }
.ez-name:hover { color: #0e7490; }
.ez-sub { font-size: .68rem; color: #94a3b8; margin-top: .1rem; }
/* اعداد و شماره‌ها */
.ez-num { font-variant-numeric: tabular-nums; }
.ez-phone {
direction: ltr; unicode-bidi: isolate;
font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
.ez-muted { color: #cbd5e1; }
.ez-badge {
display: inline-block; padding: .1rem .45rem; border-radius: 999px;
font-size: .68rem; font-weight: 700; white-space: nowrap; line-height: 1.6;
}
.ez-badge.danger { background: #fee2e2; color: #b91c1c; }
.ez-badge.success { background: #dcfce7; color: #15803d; }
.ez-badge.warning { background: #fef3c7; color: #b45309; }
.ez-badge.mixed { background: #e0f2fe; color: #0369a1; }
/* تأخیر به‌صورت چیپ (خوانا در یک نگاه) */
.ez-late {
display: inline-block; min-width: 4.4rem; padding: .1rem .4rem;
border-radius: .3rem; font-size: .72rem; font-weight: 700;
font-variant-numeric: tabular-nums;
}
.ez-late.lv0 { background: #f8fafc; color: #94a3b8; }
.ez-late.lv1 { background: #fffbeb; color: #b45309; }
.ez-late.lv2 { background: #fef2f2; color: #dc2626; }
.ez-late.lv3 { background: #fde8e8; color: #991b1b; }
/* آدرس: حداکثر دو خط (بدون شکستن چیدمان) */
.ez-addr {
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
overflow: hidden; text-align: right; font-size: .78rem; color: #475569;
}
/* ---------- حالت کارت (موبایل) ---------- */
.ez-only-desktop { display: none !important; }
@media (max-width: 899px) {
.ez-table thead { display: none; }
.ez-table, .ez-table tbody, .ez-table tr, .ez-table td { display: block; width: 100%; }
.ez-table tbody tr {
display: flex; flex-wrap: wrap; align-items: baseline;
background: #fff; border: 1px solid #e5e7eb; border-radius: .625rem;
margin-bottom: .5rem; padding: .55rem .7rem;
}
.ez-table tbody tr:hover td { background: transparent; }
.ez-table tbody td {
flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: baseline;
gap: .3rem; padding: .12rem 0; border: 0; text-align: right;
}
.ez-table tbody td::before {
content: attr(data-label); flex: 0 0 6.5rem;
font-size: .7rem; color: #94a3b8;
}
/* سرِ کارت: نام مشتری، بدون برچسب */
.ez-table tbody td.ez-card-head {
padding-bottom: .35rem; margin-bottom: .25rem;
border-bottom: 1px dashed #eef2f6;
}
.ez-table tbody td.ez-card-head::before,
.ez-table tbody td.ez-nolabel::before { content: none; }
.ez-actions { flex: 1 0 100%; display: flex; gap: .35rem; margin-top: .4rem; }
.ez-actions .button { margin: 0 !important; flex: 1 1 0; }
.ez-addr { -webkit-line-clamp: 3; }
.ez-legend { font-size: .7rem; }
}
@media (min-width: 900px) {
.ez-table thead { display: table-header-group; }
.ez-table tbody { display: table-row-group; }
.ez-table tr { display: table-row; }
.ez-table td { display: table-cell; }
.ez-table td::before { content: none; }
.ez-only-desktop { display: table-cell !important; }
.ez-only-mobile { display: none !important; }
.ez-actions { display: none; }
}
</style>
<div class="ez-wrap">
<div class="columns m-0">
<div class="column is-12 p-0">
{# ===== نوار آمار: هر عدد لینک فیلتر ===== #}
<div class="ez-stats">
<a href="?view=all&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="ez-stat {% if view == 'all' %}is-active{% endif %}">
<span class="ez-num">{{ counts.total }}</span>
<span class="ez-lbl">کل معوق</span>
</a>
<a href="?view=uncoordinated&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="ez-stat {% if view == 'uncoordinated' %}is-active{% endif %}">
<span class="ez-num has-text-danger">{{ counts.uncoordinated }}</span>
<span class="ez-lbl">بدون قرار</span>
</a>
<a href="?view=coordinated&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="ez-stat {% if view == 'coordinated' %}is-active{% endif %}">
<span class="ez-num has-text-success">{{ counts.coordinated }}</span>
<span class="ez-lbl">با قرار</span>
</a>
<a href="?view=manual&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="ez-stat {% if view == 'manual' %}is-active{% endif %}">
<span class="ez-num has-text-warning-dark">{{ counts.manual }}</span>
<span class="ez-lbl">قرار هماهنگی</span>
</a>
<a href="?view=scheduled&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="ez-stat {% if view == 'scheduled' %}is-active{% endif %}">
<span class="ez-num has-text-link">{{ counts.scheduled }}</span>
<span class="ez-lbl">بازدید برنامه‌ریزی‌شده</span>
</a>
</div>
{# تفکیک مدت تأخیر در نمای جاری #}
<div class="ez-legend">
<span>تأخیر:</span>
<span><b>{{ counts.buckets.lt30 }}</b> زیر ۳۰ روز</span>
<span><b>{{ counts.buckets.30to90 }}</b> ۳۰–۹۰ روز</span>
<span><b>{{ counts.buckets.90to180 }}</b> ۹۰–۱۸۰ روز</span>
<span><b>{{ counts.buckets.gt180 }}</b> بیش از ۶ ماه</span>
</div>
{# ===== ابزارک‌ها: جستجو و مرتب‌سازی ===== #}
<div class="ez-toolbar">
<a href="#" id="openSearchBox" class="button is-info is-outlined is-rounded is-small">
جستجو{% if searchVal %}: «{{ searchVal }}»{% endif %}
</a>
<span class="is-size-7 has-text-grey">مرتب‌سازی</span>
<span class="ez-seg">
<a href="?view={{ view }}&sort=oldest{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="{% if sort == 'oldest' %}is-active{% endif %}">قدیمی‌ترین</a>
<a href="?view={{ view }}&sort=overdue{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="{% if sort == 'overdue' %}is-active{% endif %}">بیشترین تأخیر</a>
<a href="?view={{ view }}&sort=newest{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}"
class="{% if sort == 'newest' %}is-active{% endif %}">جدیدترین</a>
</span>
{% if searchVal %}
<a href="?view={{ view }}&sort={{ sort }}" class="button is-small is-light">حذف جستجو</a>
{% endif %}
</div>
<div class="table-container">
<table class="table has-text-centered is-bordered is-striped is-narrow is-hoverable is-fullwidth">
<!-- Your table content -->
<table class="table ez-table">
<colgroup>
<col style="width: 23%">
<col style="width: 13%">
<col style="width: 10%">
<col style="width: 11%">
<col style="width: 10%">
<col style="width: 9%">
<col style="width: 11%">
<col style="width: 13%">
</colgroup>
<thead>
<tr style="
background: #33b5d3;
">
<th >ردیف</th>
<th style="min-width: 200px;">نام و نام خانودگی</th>
<th >آخرین بازدید</th>
<th >تلفن</th>
<th style="min-width: 400px;">آدرس</th>
<tr>
<th>نام و نام خانوادگی</th>
<th>وضعیت قرار</th>
<th>قرار هماهنگی</th>
<th>بازدید برنامه‌ریزی‌شده</th>
<th>آخرین بازدید</th>
<th>تأخیر</th>
<th>تلفن</th>
<th>آدرس</th>
</tr>
</thead>
<tbody>
{% for customer in customers %}
<tr>
<td>{{ forloop.counter }}</td>
<td>
{# سرِ ردیف: چیپ شماره + نام (شمارهٔ ردیف دیگر ستون جداگانه ندارد) #}
<td data-label="نام" class="ez-card-head" style="text-align: right;">
<input class="is-hidden fid fidv-20" value="20">
<a target="_blank" href="{% url 'admin_panel:customerAction' customer.id %}" class="tdvl name">
{% if customer.name_family|length >= 2 %} {{ customer.name_family }} {% else %} نامعلوم {% endif %}
<span class="ez-idx">{{ forloop.counter0|add:customers.start_index }}</span>
<a target="_blank" href="{% url 'admin_panel:customerAction' customer.id %}" class="tdvl name ez-name">
{% if customer.name_family|length >= 2 %}{{ customer.name_family }}{% else %}نامعلوم{% endif %}
</a>
{% if customer.idCode %}<div class="ez-sub">کد: {{ customer.idCode }}</div>{% endif %}
</td>
<td>
<div class="tdvl lastView">
{{ customer.last_bazdid_dt | toJalali }}
</div>
{% with appt=appointment_rows|row_for:customer.id %}
<td data-label="وضعیت" class="ez-nolabel">
{% if appt.manual and appt.scheduled or appt.manual and appt.coordination %}
<span class="ez-badge mixed">قرار + بازدید</span>
{% elif appt.manual %}
<span class="ez-badge warning">قرار هماهنگی</span>
{% elif appt.scheduled or appt.coordination %}
<span class="ez-badge success">بازدید برنامه‌ریزی‌شده</span>
{% else %}
<span class="ez-badge danger">بدون قرار</span>
{% endif %}
</td>
<td data-label="قرار هماهنگی" class="ez-num ez-nowrap">
{% if appt.manual %}{{ appt.manual|toJalali }}{% else %}<span class="ez-muted">—</span>{% endif %}
</td>
<td data-label="بازدید برنامه" class="ez-num ez-nowrap">
{% if appt.scheduled %}{{ appt.scheduled|toJalali }}
{% elif appt.coordination %}{{ appt.coordination|toJalali }}<div class="ez-sub">در انتظار تماس</div>
{% else %}<span class="ez-muted">—</span>{% endif %}
</td>
<td data-label="آخرین بازدید" class="ez-num ez-nowrap lastView">
{% if appt.last_visit %}{{ appt.last_visit|toJalali }}{% else %}<span class="ez-muted">بدون بازدید</span>{% endif %}
</td>
<td data-label="تأخیر" class="ez-nowrap">
{% if appt.overdue_days >= 180 %}
<span class="ez-late lv3">{{ appt.overdue_days }} روز</span>
{% elif appt.overdue_days >= 90 %}
<span class="ez-late lv2">{{ appt.overdue_days }} روز</span>
{% elif appt.overdue_days >= 30 %}
<span class="ez-late lv1">{{ appt.overdue_days }} روز</span>
{% else %}
<span class="ez-late lv0">{{ appt.overdue_days }} روز</span>
{% endif %}
</td>
{% endwith %}
<td>
<div class="tdvl mobile_1">
{{ customer.phone1 }}
</div>
<div class="tdvl is-hidden mobile_2">
09384999655
</div>
<div class="tdvl is-hidden mobile_3">
{{ customer.phone2 }}
</div>
</td>
<td>
<div class="tdvl address">
{{ customer.address }}
<td data-label="تلفن" class="ez-nowrap">
<span class="tdvl mobile_1 ez-phone">
{% if customer.phone1 %}{{ customer.phone1 }}{% else %}<span class="ez-muted">—</span>{% endif %}
</span>
<span class="tdvl is-hidden mobile_2">{{ customer.phone2 }}</span>
<span class="tdvl is-hidden mobile_3">{{ customer.phone3 }}</span>
{# دکمه‌های اقدام سریع — فقط موبایل #}
<div class="ez-actions">
{% if customer.phone1 %}
<a href="tel:{{ customer.phone1 }}" class="button is-success is-small">تماس</a>
{% endif %}
<a target="_blank" href="{% url 'admin_panel:customerAction' customer.id %}" class="button is-info is-small">ثبت اقدام</a>
</div>
</td>
</tr>
<td data-label="آدرس">
<div class="tdvl address ez-addr" title="{{ customer.address }}">{{ customer.address }}</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="has-text-centered py-5">
<span class="has-text-grey">موردی در این نما پیدا نشد.</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="">
{% if customers.has_previous %}
<a class="button is-danger" href="?{% if searchVal %}searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}&page={{ customers.previous_page_number }}">صفحه قبل</a>
{% endif %}
{% if customers.has_next %}
<a class="button is-danger" href="?{% if searchVal %}searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}&page={{ customers.next_page_number }}">صفحه بعد</a>
{# ===== صفحه‌بندی ===== #}
<div class="is-flex is-align-items-center is-justify-content-space-between mt-3 mb-5">
{% if customers.has_previous %}
<a class="button is-danger is-small" href="?view={{ view }}&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}&page={{ customers.previous_page_number }}">صفحه قبل</a>
{% else %}<span></span>{% endif %}
{% endif %}
<span class="is-size-7 has-text-grey">
صفحهٔ {{ customers.number }} از {{ customers.paginator.num_pages }} ·
{{ customers.paginator.count }} مورد
</span>
</div>
{% if customers.has_next %}
<a class="button is-danger is-small" href="?view={{ view }}&sort={{ sort }}{% if searchVal %}&searchVal={{ searchVal }}&searchOn={{ searchOn }}{% endif %}&page={{ customers.next_page_number }}">صفحه بعد</a>
{% else %}<span></span>{% endif %}
</div>
</div>
</div>
</div>
<div class="modal SearchBox">
<div class="modal-background closeSearch"></div>
<div class="modal-content">
<!-- -->
<form method="get" class="columns is-mobile is-multiline">
{# حفظ نما و ترتیب هنگام جست‌وجو #}
<input type="hidden" name="view" value="{{ view }}">
<input type="hidden" name="sort" value="{{ sort }}">
<div class="field column is-7-desktop is-12-mobile m-0">
<label class="label">جستجو کنید</label>
<div class="control">
<input name="searchVal" class="input search" type="text" placeholder="">
<input name="searchVal" class="input search" type="text" value="{{ searchVal }}" placeholder="نام، تلفن، کد یا آدرس">
</div>
</div>
<div class="field column is-5-desktop is-12-mobile m-0">
<label class="label">فیلد</label>
<div class="select">
<div class="select is-fullwidth">
<select name="searchOn">
<!--
<option value="name">نام و نام خانوادگی</option>
<option value="code">کدمشتری</option>
<option value="phone">تلفن</option> !-->
<option value="address">آدرس</option>
<option value="name" {% if searchOn == 'name' %}selected{% endif %}>نام و نام خانوادگی</option>
<option value="phone" {% if searchOn == 'phone' %}selected{% endif %}>تلفن</option>
<option value="address" {% if searchOn == 'address' %}selected{% endif %}>آدرس</option>
<option value="code" {% if searchOn == 'code' %}selected{% endif %}>کد مشتری</option>
</select>
</div>
</div>
<div class="field column is-12-desktop is-12-mobile m-0">
<input class="button is-success" id="searchbtn" value="جستجو مشتری" type="submit">
<input class="button is-success is-fullwidth" id="searchbtn" value="جستجو مشتری" type="submit">
</div>
<!-- -->
</form>
@@ -112,109 +382,17 @@
<button class="modal-close is-large closeSearch" aria-label="close"></button>
</div>
<div class="modal BazdidF">
<div class="modal-background closeBazdidF"></div>
<div class="modal-content">
<div class="columns is-mobile is-multiline">
<div class="field column is-12-desktop is-12-mobile m-0">
<label class="label">نام ونام خانوادگی</label>
<div class="control">
<input class="input rf_name" id="bz_name" type="text" placeholder="نام نام خانوادگی">
</div>
</div>
<div class="field column is-4-desktop is-12-mobile m-0">
<label class="label">تلفن همراه</label>
<div class="control">
<input class="input rf_mobile" id="phone-1" type="text" placeholder="">
</div>
</div>
<div class="field column is-4-desktop is-12-mobile m-0">
<label class="label">تاریخ</label>
<div class="control">
<input name="bazdidDateTime" id="persianDateP" class="input bz_DateTime" type="text" placeholder="">
</div>
</div>
<div class="field column is-4-desktop is-12-mobile m-0">
<label class="label">توسط</label>
<div class="select">
<select>
<option>کدام خدمات؟</option>
<option>خدمات 1</option>
<option>خدمات 2</option>
<option>خدمات 3</option>
</select>
</div>
</div>
<div class="field column is-4-desktop is-12-mobile m-0">
<label class="label">زمان مراجعه</label>
<div class="select">
<select>
<option>زمان را انتخاب کنید</option>
<option>صبح</option>
<option>ظهر</option>
<option>بعد از ظهر</option>
<option>غروب</option>
<option>شب</option>
</select>
</div>
</div>
<div class="field column is-8-desktop is-12-mobile m-0">
<label class="label">توضیحات</label>
<div class="control">
<textarea class="textarea" placeholder="توضیحات"></textarea> </div>
</div>
<div class="field column is-12-desktop is-12-mobile m-0">
<input class="button is-success" id="editCS" value="ایجاد بازدید" type="submit">
</div>
</div>
</div>
<button class="modal-close is-large closeBazdidF" aria-label="close"></button>
</div>
{% endblock %}
{% block jsEnd %}
<script>
$(document).ready(function() {
kamaDatepicker('persianDateP', { buttonsColor: "red", forceFarsiDigits: true });
$(".closeBazdidF").click(function(){
$(".BazdidF").removeClass("is-active")
$("#openSearchBox").click(function(e) {
e.preventDefault();
$(".SearchBox").addClass("is-active");
});
$("#openSearchBox").click(function(){
$(".SearchBox").addClass("is-active")
$(".closeSearch").click(function() {
$(".SearchBox").removeClass("is-active");
});
$(".closeSearch").click(function(){
$(".SearchBox").removeClass("is-active")
});
$(".openRow").click(function(){
var id = $(this).parent().parent().find('.fid').val().trim();
var name = $(this).parent().parent().find('.name').text().trim();
var mobile_1 = $(this).parent().parent().find('.mobile_1').text().trim();
var address = $(this).parent().parent().find('.address').text().trim();
//alert(id)
$(".BazdidF").find('#bz_name').val(name);
$(".BazdidF").find('#phone-1').val(mobile_1);
//$("#bedehiStatus").val('وضعیت')
$(".BazdidF").addClass("is-active")
});
} );
});
</script>
{% endblock %}
{% endblock %}