feat: initial commit

This commit is contained in:
s.esmaeili
2026-09-09 06:46:27 +03:30
commit 0ed6bed028
214 changed files with 20177 additions and 0 deletions
@@ -0,0 +1,168 @@
<!--
Ported from legacy henza_pwa/src/views/user/src/components/transactions.vue
("اسناد" — financial documents). Replaces the English AccountView scaffold
that had no legacy counterpart.
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 exactly: `hasFactor(item)` was
`item.type != 9` (type 9 = سفارش, the debt-creating order entry) → green
#F0FDF4/#22C55E + add-square for everything else, red #FEF2F2/#F44336 +
minus-square for orders. That maps 1:1 onto our CREDIT/DEBIT direction,
since an ORDER_STOCK entry is exactly the DEBIT case.
DROPPED: 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="fixed top-0 z-10 w-full">
<AppHeader>
<div class="flex justify-between items-center">
<Icon icon="vuesax-linear:arrow-right-1" class="cursor-pointer text-white" size="25" @click="router.push({ name: 'userPage::hasSubRoute' })" />
<h3 class="text-white bold mb-0">اسناد</h3>
<div></div>
</div>
</AppHeader>
</div>
<div class="h-24 bg-[#F9FAFB]"></div>
<container class="overflow-y-auto bg-[#F9FAFB]">
<Spinner :spinning="loading" tip="درحال دریافت اطلاعات" cls="no-scrollbar overflow-auto">
<div
v-if="entries.length"
v-for="item in entries"
:key="item.id"
@click="openEntry(item)"
class="rounded-[10px] border-[#E8EAEE] py-[16px] px-[14px] w-full mb-[16px] grid grid-cols-8 items-center cursor-pointer bg-white"
>
<div class="col-span-1 items-end h-full">
<Icon
:icon="getIcon(item)"
:style="{ background: getColor(item), color: getColor2(item) }"
class="rounded-full p-[10px] ml-2 h-[40px] w-[40px]"
size="20"
/>
</div>
<div class="col-span-7 pr-1 text-primary mt-2">
<div class="flex justify-between items-center">
<div class="font-bold mb-2">{{ item.note || '- -' }}</div>
<div
class="rounded-[5px] px-[20px] py-[4px] w-fit text-[16px] mb-2 font-semibold"
:style="{ background: getColor(item), color: getColor2(item) }"
>
{{ SOURCE_LABELS[item.sourceType] ?? item.sourceType }}
</div>
</div>
<div class="flex justify-between items-center">
<div class="text-[14px] font-semibold mb-2">{{ formatAmount(item) }}</div>
<div class="h-10"></div>
</div>
<div class="mb-2 text-[14px] text-grey font-semibold">{{ jdate(item.createdAt) }}</div>
<div class="flex justify-between pt-1">
<div class="text-[12px] font-bold">{{ item.confirmed ? 'تایید شده' : 'در انتظار تایید' }}</div>
<Icon icon="vuesax-linear:arrow-left-1" class="cursor-pointer text-primary" size="20" />
</div>
</div>
</div>
<EmptyState v-else :image="transactionEmpty" />
</Spinner>
<Drawer
v-model:visible="showPopUp"
placement="bottom"
v-if="activeEntry"
:closable="false"
height="450px"
:class="['henza-rounded-drawer rtl', 'bg-white', 'text-primary']"
:bodyStyle="{ display: 'flex', flexDirection: 'column' }"
>
<div class="w-full h-full flex flex-col">
<div class="pb-[10px] border-b border-solid border-[#E5E7EB] flex text-[16px] font-bold">جزئیات سند</div>
<div class="flex-1 py-[10px] overflow-auto no-scrollbar">
<div class="flex justify-between mb-4">
<div class="text-grey">نوع سند :</div>
<div class="font-semibold">{{ SOURCE_LABELS[activeEntry.sourceType] ?? activeEntry.sourceType }}</div>
</div>
<div class="flex justify-between mb-4">
<div class="text-grey">مبلغ :</div>
<div class="font-semibold">{{ formatAmount(activeEntry) }}</div>
</div>
<div class="flex justify-between mb-4">
<div class="text-grey">وضعیت :</div>
<div class="font-semibold">{{ activeEntry.confirmed ? 'تایید شده' : 'در انتظار تایید' }}</div>
</div>
<div class="flex justify-between mb-4">
<div class="text-grey">تاریخ :</div>
<div class="font-semibold">{{ jdate(activeEntry.createdAt) }}</div>
</div>
<div class="flex justify-between mb-4">
<div class="text-grey">توضیحات :</div>
<div class="font-semibold">{{ activeEntry.note || '- -' }}</div>
</div>
</div>
</div>
</Drawer>
</container>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Drawer } from 'ant-design-vue'
import Icon from '../../components/icon/Icon.vue'
import AppHeader from '../../components/AppHeader.vue'
import Spinner from '../../components/Spinner.vue'
import EmptyState from '../../components/EmptyState.vue'
import transactionEmpty from '../../assets/img/empty/transactionEmpty.svg'
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 router = useRouter()
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 getIcon = (item: LedgerEntrySummary) => (isCredit(item) ? 'vuesax-linear:add-square' : 'vuesax-linear:minus-square')
const getColor = (item: LedgerEntrySummary) => (isCredit(item) ? '#F0FDF4' : '#FEF2F2')
const getColor2 = (item: LedgerEntrySummary) => (isCredit(item) ? '#22C55E' : '#F44336')
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>