SearchView: Removed the dynamic collection-tile grid (e.g. "پرفروشهای این هفته") and the "نتایج / N محصول" section header from the UI. Underlying data/filtering logic untouched.
DashboardView ("میز کار"): Rebuilt from scratch with a new structure, distinct from the old card+grid layout but visually aligned with HomeView (same blurred bar, @ink/@wine palette, staged reveal animation on mount):
Single hero card (ink→wine gradient) showing balance, gold/fiat debt, and identity — no quick-action icons inside it anymore.
Live gold rate folded into a slim inline row at the bottom instead of a titled section.
Iterated shadow/spacing on the hero card per feedback (softer, less clipped).
DialogVant / DialogVantOneButton (shared modal components, used in 7+ call sites: Dashboard, Cart, SingleProduct, Register, App): Replaced Vant's default dialog chrome with the app's own rounded-card language — circular icon badge for the illustration, pill-shaped primary/secondary buttons. No call-site markup changed.
LoginView: Rebuilt from scratch — full-bleed dark hero (brand + step copy) overlapped by a floating white form sheet, animated crossfade transition between the mobile-number and OTP steps, same staged entrance animation as Home/Dashboard. All auth logic (API check, OTP stub, session login) unchanged.
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
<!--
|
||||
Ported from legacy henza_pwa/src/views/user/src/components/transactions.vue
|
||||
("اسناد" — financial documents), restyled into the redesigned design
|
||||
language (see components/home/* and HomeView).
|
||||
|
||||
Data source: legacy listed `Cash` rows from POST /get-my-findoc. The new
|
||||
system's equivalent is LedgerEntry, already exposed by
|
||||
GET /customers/:id/ledger-entries, so the list is real data.
|
||||
|
||||
Legacy's colour/icon logic is preserved in meaning: `hasFactor(item)` was
|
||||
`item.type != 9` (type 9 = سفارش, the debt-creating order entry) → a green
|
||||
"in" treatment for everything else, red "out" for orders. That maps 1:1 onto
|
||||
our CREDIT/DEBIT direction, since an ORDER_STOCK entry is exactly the DEBIT
|
||||
case. The palette is now the design system's own rather than hardcoded hexes.
|
||||
|
||||
DROPPED, unchanged from the previous version: the "دریافت فایل" per-document
|
||||
download — legacy's getFile() hit the old Laravel host; there is no
|
||||
document-file endpoint in the new API.
|
||||
-->
|
||||
<template>
|
||||
<div class="tx">
|
||||
<PageBar title="اسناد" back-to="userPage::hasSubRoute" />
|
||||
|
||||
<Spinner :spinning="loading" tip="درحال دریافت اطلاعات">
|
||||
<container class="tx__body">
|
||||
<template v-if="entries.length">
|
||||
<SectionHead title="گردش حساب" :subtitle="`${entries.length} سند`" />
|
||||
|
||||
<div class="tx__list">
|
||||
<article v-for="item in entries" :key="item.id" class="tx__row" @click="openEntry(item)">
|
||||
<span class="tx__mark" :class="isCredit(item) ? 'is-in' : 'is-out'" aria-hidden="true">
|
||||
<Icon :icon="isCredit(item) ? 'vuesax-linear:arrow-down' : 'vuesax-linear:arrow-up'" size="16" />
|
||||
</span>
|
||||
|
||||
<div class="tx__main">
|
||||
<div class="tx__head">
|
||||
<h3 class="tx__kind">{{ SOURCE_LABELS[item.sourceType] ?? item.sourceType }}</h3>
|
||||
<span class="tx__amount" :class="isCredit(item) ? 'is-in' : 'is-out'">
|
||||
{{ formatAmount(item) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p v-if="item.note" class="tx__note">{{ item.note }}</p>
|
||||
|
||||
<div class="tx__foot">
|
||||
<span class="tx__date">{{ jdate(item.createdAt) }}</span>
|
||||
<span class="tx__state" :class="{ 'is-pending': !item.confirmed }">
|
||||
{{ item.confirmed ? 'تایید شده' : 'در انتظار تایید' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<section v-else-if="!loading" class="tx__empty">
|
||||
<Icon icon="vuesax-linear:receipt-text" size="26" />
|
||||
<p>سندی برای حساب شما ثبت نشده است.</p>
|
||||
</section>
|
||||
</container>
|
||||
</Spinner>
|
||||
|
||||
<Drawer
|
||||
v-model:visible="showPopUp"
|
||||
placement="bottom"
|
||||
v-if="activeEntry"
|
||||
:closable="false"
|
||||
height="420px"
|
||||
class="rtl"
|
||||
:bodyStyle="{ padding: 0 }"
|
||||
>
|
||||
<div class="sheet">
|
||||
<h3 class="sheet__title">جزئیات سند</h3>
|
||||
<dl class="sheet__specs">
|
||||
<div class="sheet__spec">
|
||||
<dt>نوع سند</dt>
|
||||
<dd>{{ SOURCE_LABELS[activeEntry.sourceType] ?? activeEntry.sourceType }}</dd>
|
||||
</div>
|
||||
<div class="sheet__spec">
|
||||
<dt>مبلغ</dt>
|
||||
<dd :class="isCredit(activeEntry) ? 'is-in' : 'is-out'">{{ formatAmount(activeEntry) }}</dd>
|
||||
</div>
|
||||
<div class="sheet__spec">
|
||||
<dt>وضعیت</dt>
|
||||
<dd>{{ activeEntry.confirmed ? 'تایید شده' : 'در انتظار تایید' }}</dd>
|
||||
</div>
|
||||
<div class="sheet__spec">
|
||||
<dt>تاریخ</dt>
|
||||
<dd>{{ jdate(activeEntry.createdAt) }}</dd>
|
||||
</div>
|
||||
<div class="sheet__spec">
|
||||
<dt>توضیحات</dt>
|
||||
<dd>{{ activeEntry.note || '—' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Drawer } from 'ant-design-vue'
|
||||
import Icon from '../../components/icon/Icon.vue'
|
||||
import PageBar from '../../components/ui/PageBar.vue'
|
||||
import Spinner from '../../components/Spinner.vue'
|
||||
import SectionHead from '../../components/home/SectionHead.vue'
|
||||
import { jdate, price } from '../../utils/filters'
|
||||
import { useSessionStore } from '../../stores/session'
|
||||
import { api, type LedgerEntrySummary } from '../../api/client'
|
||||
|
||||
const SOURCE_LABELS: Record<string, string> = {
|
||||
ORDER_STOCK: 'سفارش',
|
||||
ORDER_PRE_ORDER: 'سفارش ساخت',
|
||||
REPAYMENT_MELTED_GOLD: 'طلای آبشده',
|
||||
REPAYMENT_TRANSFER: 'حواله',
|
||||
REPAYMENT_CHEQUE: 'چک',
|
||||
REPAYMENT_CASH: 'پولی',
|
||||
WALLET_BONUS: 'پاداش',
|
||||
ADJUSTMENT: 'اصلاحیه',
|
||||
}
|
||||
|
||||
const session = useSessionStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const entries = ref<LedgerEntrySummary[]>([])
|
||||
const showPopUp = ref(false)
|
||||
const activeEntry = ref<LedgerEntrySummary | null>(null)
|
||||
|
||||
// legacy hasFactor(item) === item.type != 9 (9 = سفارش/order). Our DEBIT
|
||||
// entries are exactly that case.
|
||||
const isCredit = (item: LedgerEntrySummary) => item.direction === 'CREDIT'
|
||||
|
||||
const formatAmount = (item: LedgerEntrySummary) =>
|
||||
item.currency === 'GOLD_GRAM' ? `${item.amount} گرم` : `${price(item.amount)} ریال`
|
||||
|
||||
const openEntry = (item: LedgerEntrySummary) => {
|
||||
activeEntry.value = item
|
||||
showPopUp.value = true
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!session.customerId) return
|
||||
loading.value = true
|
||||
try {
|
||||
entries.value = await api.getLedgerEntries(session.customerId)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tx {
|
||||
background: @canvas;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.tx__body {
|
||||
padding-top: 76px;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
.tx__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tx__row {
|
||||
display: flex;
|
||||
gap: 11px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
background: @canvas;
|
||||
border: 1px solid @hairline;
|
||||
border-radius: 18px;
|
||||
transition: transform 0.18s ease;
|
||||
}
|
||||
|
||||
.tx__row:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.tx__mark {
|
||||
flex: 0 0 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.tx__mark.is-in {
|
||||
color: @green;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.tx__mark.is-out {
|
||||
color: @wine;
|
||||
background: @wineWash;
|
||||
}
|
||||
|
||||
.tx__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tx__head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tx__kind {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: @ink;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tx__amount {
|
||||
font-size: 12.5px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tx__amount.is-in {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
.tx__amount.is-out {
|
||||
color: @wine;
|
||||
}
|
||||
|
||||
.tx__note {
|
||||
margin: 6px 0 0;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.7;
|
||||
color: @inkMuted;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tx__foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.tx__date {
|
||||
font-size: 11px;
|
||||
color: @inkFaint;
|
||||
}
|
||||
|
||||
.tx__state {
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
color: @inkMuted;
|
||||
}
|
||||
|
||||
.tx__state.is-pending {
|
||||
color: @notify2;
|
||||
}
|
||||
|
||||
.tx__empty {
|
||||
margin-top: 8px;
|
||||
padding: 34px 20px;
|
||||
border: 1px dashed @hairline;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: @inkFaint;
|
||||
}
|
||||
.tx__empty p {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.sheet {
|
||||
padding: 18px 16px 22px;
|
||||
}
|
||||
|
||||
.sheet__title {
|
||||
margin: 0 0 14px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 800;
|
||||
color: @ink;
|
||||
}
|
||||
|
||||
.sheet__specs {
|
||||
margin: 0;
|
||||
border: 1px solid @hairline;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sheet__spec {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid @hairlineSoft;
|
||||
}
|
||||
|
||||
.sheet__spec:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.sheet__spec dt {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: @inkFaint;
|
||||
}
|
||||
|
||||
.sheet__spec dd {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
color: @ink;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sheet__spec dd.is-in {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
.sheet__spec dd.is-out {
|
||||
color: @wine;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user