Files
fabric_trade_admin/src/views/checks/CheckManagementView.vue
T
safavimohadeseh78andCursor 1c3c494a43 Improve order detail UX, confirm check actions, and fix notification deep links.
Normalize inbox /orders/:id links, open related admin pages on click, and keep Jalali dates consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 14:32:09 +02:00

337 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { checksApi, mapAdminOrder, ordersApi, type ApiOrder } from '@/services/api'
import { ApiError } from '@/services/http'
import { formatCurrency, formatDate } from '@/utils/format'
import { useToast } from '@/composables/useToast'
import { confirm } from '@/composables/useConfirm'
import type { Check } from '@/types'
const route = useRoute()
const router = useRouter()
const toast = useToast()
const activeTab = ref<'info' | 'images' | 'history'>('info')
const order = ref<ApiOrder | null>(null)
const orderChecks = ref<Check[]>([])
const busyId = ref<string | null>(null)
const displayOrder = computed(() =>
order.value ? mapAdminOrder(order.value) : null,
)
const totalCheckAmount = computed(() =>
orderChecks.value.reduce((sum, c) => sum + c.amount, 0),
)
async function loadData() {
const orderId = String(route.params.id)
try {
const [orderData, checksData] = await Promise.all([
ordersApi.get(orderId),
checksApi.listByOrder(orderId),
])
order.value = orderData
orderChecks.value = checksData
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'بارگذاری چک‌ها انجام نشد.')
}
}
onMounted(() => {
void loadData()
})
function goBack() {
router.push('/orders')
}
function checkBadgeStatus(status: Check['status']) {
if (status === 'approved') return 'final_approval'
if (status === 'rejected') return 'needs_correction'
return 'pending_check_review'
}
async function updateCheckStatus(check: Check, status: Check['status']) {
if (status !== 'approved' && status !== 'rejected') return
const amountLabel = formatCurrency(check.amount)
const ok = await confirm(
status === 'approved'
? {
title: 'تأیید چک',
message: `آیا از تأیید چک ردیف ${check.row} به مبلغ ${amountLabel} مطمئن هستید؟`,
tone: 'primary',
confirmLabel: 'تأیید چک',
cancelLabel: 'انصراف',
}
: {
title: 'رد چک',
message: `آیا از رد چک ردیف ${check.row} به مبلغ ${amountLabel} مطمئن هستید؟`,
tone: 'danger',
confirmLabel: 'رد چک',
cancelLabel: 'انصراف',
},
)
if (!ok) return
busyId.value = check.id
try {
const updated = await checksApi.updateStatus(check.id, { status })
const index = orderChecks.value.findIndex((c) => c.id === check.id)
if (index !== -1) orderChecks.value[index] = updated
toast.success(status === 'approved' ? 'چک تأیید شد.' : 'چک رد شد.')
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'به‌روزرسانی وضعیت چک انجام نشد.')
} finally {
busyId.value = null
}
}
async function approveAll() {
const pending = orderChecks.value.filter((c) => c.status === 'pending_review')
if (!pending.length) {
toast.error('چکی برای تأیید وجود ندارد.')
return
}
const ok = await confirm({
title: 'تأیید همه چک‌ها',
message: `آیا از تأیید ${pending.length} چک در انتظار بررسی مطمئن هستید؟`,
tone: 'primary',
confirmLabel: 'تأیید همه',
cancelLabel: 'انصراف',
})
if (!ok) return
for (const check of pending) {
busyId.value = check.id
try {
const updated = await checksApi.updateStatus(check.id, { status: 'approved' })
const index = orderChecks.value.findIndex((c) => c.id === check.id)
if (index !== -1) orderChecks.value[index] = updated
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'به‌روزرسانی وضعیت چک انجام نشد.')
busyId.value = null
return
}
}
busyId.value = null
toast.success('همه چک‌های در انتظار تأیید شدند.')
}
</script>
<template>
<div class="checks-page">
<div class="admin-page-header">
<div>
<h1 class="admin-page-header__title">مدیریت چک‌ها</h1>
<p class="admin-page-header__breadcrumb">سفارش‌ها / مدیریت سفارش</p>
</div>
<div v-if="displayOrder" class="checks-page__order-meta">
<strong>{{ displayOrder.orderNumber }}</strong>
<StatusBadge :status="displayOrder.status" />
</div>
</div>
<div class="admin-tabs">
<button
type="button"
class="admin-tab"
:class="{ active: activeTab === 'info' }"
@click="activeTab = 'info'"
>
اطلاعات چک‌ها ({{ orderChecks.length }})
</button>
<button
type="button"
class="admin-tab"
:class="{ active: activeTab === 'images' }"
@click="activeTab = 'images'"
>
تصاویر چک‌ها
</button>
<button
type="button"
class="admin-tab"
:class="{ active: activeTab === 'history' }"
@click="activeTab = 'history'"
>
تاریخچه بررسی
</button>
</div>
<template v-if="activeTab === 'info'">
<div class="admin-summary-grid">
<div class="admin-summary-item">
<p class="admin-summary-item__label">تعداد چک‌ها</p>
<p class="admin-summary-item__value">{{ orderChecks.length }} فقره</p>
</div>
<div class="admin-summary-item">
<p class="admin-summary-item__label">راس چک‌ها</p>
<p class="admin-summary-item__value">
<AdminIcon name="check" :size="14" />
{{ order?.checkRassDays ?? '—' }} روز
</p>
<p v-if="order?.checkRassDate" class="checks-page__rass-date">
تاریخ راس: {{ formatDate(order.checkRassDate) }}
</p>
</div>
<div class="admin-summary-item">
<p class="admin-summary-item__label">مجموع چک‌ها</p>
<p class="admin-summary-item__value">{{ formatCurrency(totalCheckAmount) }}</p>
</div>
<div class="admin-summary-item">
<p class="admin-summary-item__label">مبلغ کل سفارش</p>
<p class="admin-summary-item__value">
{{ displayOrder?.totalAmount ? formatCurrency(displayOrder.totalAmount) : '—' }}
</p>
</div>
</div>
<div class="checks-page__table admin-card desktop-only">
<div class="admin-table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>ردیف</th>
<th>مبلغ (تومان)</th>
<th>بانک</th>
<th>شناسه صیادی</th>
<th>تاریخ سررسید</th>
<th>وضعیت</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="check in orderChecks" :key="check.id">
<td>{{ check.row }}</td>
<td>{{ formatCurrency(check.amount) }}</td>
<td>{{ check.bankName }}</td>
<td>{{ check.sayadId }}</td>
<td>{{ formatDate(check.dueDate) }}</td>
<td><StatusBadge :status="checkBadgeStatus(check.status)" /></td>
<td>
<div v-if="check.status === 'pending_review'" class="admin-action-group">
<button
type="button"
class="admin-btn admin-btn--outline-success admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'approved')"
>
تأیید
</button>
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</div>
<span v-else>—</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="compact-only admin-card-list">
<article v-for="check in orderChecks" :key="check.id" class="admin-card-item">
<div class="admin-card-item__header">
<p class="admin-card-item__title">چک {{ check.row }} — {{ formatCurrency(check.amount) }}</p>
<StatusBadge :status="checkBadgeStatus(check.status)" />
</div>
<div class="admin-card-item__meta">
<span>بانک {{ check.bankName }}</span>
<span>سررسید {{ formatDate(check.dueDate) }}</span>
<span dir="ltr">{{ check.sayadId }}</span>
</div>
<div v-if="check.status === 'pending_review'" class="admin-card-item__actions">
<button
type="button"
class="admin-btn admin-btn--outline-success admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'approved')"
>
تأیید
</button>
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</div>
</article>
</div>
<div class="checks-page__footer">
<p class="checks-page__note">
راس چک‌ها بر اساس تاریخ ثبت سفارش محاسبه شده است.
</p>
<div class="admin-action-group">
<button type="button" class="admin-btn admin-btn--success" @click="approveAll">
تأیید همه چک‌ها
</button>
<button type="button" class="admin-btn admin-btn--outline">
<AdminIcon name="print" :size="16" />
چاپ لیست‌ها
</button>
<button type="button" class="admin-btn admin-btn--outline admin-btn--sm" @click="goBack">
بازگشت
</button>
</div>
</div>
</template>
<div v-else class="admin-empty-placeholder admin-card">
{{ activeTab === 'images' ? 'تصاویر چک‌ها' : 'تاریخچه بررسی' }} — به‌زودی
</div>
</div>
</template>
<style scoped>
.checks-page__order-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
font-size: 0.9375rem;
}
.checks-page__rass-date {
margin: 4px 0 0;
font-size: 0.75rem;
color: var(--color-text-muted);
font-weight: 400;
}
.checks-page__table {
margin-top: 16px;
overflow: hidden;
}
.checks-page__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-top: 16px;
flex-wrap: wrap;
}
.checks-page__note {
margin: 0;
font-size: 0.8125rem;
color: var(--color-text-muted);
}
</style>