Files
fabric_trade_admin/src/views/checks/CheckManagementView.vue
T

289 lines
9.4 KiB
Vue
Raw Normal View History

<script setup lang="ts">
2026-08-06 17:12:28 +03:30
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'
2026-08-06 17:12:28 +03:30
import { checksApi, mapAdminOrder, ordersApi, type ApiOrder } from '@/services/api'
import { ApiError } from '@/services/http'
import { formatCurrency } from '@/utils/format'
2026-08-06 17:12:28 +03:30
import { useToast } from '@/composables/useToast'
import type { Check } from '@/types'
const route = useRoute()
const router = useRouter()
2026-08-06 17:12:28 +03:30
const toast = useToast()
const activeTab = ref<'info' | 'images' | 'history'>('info')
2026-08-06 17:12:28 +03:30
const order = ref<ApiOrder | null>(null)
const orderChecks = ref<Check[]>([])
const busyId = ref<string | null>(null)
2026-08-06 17:12:28 +03:30
const displayOrder = computed(() =>
order.value ? mapAdminOrder(order.value) : null,
)
const totalCheckAmount = computed(() =>
orderChecks.value.reduce((sum, c) => sum + c.amount, 0),
)
2026-08-06 17:12:28 +03:30
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')
}
2026-08-06 17:12:28 +03:30
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']) {
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')
for (const check of pending) {
await updateCheckStatus(check, 'approved')
}
}
</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>
2026-08-06 17:12:28 +03:30
<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" />
2026-08-06 17:12:28 +03:30
{{ order?.checkRassDays ?? '—' }} روز
</p>
<p v-if="order?.checkRassDate" class="checks-page__rass-date">
تاریخ راس: {{ order.checkRassDate.slice(0, 10) }}
</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">
2026-08-06 17:12:28 +03:30
{{ 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>
2026-08-06 17:12:28 +03:30
<td>{{ check.dueDate.slice(0, 10) }}</td>
<td><StatusBadge :status="checkBadgeStatus(check.status)" /></td>
<td>
2026-08-06 17:12:28 +03:30
<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>
2026-08-06 17:12:28 +03:30
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</div>
2026-08-06 17:12:28 +03:30
<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>
2026-08-06 17:12:28 +03:30
<StatusBadge :status="checkBadgeStatus(check.status)" />
</div>
<div class="admin-card-item__meta">
<span>بانک {{ check.bankName }}</span>
2026-08-06 17:12:28 +03:30
<span>سررسید {{ check.dueDate.slice(0, 10) }}</span>
<span dir="ltr">{{ check.sayadId }}</span>
</div>
2026-08-06 17:12:28 +03:30
<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>
2026-08-06 17:12:28 +03:30
<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">
2026-08-06 17:12:28 +03:30
<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;
2026-08-06 17:12:28 +03:30
margin-top: 16px;
flex-wrap: wrap;
}
.checks-page__note {
margin: 0;
font-size: 0.8125rem;
color: var(--color-text-muted);
}
</style>