Move settlement after invoice and wire real notification inbox.
Submit orders without choosing payment first, route cash/check flows under /orders/:id, show status-based next actions, and connect the header bell to unread notifications. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCartPage } from '@/composables/useCartPage'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import MobileCartPage from '@/views/cart/components/MobileCartPage.vue'
|
||||
import DesktopCartPage from '@/views/cart/components/DesktopCartPage.vue'
|
||||
|
||||
@@ -13,6 +14,8 @@ const {
|
||||
isEmpty,
|
||||
companyName,
|
||||
showFullDialog,
|
||||
submitting,
|
||||
submitError,
|
||||
removeItem,
|
||||
proceedToCheckout,
|
||||
proceedAnyway,
|
||||
@@ -31,6 +34,8 @@ function goToOrdering() {
|
||||
|
||||
<template>
|
||||
<div class="cart-view">
|
||||
<FormErrorBanner v-if="submitError" :message="submitError" class="cart-view__error" />
|
||||
|
||||
<MobileCartPage
|
||||
class="mobile-only"
|
||||
:items="items"
|
||||
@@ -39,6 +44,7 @@ function goToOrdering() {
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
:submitting="submitting"
|
||||
@back="router.push('/')"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@@ -53,6 +59,7 @@ function goToOrdering() {
|
||||
:max-meterage="cartStore.maxMeterage"
|
||||
:remaining-capacity="cartStore.remainingCapacity"
|
||||
:is-empty="isEmpty"
|
||||
:submitting="submitting"
|
||||
@remove="removeItem"
|
||||
@checkout="proceedToCheckout"
|
||||
@select-company="router.push('/')"
|
||||
@@ -66,7 +73,9 @@ function goToOrdering() {
|
||||
سقف متراژ این فاکتور تکمیل شده است. آیا مایلید ادامه محصولات را در فاکتور جدید ثبت کنید؟
|
||||
</p>
|
||||
<div class="dialog-actions">
|
||||
<BaseButton variant="outline" @click="proceedAnyway">تسویه همین فاکتور</BaseButton>
|
||||
<BaseButton variant="outline" :loading="submitting" @click="proceedAnyway">
|
||||
ثبت همین فاکتور
|
||||
</BaseButton>
|
||||
<BaseButton @click="createNewInvoice">فاکتور جدید</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,6 +92,10 @@ function goToOrdering() {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cart-view__error {
|
||||
margin: 0 var(--page-padding);
|
||||
}
|
||||
|
||||
.dialog h3 {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CheckoutStepper from '@/components/layout/CheckoutStepper.vue'
|
||||
import IconCart from '@/components/icons/IconCart.vue'
|
||||
@@ -11,16 +10,17 @@ import IconClipboardEmpty from '@/components/icons/IconClipboardEmpty.vue'
|
||||
import IconLightbulb from '@/components/icons/IconLightbulb.vue'
|
||||
import IconLayoutGrid from '@/components/icons/IconLayoutGrid.vue'
|
||||
import IconDiamond from '@/components/icons/IconDiamond.vue'
|
||||
import { formatMeterage, formatNumber, formatCurrency } from '@/utils/format'
|
||||
import { formatMeterage, formatNumber } from '@/utils/format'
|
||||
import type { CartItem } from '@/types'
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
items: CartItem[]
|
||||
companyName: string
|
||||
totalMeterage: number
|
||||
maxMeterage: number
|
||||
remainingCapacity: number
|
||||
isEmpty: boolean
|
||||
submitting?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -32,10 +32,6 @@ const emit = defineEmits<{
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const payableAmount = computed(() =>
|
||||
props.items.reduce((sum, item) => sum + (item.unitPrice ?? 0) * item.meterage, 0),
|
||||
)
|
||||
|
||||
const quickLinks = [
|
||||
{
|
||||
title: 'انتخاب شرکت',
|
||||
@@ -160,25 +156,28 @@ const quickLinks = [
|
||||
</span>
|
||||
<strong>{{ formatMeterage(totalMeterage) }}</strong>
|
||||
</div>
|
||||
<div class="cart-summary-card__row">
|
||||
<div class="cart-summary-card__row cart-summary-card__row--hint">
|
||||
<span class="cart-summary-card__row-label">
|
||||
<IconOrders :size="16" />
|
||||
مبلغ قابل پرداخت
|
||||
مبلغ فاکتور
|
||||
</span>
|
||||
<span class="cart-summary-card__hint-value">
|
||||
پس از قیمتگذاری مدیریت
|
||||
</span>
|
||||
<strong>{{
|
||||
payableAmount > 0 ? formatCurrency(payableAmount) : '۰ تومان'
|
||||
}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="cart-summary-card__cta"
|
||||
:disabled="isEmpty"
|
||||
:disabled="isEmpty || submitting"
|
||||
@click="emit('checkout')"
|
||||
>
|
||||
<IconChevronLeft :size="16" />
|
||||
ادامه و انتخاب تسویه
|
||||
{{ submitting ? 'در حال ثبت…' : 'ثبت سفارش' }}
|
||||
</button>
|
||||
<p class="cart-summary-card__note">
|
||||
قیمتگذاری توسط مدیریت انجام میشود؛ پس از صدور فاکتور روش تسویه را انتخاب میکنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="cart-guide-card">
|
||||
@@ -475,7 +474,7 @@ const quickLinks = [
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
font-size: 0.8125rem;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.cart-summary-card__row:last-child {
|
||||
@@ -494,6 +493,18 @@ const quickLinks = [
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.cart-summary-card__hint-value {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.cart-summary-card__note {
|
||||
margin: 10px 0 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.cart-summary-card__cta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -13,6 +13,7 @@ defineProps<{
|
||||
maxMeterage: number
|
||||
remainingCapacity: number
|
||||
isEmpty: boolean
|
||||
submitting?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -78,7 +79,11 @@ const emit = defineEmits<{
|
||||
<strong>{{ formatMeterage(remainingCapacity) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<BaseButton block @click="emit('checkout')">ادامه و انتخاب نوع تسویه</BaseButton>
|
||||
<p class="cart-mobile-page__hint">
|
||||
پس از ثبت، مدیریت سفارش را قیمتگذاری میکند و فاکتور برای انتخاب روش تسویه به شما اعلام
|
||||
میشود.
|
||||
</p>
|
||||
<BaseButton block :loading="submitting" @click="emit('checkout')">ثبت سفارش</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,6 +113,13 @@ const emit = defineEmits<{
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.cart-mobile-page__hint {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.cart-mobile-page__title {
|
||||
margin: 0;
|
||||
font-size: 1.0625rem;
|
||||
|
||||
@@ -1,48 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useCatalogStore } from '@/stores/catalog'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import IconSuccessCircle from '@/components/icons/IconSuccessCircle.vue'
|
||||
import { toPersianDigits } from '@/utils/format'
|
||||
import type { BankAccount, Order } from '@/types'
|
||||
import { formatCurrency, formatDateTime } from '@/utils/format'
|
||||
import type { BankAccount } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
const authStore = useAuthStore()
|
||||
const catalogStore = useCatalogStore()
|
||||
const bankAccounts = ref<BankAccount[]>([])
|
||||
const submitting = ref(false)
|
||||
const formError = ref('')
|
||||
const createdOrder = ref<Order | null>(null)
|
||||
|
||||
const companyId = computed(() => {
|
||||
const firstItem = cartStore.activeCart?.items[0]
|
||||
return firstItem?.companyId || cartStore.selectedCompanyId || cartStore.activeCart?.companyId || ''
|
||||
})
|
||||
const companyName = computed(
|
||||
() => cartStore.companyNamesLabel || cartStore.activeCart?.companyName || '',
|
||||
const orderId = computed(() => route.params.id as string)
|
||||
const order = computed(() => orderStore.getOrderById(orderId.value))
|
||||
const companyId = computed(() => order.value?.companyId ?? '')
|
||||
const companyName = computed(() => order.value?.companyName ?? '')
|
||||
|
||||
watch(
|
||||
orderId,
|
||||
async (id) => {
|
||||
if (id && !orderStore.getOrderById(id)) await orderStore.fetchOrder(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!catalogStore.getCompany(companyId.value)) {
|
||||
await catalogStore.loadCompanies()
|
||||
}
|
||||
if (companyId.value) {
|
||||
bankAccounts.value = await catalogStore.loadBankAccounts(companyId.value)
|
||||
}
|
||||
})
|
||||
watch(
|
||||
companyId,
|
||||
async (id) => {
|
||||
if (!id) {
|
||||
bankAccounts.value = []
|
||||
return
|
||||
}
|
||||
if (!catalogStore.getCompany(id)) await catalogStore.loadCompanies()
|
||||
bankAccounts.value = await catalogStore.loadBankAccounts(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const invoiceLabel = computed(() =>
|
||||
order.value?.finalAmount ? formatCurrency(order.value.finalAmount) : '—',
|
||||
)
|
||||
|
||||
const expiryMessage = computed(() => {
|
||||
const hour = catalogStore.getCompany(companyId.value)?.cashInvoiceValidUntilHour ?? 17
|
||||
const time = toPersianDigits(String(hour).padStart(2, '0'))
|
||||
return `پس از ثبت، فاکتور تا ساعت ${time}:۰۰ امروز معتبر است.`
|
||||
const expiry = order.value?.cashInvoiceExpiresAt
|
||||
if (!expiry) return 'مبلغ فاکتور را به یکی از حسابهای زیر واریز و رسید را ثبت کنید.'
|
||||
return `مهلت پرداخت این فاکتور تا ${formatDateTime(expiry)} است.`
|
||||
})
|
||||
|
||||
const accountsSectionTitle = computed(() => {
|
||||
@@ -61,34 +67,12 @@ function formatIban(value: string) {
|
||||
return clean.replace(/(.{4})/g, '$1 ').trim()
|
||||
}
|
||||
|
||||
async function createOrder(): Promise<Order | null> {
|
||||
if (createdOrder.value) return createdOrder.value
|
||||
if (!authStore.user || !cartStore.activeCart || submitting.value) return null
|
||||
formError.value = ''
|
||||
submitting.value = true
|
||||
try {
|
||||
const order = await orderStore.submitOrder(authStore.user.id, cartStore.activeCart, 'cash')
|
||||
createdOrder.value = order
|
||||
await cartStore.hydrate()
|
||||
return order
|
||||
} catch (e) {
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
||||
return null
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
function viewOrder() {
|
||||
router.replace(`/orders/${orderId.value}?track=1`)
|
||||
}
|
||||
|
||||
async function viewOrder() {
|
||||
const order = await createOrder()
|
||||
if (!order) return
|
||||
router.replace(`/orders/${order.id}?track=1`)
|
||||
}
|
||||
|
||||
async function registerReceipt() {
|
||||
const order = await createOrder()
|
||||
if (!order) return
|
||||
router.replace(`/orders/${order.id}?track=1&receipt=1`)
|
||||
function registerReceipt() {
|
||||
router.replace(`/orders/${orderId.value}?track=1&receipt=1`)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -99,17 +83,13 @@ async function registerReceipt() {
|
||||
title="تسویه نقدی"
|
||||
show-back
|
||||
centered
|
||||
@back="router.push('/checkout/settlement')"
|
||||
@back="router.push(`/orders/${orderId}`)"
|
||||
/>
|
||||
|
||||
<div class="cash-page__scroll page__scroll">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="cash-page__success" :class="{ 'cash-page__success--done': createdOrder }">
|
||||
<IconSuccessCircle v-if="createdOrder" :size="56" />
|
||||
<p class="cash-page__success-title">
|
||||
{{ createdOrder ? 'فاکتور شما ثبت شد.' : 'آماده ثبت فاکتور نقدی' }}
|
||||
</p>
|
||||
<div class="cash-page__success cash-page__success--done">
|
||||
<IconSuccessCircle :size="56" />
|
||||
<p class="cash-page__success-title">مبلغ قابل پرداخت {{ invoiceLabel }}</p>
|
||||
<p class="cash-page__success-subtitle">{{ expiryMessage }}</p>
|
||||
</div>
|
||||
|
||||
@@ -138,11 +118,9 @@ async function registerReceipt() {
|
||||
</div>
|
||||
|
||||
<div class="cash-page__footer">
|
||||
<BaseButton variant="outline" block :loading="submitting" @click="viewOrder">
|
||||
ثبت و مشاهده سفارش
|
||||
</BaseButton>
|
||||
<BaseButton block class="cash-page__submit" :loading="submitting" @click="registerReceipt">
|
||||
ثبت سفارش و رسید پرداخت
|
||||
<BaseButton variant="outline" block @click="viewOrder">مشاهده سفارش</BaseButton>
|
||||
<BaseButton block class="cash-page__submit" @click="registerReceipt">
|
||||
ثبت رسید پرداخت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,15 +132,11 @@ async function registerReceipt() {
|
||||
<p class="desktop-page__subtitle">{{ expiryMessage }}</p>
|
||||
</header>
|
||||
|
||||
<div class="cash-page__success" :class="{ 'cash-page__success--done': createdOrder }">
|
||||
<IconSuccessCircle v-if="createdOrder" :size="56" />
|
||||
<p class="cash-page__success-title">
|
||||
{{ createdOrder ? 'فاکتور شما ثبت شد.' : 'آماده ثبت فاکتور نقدی' }}
|
||||
</p>
|
||||
<div class="cash-page__success cash-page__success--done">
|
||||
<IconSuccessCircle :size="56" />
|
||||
<p class="cash-page__success-title">مبلغ قابل پرداخت {{ invoiceLabel }}</p>
|
||||
</div>
|
||||
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<section class="accounts-panel">
|
||||
<h2 class="accounts-panel__title">{{ accountsSectionTitle }}</h2>
|
||||
<div class="desktop-grid-2">
|
||||
@@ -184,11 +158,11 @@ async function registerReceipt() {
|
||||
</section>
|
||||
|
||||
<div class="cash-desktop-page__actions">
|
||||
<BaseButton variant="outline" class="desktop-btn-compact" :loading="submitting" @click="viewOrder">
|
||||
ثبت و مشاهده سفارش
|
||||
<BaseButton variant="outline" class="desktop-btn-compact" @click="viewOrder">
|
||||
مشاهده سفارش
|
||||
</BaseButton>
|
||||
<BaseButton class="desktop-btn-compact" :loading="submitting" @click="registerReceipt">
|
||||
ثبت سفارش و رسید پرداخت
|
||||
<BaseButton class="desktop-btn-compact" @click="registerReceipt">
|
||||
ثبت رسید پرداخت
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import BaseInput from '@/components/ui/BaseInput.vue'
|
||||
@@ -22,9 +22,11 @@ import {
|
||||
fileToBase64,
|
||||
} from '@/utils/validation'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const orderId = computed(() => route.params.id as string)
|
||||
const isFull = computed(() => orderStore.draftCheckMode === 'full')
|
||||
|
||||
const amount = ref('')
|
||||
@@ -137,7 +139,7 @@ function saveCheck() {
|
||||
})
|
||||
|
||||
resetForm()
|
||||
router.push('/checkout/check-list')
|
||||
router.push(`/orders/${orderId.value}/check-list`)
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
@@ -163,7 +165,7 @@ function resetForm() {
|
||||
<PageHeader
|
||||
:title="isFull ? 'ثبت چک - حالت کامل' : 'ثبت چک - حالت مختصر'"
|
||||
show-back
|
||||
@back="router.push('/checkout/check-mode')"
|
||||
@back="router.push(`/orders/${orderId}/check-mode`)"
|
||||
/>
|
||||
|
||||
<div class="page__scroll checkout-page__scroll">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } 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'
|
||||
@@ -15,43 +14,50 @@ import type { CheckData } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
const authStore = useAuthStore()
|
||||
const formError = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
const isCheckout = computed(() => route.name === 'check-list')
|
||||
const orderId = computed(() => (route.params.id as string | undefined) ?? '')
|
||||
const order = computed(() =>
|
||||
orderId.value ? orderStore.getOrderById(orderId.value) : undefined,
|
||||
)
|
||||
|
||||
watch(
|
||||
orderId,
|
||||
async (id) => {
|
||||
if (!id || !isCheckout.value) return
|
||||
const current = orderStore.getOrderById(id) ?? (await orderStore.fetchOrder(id))
|
||||
if (!current) return
|
||||
// در مسیر اصلاح، چکهای ثبتشده به فرم برمیگردند تا مشتری ویرایش کند
|
||||
if (orderStore.draftChecks.length === 0 && (current.checks?.length ?? 0) > 0) {
|
||||
orderStore.loadChecksIntoDraft(current)
|
||||
} else if (orderStore.draftOrderId !== id) {
|
||||
orderStore.startCheckDraft(id, current.checkMode ?? undefined)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const checks = computed<CheckData[]>(() => {
|
||||
if (orderStore.draftChecks.length > 0) return orderStore.draftChecks
|
||||
if (isCheckout.value) return order.value?.checks ?? []
|
||||
if (!authStore.user) return []
|
||||
|
||||
return orderStore
|
||||
.userOrders(authStore.user.id)
|
||||
.filter((order) => order.settlementType === 'check' && order.checks?.length)
|
||||
.flatMap((order) => order.checks ?? [])
|
||||
.filter((o) => o.settlementType === 'check' && o.checks?.length)
|
||||
.flatMap((o) => o.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 invoiceAmount = computed(() => order.value?.finalAmount ?? 0)
|
||||
|
||||
const isAmountComplete = computed(
|
||||
() => invoiceAmount.value > 0 && rass.value.totalAmount >= invoiceAmount.value,
|
||||
@@ -65,11 +71,11 @@ const openMenuId = computed({
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
router.push(isCheckout.value ? '/checkout/check-form' : '/profile')
|
||||
router.push(isCheckout.value ? `/orders/${orderId.value}` : '/profile')
|
||||
}
|
||||
|
||||
function addAnother() {
|
||||
router.push('/checkout/check-form')
|
||||
router.push(`/orders/${orderId.value}/check-form`)
|
||||
}
|
||||
|
||||
async function removeCheck(id: string) {
|
||||
@@ -89,24 +95,20 @@ function toggleMenu(id: string) {
|
||||
openMenuId.value = openMenuId.value === id ? '' : id
|
||||
}
|
||||
|
||||
async function submitOrder() {
|
||||
if (!authStore.user || !cartStore.activeCart || checks.value.length === 0) return
|
||||
if (submitting.value) return
|
||||
async function submitChecks() {
|
||||
if (!orderId.value || orderStore.draftChecks.length === 0 || submitting.value) return
|
||||
formError.value = ''
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
const order = await orderStore.submitOrder(
|
||||
authStore.user.id,
|
||||
cartStore.activeCart,
|
||||
'check',
|
||||
await orderStore.updateOrderChecks(
|
||||
orderId.value,
|
||||
[...orderStore.draftChecks],
|
||||
orderStore.draftCheckMode ?? 'brief',
|
||||
)
|
||||
await cartStore.hydrate()
|
||||
router.replace(`/orders/${order.id}/success`)
|
||||
router.replace(`/orders/${orderId.value}?track=1`)
|
||||
} catch (e) {
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت سفارش ناموفق بود.'
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت چکها ناموفق بود.'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
@@ -203,7 +205,7 @@ async function submitOrder() {
|
||||
block
|
||||
:disabled="checks.length === 0"
|
||||
:loading="submitting"
|
||||
@click="submitOrder"
|
||||
@click="submitChecks"
|
||||
>
|
||||
ارسال برای بررسی
|
||||
</BaseButton>
|
||||
@@ -283,7 +285,7 @@ async function submitOrder() {
|
||||
block
|
||||
:disabled="checks.length === 0"
|
||||
:loading="submitting"
|
||||
@click="submitOrder"
|
||||
@click="submitChecks"
|
||||
>
|
||||
ارسال برای بررسی
|
||||
</BaseButton>
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import IconCheckBook from '@/components/icons/IconCheckBook.vue'
|
||||
import IconEditPen from '@/components/icons/IconEditPen.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
const orderId = computed(() => route.params.id as string)
|
||||
|
||||
watch(
|
||||
orderId,
|
||||
async (id) => {
|
||||
if (!id) return
|
||||
if (!orderStore.getOrderById(id)) await orderStore.fetchOrder(id)
|
||||
if (orderStore.draftOrderId !== id) orderStore.startCheckDraft(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function selectMode(mode: 'full' | 'brief') {
|
||||
orderStore.setCheckMode(mode)
|
||||
router.push('/checkout/check-form')
|
||||
router.push(`/orders/${orderId.value}/check-form`)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -21,7 +35,7 @@ function selectMode(mode: 'full' | 'brief') {
|
||||
subtitle="نحوه ثبت اطلاعات چکها را انتخاب کنید"
|
||||
show-back
|
||||
centered
|
||||
@back="router.push('/checkout/settlement')"
|
||||
@back="router.push(`/orders/${orderId}`)"
|
||||
/>
|
||||
|
||||
<div class="page__scroll check-mode-page__scroll">
|
||||
|
||||
@@ -1,23 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useCartStore } from '@/stores/cart'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import PageHeader from '@/components/layout/PageHeader.vue'
|
||||
import CheckoutStepper from '@/components/layout/CheckoutStepper.vue'
|
||||
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
|
||||
import IconCashSettlement from '@/components/icons/IconCashSettlement.vue'
|
||||
import IconCheckBook from '@/components/icons/IconCheckBook.vue'
|
||||
import { formatCurrency } from '@/utils/format'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const cartStore = useCartStore()
|
||||
const orderStore = useOrderStore()
|
||||
|
||||
function selectType(type: 'cash' | 'check') {
|
||||
if (!cartStore.activeCart) {
|
||||
router.push('/cart')
|
||||
return
|
||||
const orderId = computed(() => route.params.id as string)
|
||||
const order = computed(() => orderStore.getOrderById(orderId.value))
|
||||
const submitting = ref(false)
|
||||
const formError = ref('')
|
||||
|
||||
watch(
|
||||
orderId,
|
||||
async (id) => {
|
||||
if (id && !orderStore.getOrderById(id)) await orderStore.fetchOrder(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const invoiceLabel = computed(() =>
|
||||
order.value?.finalAmount ? formatCurrency(order.value.finalAmount) : '—',
|
||||
)
|
||||
|
||||
async function selectType(type: 'cash' | 'check') {
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
formError.value = ''
|
||||
try {
|
||||
await orderStore.chooseSettlement(orderId.value, type)
|
||||
if (type === 'cash') {
|
||||
router.replace(`/orders/${orderId.value}/cash`)
|
||||
} else {
|
||||
orderStore.startCheckDraft(orderId.value)
|
||||
router.replace(`/orders/${orderId.value}/check-mode`)
|
||||
}
|
||||
} catch (e) {
|
||||
formError.value = e instanceof Error ? e.message : 'ثبت روش تسویه ناموفق بود.'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
orderStore.startCheckout(cartStore.activeCart, type)
|
||||
router.push(type === 'cash' ? '/checkout/cash' : '/checkout/check-mode')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -29,18 +58,36 @@ function selectType(type: 'cash' | 'check') {
|
||||
subtitle="نوع تسویه را انتخاب کنید"
|
||||
show-back
|
||||
centered
|
||||
@back="router.push('/cart')"
|
||||
@back="router.push(`/orders/${orderId}`)"
|
||||
/>
|
||||
<div class="page__scroll">
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="settlement-invoice">
|
||||
<span class="settlement-invoice__label">مبلغ فاکتور</span>
|
||||
<strong class="settlement-invoice__value">{{ invoiceLabel }}</strong>
|
||||
<span v-if="order" class="settlement-invoice__meta">سفارش {{ order.orderNumber }}</span>
|
||||
</div>
|
||||
|
||||
<div class="settlement-mobile-options">
|
||||
<button type="button" class="settlement-card" @click="selectType('cash')">
|
||||
<button
|
||||
type="button"
|
||||
class="settlement-card"
|
||||
:disabled="submitting"
|
||||
@click="selectType('cash')"
|
||||
>
|
||||
<span class="settlement-card__icon settlement-card__icon--cash">
|
||||
<IconCashSettlement :size="36" />
|
||||
</span>
|
||||
<h3>تسویه نقدی</h3>
|
||||
<p>پرداخت از طریق واریز به حساب شرکت</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-card" @click="selectType('check')">
|
||||
<button
|
||||
type="button"
|
||||
class="settlement-card"
|
||||
:disabled="submitting"
|
||||
@click="selectType('check')"
|
||||
>
|
||||
<span class="settlement-card__icon settlement-card__icon--check">
|
||||
<IconCheckBook :size="36" />
|
||||
</span>
|
||||
@@ -53,20 +100,35 @@ function selectType(type: 'cash' | 'check') {
|
||||
|
||||
<!-- Desktop -->
|
||||
<div class="desktop-checkout-shell desktop-only">
|
||||
<CheckoutStepper :current-step="2" />
|
||||
<CheckoutStepper :current-step="4" />
|
||||
<header class="desktop-page__head">
|
||||
<h1 class="desktop-page__title">انتخاب نوع تسویه</h1>
|
||||
<p class="desktop-page__subtitle">نوع تسویه را انتخاب کنید</p>
|
||||
<p class="desktop-page__subtitle">
|
||||
مبلغ فاکتور {{ invoiceLabel }}{{ order ? ` · سفارش ${order.orderNumber}` : '' }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<FormErrorBanner :message="formError" />
|
||||
|
||||
<div class="desktop-settlement-grid">
|
||||
<button type="button" class="settlement-card settlement-card--desktop" @click="selectType('cash')">
|
||||
<button
|
||||
type="button"
|
||||
class="settlement-card settlement-card--desktop"
|
||||
:disabled="submitting"
|
||||
@click="selectType('cash')"
|
||||
>
|
||||
<span class="settlement-card__icon settlement-card__icon--cash">
|
||||
<IconCashSettlement :size="40" />
|
||||
</span>
|
||||
<h3>تسویه نقدی</h3>
|
||||
<p>پرداخت از طریق واریز به حساب شرکت</p>
|
||||
</button>
|
||||
<button type="button" class="settlement-card settlement-card--desktop" @click="selectType('check')">
|
||||
<button
|
||||
type="button"
|
||||
class="settlement-card settlement-card--desktop"
|
||||
:disabled="submitting"
|
||||
@click="selectType('check')"
|
||||
>
|
||||
<span class="settlement-card__icon settlement-card__icon--check">
|
||||
<IconCheckBook :size="40" />
|
||||
</span>
|
||||
@@ -105,6 +167,39 @@ function selectType(type: 'cash' | 'check') {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.settlement-card:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.settlement-invoice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 18px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 16px;
|
||||
background: var(--color-primary-light);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.settlement-invoice__label {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.settlement-invoice__value {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.settlement-invoice__meta {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.settlement-card--desktop {
|
||||
min-height: 220px;
|
||||
border: 1px solid var(--color-border);
|
||||
|
||||
@@ -111,10 +111,46 @@ function formatOrderDateTime(date: string) {
|
||||
return `${timePart} - ${datePart}`
|
||||
}
|
||||
|
||||
function settlementLabel(type: 'cash' | 'check') {
|
||||
function settlementLabel(type: 'cash' | 'check' | null) {
|
||||
if (!type) return 'در انتظار انتخاب مشتری'
|
||||
return type === 'cash' ? 'تسویه نقدی' : 'تسویه چکی'
|
||||
}
|
||||
|
||||
type NextAction = { label: string; to?: string; receipt?: boolean }
|
||||
|
||||
/** اقدام بعدی که در توپ زمین مشتری است. */
|
||||
const nextAction = computed<NextAction | null>(() => {
|
||||
const current = order.value
|
||||
if (!current) return null
|
||||
switch (current.status) {
|
||||
case 'awaiting_settlement':
|
||||
case 'awaiting_settlement_fix':
|
||||
return { label: 'انتخاب روش تسویه', to: `/orders/${current.id}/settlement` }
|
||||
case 'check_in_progress':
|
||||
return { label: 'ثبت چکها', to: `/orders/${current.id}/check-mode` }
|
||||
case 'check_needs_fix':
|
||||
case 'check_rejected':
|
||||
return { label: 'اصلاح چکها', to: `/orders/${current.id}/check-list` }
|
||||
case 'awaiting_cash_payment':
|
||||
case 'cash_invoice_issued':
|
||||
return { label: 'ثبت رسید پرداخت', receipt: true }
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
function runNextAction() {
|
||||
const action = nextAction.value
|
||||
if (!action) return
|
||||
if (action.receipt) {
|
||||
showTracking.value = true
|
||||
showOrderDetails.value = true
|
||||
showReceiptForm.value = true
|
||||
return
|
||||
}
|
||||
if (action.to) router.push(action.to)
|
||||
}
|
||||
|
||||
function openTracking() {
|
||||
showTracking.value = true
|
||||
showOrderDetails.value = false
|
||||
@@ -236,11 +272,18 @@ async function submitReceipt() {
|
||||
<div class="order-info-card__row order-info-card__row--last">
|
||||
<span class="order-info-card__key">مبلغ فاکتور</span>
|
||||
<strong class="order-info-card__amount">
|
||||
{{ order.finalAmount ? formatNumber(order.finalAmount) : '—' }}
|
||||
{{ order.finalAmount ? formatNumber(order.finalAmount) : 'پس از قیمتگذاری مدیریت' }}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="nextAction" class="order-next-action">
|
||||
<span class="order-next-action__text">برای ادامه سفارش، این مرحله باقی مانده است.</span>
|
||||
<BaseButton class="order-next-action__btn" @click="runNextAction">
|
||||
{{ nextAction.label }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<div v-if="showTracking" class="order-detail-page__tracking">
|
||||
<button type="button" class="order-detail-back desktop-only" @click="router.push('/orders')">
|
||||
← بازگشت به سفارشها
|
||||
@@ -286,11 +329,11 @@ async function submitReceipt() {
|
||||
</div>
|
||||
|
||||
<BaseButton
|
||||
v-if="!order.cashReceipt && order.status !== 'expired'"
|
||||
v-if="nextAction?.receipt"
|
||||
block
|
||||
@click="showReceiptForm = true"
|
||||
>
|
||||
ثبت رسید پرداخت
|
||||
{{ order.cashReceipt ? 'ثبت رسید جدید' : 'ثبت رسید پرداخت' }}
|
||||
</BaseButton>
|
||||
|
||||
<div v-if="order.cashReceipt" class="tracking-card">
|
||||
@@ -550,6 +593,36 @@ async function submitReceipt() {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.order-next-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: var(--color-primary-light);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.order-next-action__text {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.order-next-action {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.order-next-action__btn {
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.order-detail-page__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 0;
|
||||
|
||||
@@ -18,8 +18,8 @@ const order = computed(() => orderStore.getOrderById(route.params.id as string))
|
||||
<div class="success-icon">✓</div>
|
||||
<h1 class="page-title text-center">سفارش ثبت شد</h1>
|
||||
<p class="text-center text-muted mb-4">
|
||||
سفارش شما با موفقیت ثبت شد. اطلاعات محصولات و شرایط تسویه توسط واحد فروش بررسی شده و نتیجه از
|
||||
طریق پیامک و پنل کاربری اعلام میشود.
|
||||
سفارش شما با موفقیت ثبت شد. کارشناسان ما سفارش را بررسی و قیمتگذاری میکنند و بهمحض صدور
|
||||
فاکتور، از طریق پیامک و پنل کاربری به شما اطلاع میدهیم تا روش تسویه را انتخاب کنید.
|
||||
</p>
|
||||
|
||||
<div v-if="order" class="card mb-4">
|
||||
@@ -36,8 +36,8 @@ const order = computed(() => orderStore.getOrderById(route.params.id as string))
|
||||
<span>{{ formatMeterage(order.totalMeterage) }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span>نوع تسویه:</span>
|
||||
<span>{{ order.settlementType === 'cash' ? 'نقدی' : 'چکی' }}</span>
|
||||
<span>وضعیت:</span>
|
||||
<span>در انتظار قیمتگذاری</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,118 +1,190 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import BaseButton from '@/components/ui/BaseButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
import ProfileSubpageLayout from '@/views/profile/components/ProfileSubpageLayout.vue'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import type { AppNotification, NotificationTone } from '@/types'
|
||||
|
||||
const emailOrders = ref(true)
|
||||
const emailChecks = ref(true)
|
||||
const smsOrders = ref(true)
|
||||
const smsMarketing = ref(false)
|
||||
const saved = ref(false)
|
||||
const router = useRouter()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
function save() {
|
||||
saved.value = true
|
||||
setTimeout(() => {
|
||||
saved.value = false
|
||||
}, 2000)
|
||||
const items = computed(() => notificationStore.items)
|
||||
const hasUnread = computed(() => notificationStore.hasUnread)
|
||||
|
||||
const toneIcons: Record<NotificationTone, string> = {
|
||||
info: 'vuesax-linear:information',
|
||||
success: 'vuesax-linear:tick-circle',
|
||||
warning: 'vuesax-linear:timer',
|
||||
danger: 'vuesax-linear:close-circle',
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void notificationStore.hydrate()
|
||||
})
|
||||
|
||||
async function open(item: AppNotification) {
|
||||
await notificationStore.markRead(item.id)
|
||||
if (item.link) router.push(item.link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProfileSubpageLayout title="اعلانها" subtitle="مدیریت اعلانهای ایمیل و پیامک">
|
||||
<div class="notify-list">
|
||||
<label class="notify-row">
|
||||
<span class="notify-row__text">
|
||||
<strong>ایمیل وضعیت سفارش</strong>
|
||||
<span>اطلاع از تأیید، ارسال و تکمیل سفارش</span>
|
||||
<ProfileSubpageLayout title="اعلانها" subtitle="آخرین خبرهای سفارشهای شما">
|
||||
<div v-if="items.length" class="notif-box">
|
||||
<div class="notif-box__head">
|
||||
<span class="notif-box__count">
|
||||
{{ hasUnread ? `${notificationStore.unreadCount} اعلان خواندهنشده` : 'همه خوانده شده' }}
|
||||
</span>
|
||||
<input v-model="emailOrders" type="checkbox" class="notify-row__switch" />
|
||||
</label>
|
||||
<BaseButton
|
||||
v-if="hasUnread"
|
||||
variant="outline"
|
||||
class="notif-box__mark"
|
||||
@click="notificationStore.markAllRead()"
|
||||
>
|
||||
خواندن همه
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<label class="notify-row">
|
||||
<span class="notify-row__text">
|
||||
<strong>ایمیل سررسید چک</strong>
|
||||
<span>یادآوری قبل از موعد چکهای ثبتشده</span>
|
||||
<button
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="notif-row"
|
||||
:class="[`notif-row--${item.tone}`, { 'notif-row--unread': !item.isRead }]"
|
||||
@click="open(item)"
|
||||
>
|
||||
<span class="notif-row__icon" aria-hidden="true">
|
||||
<BasicIcon :icon="toneIcons[item.tone]" :size="22" :stroke-width="1.5" />
|
||||
</span>
|
||||
<input v-model="emailChecks" type="checkbox" class="notify-row__switch" />
|
||||
</label>
|
||||
|
||||
<label class="notify-row">
|
||||
<span class="notify-row__text">
|
||||
<strong>پیامک وضعیت سفارش</strong>
|
||||
<span>ارسال پیامک برای تغییر وضعیت سفارش</span>
|
||||
<span class="notif-row__body">
|
||||
<strong class="notif-row__title">{{ item.title }}</strong>
|
||||
<span class="notif-row__text">{{ item.body }}</span>
|
||||
<span class="notif-row__time">{{ formatDateTime(item.sentAt) }}</span>
|
||||
</span>
|
||||
<input v-model="smsOrders" type="checkbox" class="notify-row__switch" />
|
||||
</label>
|
||||
|
||||
<label class="notify-row">
|
||||
<span class="notify-row__text">
|
||||
<strong>پیامکهای تبلیغاتی</strong>
|
||||
<span>تخفیفها و پیشنهادهای ویژه</span>
|
||||
</span>
|
||||
<input v-model="smsMarketing" type="checkbox" class="notify-row__switch" />
|
||||
</label>
|
||||
<span v-if="!item.isRead" class="notif-row__dot" aria-label="خواندهنشده" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<BaseButton block class="notify-save" @click="save">ذخیره تنظیمات</BaseButton>
|
||||
<p v-if="saved" class="notify-saved">تنظیمات اعلان ذخیره شد ✓</p>
|
||||
<EmptyState
|
||||
v-else
|
||||
icon="vuesax-linear:notification-bing"
|
||||
title="اعلانی ندارید"
|
||||
message="وقتی فاکتور سفارشتان صادر شود یا وضعیت چک و پرداخت تغییر کند، همینجا خبردار میشوید."
|
||||
>
|
||||
<BaseButton variant="outline" @click="router.push('/orders')">سفارشهای من</BaseButton>
|
||||
</EmptyState>
|
||||
</ProfileSubpageLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.notify-list {
|
||||
.notif-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.notify-row {
|
||||
.notif-box__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
gap: 12px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.notif-box__count {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.notif-box__mark {
|
||||
width: auto;
|
||||
padding-inline: 14px;
|
||||
}
|
||||
|
||||
.notif-row {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1fr auto;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
text-align: right;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 14px;
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.notify-row:last-of-type {
|
||||
border-bottom: none;
|
||||
.notif-row:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.notify-row__text {
|
||||
.notif-row--unread {
|
||||
background: var(--color-elevated);
|
||||
}
|
||||
|
||||
.notif-row__icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-info-light);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.notif-row--success .notif-row__icon {
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.notif-row--warning .notif-row__icon {
|
||||
background: var(--color-warning-light);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.notif-row--danger .notif-row__icon {
|
||||
background: var(--color-danger-light);
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.notif-row__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notify-row__text strong {
|
||||
.notif-row__title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.notify-row__text span {
|
||||
font-size: 0.75rem;
|
||||
.notif-row__text {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.notify-row__switch {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
.notif-row__time {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.notif-row__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-top: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-danger);
|
||||
flex-shrink: 0;
|
||||
accent-color: var(--color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notify-save {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.notify-saved {
|
||||
margin: 8px 0 0;
|
||||
font-size: 0.875rem;
|
||||
text-align: center;
|
||||
color: var(--color-success);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -111,7 +111,7 @@ const settingsItems: Array<{
|
||||
{
|
||||
id: 'notifications',
|
||||
label: 'اعلانها',
|
||||
description: 'مدیریت اعلانهای ایمیل و پیامک',
|
||||
description: 'خبرهای فاکتور، چک و ارسال سفارش',
|
||||
icon: IconBell,
|
||||
path: '/profile/notifications',
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useOrderStore } from '@/stores/order'
|
||||
import { useNotificationStore } from '@/stores/notification'
|
||||
import IconBell from '@/components/icons/IconBell.vue'
|
||||
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
|
||||
import IconProfile from '@/components/icons/IconProfile.vue'
|
||||
import IconCheckBook from '@/components/icons/IconCheckBook.vue'
|
||||
@@ -12,10 +14,19 @@ import IconSettings from '@/components/icons/IconSettings.vue'
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const orderStore = useOrderStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
const draftCheckCount = computed(() => orderStore.draftChecks.length)
|
||||
|
||||
const menuItems = computed(() => [
|
||||
{
|
||||
id: 'notifications',
|
||||
label: 'اعلانها',
|
||||
description: 'خبرهای فاکتور، چک و ارسال',
|
||||
path: '/profile/notifications',
|
||||
icon: IconBell,
|
||||
badge: notificationStore.unreadCount,
|
||||
},
|
||||
{
|
||||
id: 'edit',
|
||||
label: 'اطلاعات کاربری',
|
||||
|
||||
Reference in New Issue
Block a user