Files
fabric_trade_user/src/views/checkout/CheckListView.vue
T

559 lines
15 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 } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useCartStore } from '@/stores/cart'
import { useOrderStore } from '@/stores/order'
import { useAuthStore } from '@/stores/auth'
import BaseButton from '@/components/ui/BaseButton.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
import IconChevronBack from '@/components/icons/IconChevronBack.vue'
import { formatCurrency, formatDate, formatNumber, toPersianDigits } from '@/utils/format'
import { calculateCheckRass } from '@/utils/checkRass'
import { confirm } from '@/composables/useConfirm'
import type { CheckData } from '@/types'
const route = useRoute()
const router = useRouter()
const cartStore = useCartStore()
const orderStore = useOrderStore()
const authStore = useAuthStore()
const isCheckout = computed(() => route.name === 'check-list')
const checks = computed<CheckData[]>(() => {
if (orderStore.draftChecks.length > 0) return orderStore.draftChecks
if (!authStore.user) return []
return orderStore
.userOrders(authStore.user.id)
.filter((order) => order.settlementType === 'check' && order.checks?.length)
.flatMap((order) => order.checks ?? [])
})
const rass = computed(() => {
if (orderStore.draftChecks.length > 0) return orderStore.getDraftRass()
if (checks.value.length === 0) {
return calculateCheckRass([], new Date())
}
return calculateCheckRass(checks.value, new Date())
})
const invoiceAmount = computed(() => {
if (!authStore.user) return 0
const activeOrder = orderStore
.userOrders(authStore.user.id)
.find((order) => order.settlementType === 'check' && order.finalAmount)
if (activeOrder?.finalAmount) return activeOrder.finalAmount
if (isCheckout.value && rass.value.totalAmount > 0) return rass.value.totalAmount
return rass.value.totalAmount
})
const isAmountComplete = computed(
() => invoiceAmount.value > 0 && rass.value.totalAmount >= invoiceAmount.value,
)
const openMenuId = computed({
get: () => (route.query.menu as string) ?? '',
set: (id: string) => {
router.replace({ query: id ? { menu: id } : {} })
},
})
function goBack() {
router.push(isCheckout.value ? '/checkout/check-form' : '/profile')
}
function addAnother() {
router.push('/checkout/check-form')
}
async function removeCheck(id: string) {
openMenuId.value = ''
const ok = await confirm({
title: 'حذف چک',
message: 'آیا از حذف این چک مطمئن هستید؟',
confirmLabel: 'حذف',
cancelLabel: 'انصراف',
tone: 'danger',
})
if (!ok) return
orderStore.removeDraftCheck(id)
}
function toggleMenu(id: string) {
openMenuId.value = openMenuId.value === id ? '' : id
}
function submitOrder() {
if (!authStore.user || !cartStore.activeCart || checks.value.length === 0) return
const order = orderStore.submitOrder(
authStore.user.id,
cartStore.activeCart,
'check',
[...orderStore.draftChecks],
orderStore.draftCheckMode ?? 'brief',
)
cartStore.clearActiveCart()
router.replace(`/orders/${order.id}/success`)
}
</script>
<template>
<!-- Mobile -->
<div class="check-list-page mobile-only">
<header class="check-list-page__nav">
<button type="button" class="nav-btn" aria-label="بازگشت" @click="goBack">
<IconChevronBack :size="20" />
</button>
<h1 class="check-list-page__title">چک‌های ثبت شده</h1>
<button type="button" class="nav-btn nav-btn--ghost" aria-hidden="true" tabindex="-1">
<IconChevronBack :size="18" class="nav-btn__flip" />
</button>
</header>
<div v-if="checks.length === 0" class="check-list-page__empty page__scroll page__scroll--with-nav">
<p>چکی ثبت نشده است.</p>
<BaseButton v-if="isCheckout" @click="addAnother">ثبت چک جدید</BaseButton>
<BaseButton v-else variant="outline" @click="router.push('/')">ثبت سفارش جدید</BaseButton>
</div>
<template v-else>
<div class="check-list-page__scroll page__scroll">
<div class="summary-cards">
<div class="summary-card">
<span class="summary-card__label">مجموع مبلغ چک‌ها</span>
<strong class="summary-card__value">{{ formatNumber(rass.totalAmount) }}</strong>
<span class="summary-card__unit">تومان</span>
</div>
<div class="summary-card">
<span class="summary-card__label">مبلغ فاکتور</span>
<strong class="summary-card__value">{{ formatNumber(invoiceAmount) }}</strong>
<span class="summary-card__unit">تومان</span>
<p v-if="isAmountComplete" class="summary-card__status">
<span class="summary-card__status-icon">✓</span>
مبلغ کامل است
</p>
</div>
</div>
<div class="checks-list">
<article v-for="(check, index) in checks" :key="check.id" class="check-card">
<div class="check-card__menu-wrap">
<button
v-if="isCheckout && orderStore.draftChecks.length > 0"
type="button"
class="check-card__menu-btn"
aria-label="گزینه‌ها"
@click="toggleMenu(check.id)"
>
⋮
</button>
<div v-if="openMenuId === check.id" class="check-card__menu">
<button type="button" @click="removeCheck(check.id)">حذف</button>
</div>
</div>
<div class="check-card__body">
<h3 class="check-card__title">چک {{ toPersianDigits(String(index + 1)) }}</h3>
<p class="check-card__amount">{{ formatCurrency(check.amount) }}</p>
<p class="check-card__date">تاریخ: {{ formatDate(check.dueDate) }}</p>
</div>
<div class="check-card__thumb-wrap">
<img
v-if="check.frontImage"
:src="check.frontImage"
class="check-card__thumb"
alt="تصویر چک"
/>
<div v-else class="check-card__thumb check-card__thumb--empty">چک</div>
</div>
</article>
</div>
<div class="rass-panel">
<h3 class="rass-panel__title">رأس چک‌ها</h3>
<div class="rass-panel__days">{{ toPersianDigits(String(rass.rassDays)) }} روز</div>
<p v-if="rass.rassDate" class="rass-panel__date">
تاریخ رأس تقریبی: {{ formatDate(rass.rassDate) }}
</p>
</div>
</div>
<div v-if="isCheckout && orderStore.draftChecks.length > 0" class="check-list-page__footer">
<BaseButton block :disabled="checks.length === 0" @click="submitOrder">
ارسال برای بررسی
</BaseButton>
</div>
</template>
</div>
<!-- Desktop -->
<div v-if="checks.length > 0" class="desktop-checkout-shell check-list-desktop desktop-only">
<header class="desktop-page__head">
<h1 class="desktop-page__title">چک‌های ثبت شده</h1>
<p class="desktop-page__subtitle">خلاصه و فهرست چک‌های ثبت‌شده</p>
</header>
<div class="desktop-two-col">
<div class="desktop-two-col__main">
<div class="desktop-table-wrap">
<table class="desktop-table">
<thead>
<tr>
<th scope="col">تصویر</th>
<th scope="col">مبلغ (تومان)</th>
<th scope="col">تاریخ سررسید</th>
<th scope="col">شناسه</th>
<th v-if="isCheckout" scope="col">عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="(check, index) in checks" :key="check.id">
<td>
<img
v-if="check.frontImage"
:src="check.frontImage"
class="check-list-desktop__thumb"
alt="تصویر چک"
/>
<span v-else>—</span>
</td>
<td>{{ formatNumber(check.amount) }}</td>
<td>{{ formatDate(check.dueDate) }}</td>
<td>چک {{ toPersianDigits(String(index + 1)) }}</td>
<td v-if="isCheckout && orderStore.draftChecks.length > 0">
<button type="button" class="orders-table__action" @click="removeCheck(check.id)">
حذف
</button>
</td>
</tr>
</tbody>
</table>
</div>
<BaseButton
v-if="isCheckout"
variant="outline"
class="desktop-btn-compact check-list-desktop__add"
@click="addAnother"
>
ثبت چک دیگر
</BaseButton>
</div>
<aside class="desktop-summary-panel">
<h2 class="desktop-summary-panel__title">خلاصه مالی</h2>
<div class="desktop-summary-panel__row">
<span>مجموع چک‌ها</span>
<strong>{{ formatNumber(rass.totalAmount) }} تومان</strong>
</div>
<div class="desktop-summary-panel__row">
<span>مبلغ فاکتور</span>
<strong>{{ formatNumber(invoiceAmount) }} تومان</strong>
</div>
<div class="desktop-summary-panel__row">
<span>رأس چک‌ها</span>
<strong>{{ toPersianDigits(String(rass.rassDays)) }} روز</strong>
</div>
<div v-if="isCheckout && orderStore.draftChecks.length > 0" class="desktop-summary-panel__action">
<BaseButton block :disabled="checks.length === 0" @click="submitOrder">
ارسال برای بررسی
</BaseButton>
</div>
</aside>
</div>
</div>
<div v-else class="desktop-checkout-shell desktop-only">
<EmptyState message="چکی ثبت نشده است">
<BaseButton v-if="isCheckout" @click="addAnother">ثبت چک جدید</BaseButton>
</EmptyState>
</div>
</template>
<style scoped>
.check-list-desktop__thumb {
width: 48px;
height: 32px;
object-fit: cover;
border-radius: 4px;
}
.check-list-desktop__add {
margin-top: 16px;
}
.check-list-page {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
height: 100%;
padding: 12px var(--page-padding) 0;
background: #ffffff;
overflow: hidden;
}
.check-list-page__nav {
flex-shrink: 0;
display: grid;
grid-template-columns: 40px 1fr 40px;
align-items: center;
margin-bottom: 16px;
}
.check-list-page__title {
margin: 0;
font-size: 1.0625rem;
font-weight: 700;
text-align: center;
}
.nav-btn {
width: 40px;
height: 40px;
border: none;
background: none;
color: var(--color-text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.nav-btn--ghost {
visibility: hidden;
pointer-events: none;
}
.nav-btn__flip {
transform: scaleX(-1);
}
.check-list-page__scroll {
flex: 1;
min-height: 0;
padding-bottom: 12px;
}
.check-list-page__empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
color: var(--color-text-muted);
}
.summary-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 16px;
}
.summary-card {
padding: 14px 12px;
border-radius: 14px;
background: #f1f5f9;
text-align: center;
}
.summary-card__label {
display: block;
font-size: 0.75rem;
color: var(--color-text-muted);
margin-bottom: 8px;
}
.summary-card__value {
display: block;
font-size: 1rem;
font-weight: 700;
color: var(--color-text);
line-height: 1.2;
}
.summary-card__unit {
display: block;
margin-top: 2px;
font-size: 0.75rem;
color: var(--color-text-muted);
}
.summary-card__status {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin: 8px 0 0;
font-size: 0.6875rem;
font-weight: 600;
color: var(--color-success);
}
.summary-card__status-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--color-success-light);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.625rem;
}
.checks-list {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 16px;
}
.check-card {
display: grid;
grid-template-columns: 28px 1fr 72px;
gap: 10px;
align-items: center;
padding: 12px;
border-radius: 14px;
background: #f8fafc;
border: 1px solid #eef2f7;
}
.check-card__menu-wrap {
position: relative;
}
.check-card__menu-btn {
width: 28px;
height: 28px;
border: none;
background: none;
color: var(--color-text-muted);
font-size: 1.125rem;
line-height: 1;
cursor: pointer;
padding: 0;
}
.check-card__menu {
position: absolute;
top: 100%;
right: 0;
z-index: 10;
min-width: 88px;
padding: 4px;
border-radius: var(--radius-sm);
background: #fff;
border: 1px solid var(--color-border);
box-shadow: var(--shadow-md);
}
.check-card__menu button {
width: 100%;
border: none;
background: none;
padding: 8px 10px;
text-align: right;
font-size: 0.8125rem;
color: var(--color-danger);
cursor: pointer;
border-radius: 6px;
}
.check-card__menu button:hover {
background: var(--color-danger-light);
}
.check-card__body {
min-width: 0;
}
.check-card__title {
margin: 0 0 4px;
font-size: 0.875rem;
font-weight: 700;
color: var(--color-text);
}
.check-card__amount {
margin: 0 0 4px;
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-text);
}
.check-card__date {
margin: 0;
font-size: 0.75rem;
color: var(--color-text-muted);
}
.check-card__thumb-wrap {
flex-shrink: 0;
}
.check-card__thumb {
width: 72px;
height: 48px;
border-radius: 8px;
object-fit: cover;
border: 1px solid var(--color-border);
background: #fff;
}
.check-card__thumb--empty {
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6875rem;
color: var(--color-text-muted);
}
.rass-panel {
padding: 16px;
border-radius: 14px;
background: #ecfdf3;
border: 1px solid #bbf7d0;
margin-bottom: 12px;
text-align: center;
}
.rass-panel__title {
margin: 0 0 6px;
font-size: 0.875rem;
font-weight: 600;
color: #166534;
}
.rass-panel__days {
font-size: 1.75rem;
font-weight: 700;
color: #15803d;
line-height: 1.2;
}
.rass-panel__date {
margin: 6px 0 0;
font-size: 0.8125rem;
color: #166534;
}
.check-list-page__footer {
flex-shrink: 0;
padding: 12px 0 calc(12px + env(safe-area-inset-bottom, 0));
background: #fff;
border-top: 1px solid var(--color-border);
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.06);
margin: 0 calc(-1 * var(--page-padding));
padding-left: var(--page-padding);
padding-right: var(--page-padding);
}
</style>