Vue 3 + Vite dashboard with PrimeVue, i18n, and API integration for institution management.
177 lines
7.7 KiB
Vue
177 lines
7.7 KiB
Vue
<!-- /src/views/payments/PaymentDetailView.vue -->
|
||
<template>
|
||
<div class="payment-detail-view" v-if="payment">
|
||
<PageHeader :title="$t('payments.paymentDetail')" :subtitle="`کد صورتحساب: ${payment._id || payment.id}`">
|
||
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/payments')" />
|
||
</PageHeader>
|
||
|
||
<div class="grid mb-4">
|
||
<!-- Summary Card -->
|
||
<div class="col-12 lg:col-4">
|
||
<div class="surface-card p-4 border-round border-1 border-color shadow-sm mb-4">
|
||
<h3 class="text-lg font-bold mb-3">خلاصه صورتحساب</h3>
|
||
<div class="flex flex-column gap-3">
|
||
<div>
|
||
<span class="text-muted text-xs block mb-1">نام کاربر / دانشجو</span>
|
||
<span class="font-bold text-color text-base">{{ payment.user?.name || payment.userName || 'کاربر' }} {{ payment.user?.surname || '' }}</span>
|
||
</div>
|
||
<div v-if="payment.classes && payment.classes.length">
|
||
<span class="text-muted text-xs block mb-1">کلاسهای مربوطه</span>
|
||
<div class="flex flex-wrap gap-1 mt-1">
|
||
<Tag v-for="c in payment.classes" :key="c._id || c" :value="c.name || 'کلاس'" severity="info" class="text-xs" />
|
||
</div>
|
||
</div>
|
||
<div v-else-if="payment.course">
|
||
<span class="text-muted text-xs block mb-1">دوره آموزشی</span>
|
||
<span class="font-semibold text-color text-sm">{{ payment.course?.title }}</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-muted text-xs block mb-1">مبلغ کل صورتحساب</span>
|
||
<span class="font-bold text-color text-xl">{{ toPersianDigits(payment.amount?.toLocaleString()) }} تومان</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-muted text-xs block mb-1">مبلغ کل دریافتی</span>
|
||
<span class="font-bold text-green-600 text-lg">{{ toPersianDigits((payment.paidAmount || 0).toLocaleString()) }} تومان</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-muted text-xs block mb-1">باقیمانده</span>
|
||
<span class="font-bold text-red-500 text-lg">{{ toPersianDigits((payment.amount - (payment.paidAmount || 0)).toLocaleString()) }} تومان</span>
|
||
</div>
|
||
<div>
|
||
<span class="text-muted text-xs block mb-1">وضعیت پرداخت</span>
|
||
<StatusTag :status="payment.status" type="payment" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Transaction History & Form -->
|
||
<div class="col-12 lg:col-8">
|
||
<div class="surface-card p-4 border-round border-1 border-color shadow-sm mb-4">
|
||
<div class="flex align-items-center justify-content-between mb-3">
|
||
<h3 class="text-lg font-bold m-0">تاریخچه تراکنشهای دریافتی</h3>
|
||
<PermissionGate permission="payments:update">
|
||
<Button label="ثبت تراکنش جدید" icon="pi pi-plus" size="small" severity="success" @click="showTransactionModal = true" />
|
||
</PermissionGate>
|
||
</div>
|
||
|
||
<DataTable :value="payment.transactions || []" class="p-datatable-sm text-sm">
|
||
<Column field="amount" header="مبلغ تراکنش">
|
||
<template #body="{ data }">
|
||
{{ toPersianDigits(data.amount?.toLocaleString()) }} تومان
|
||
</template>
|
||
</Column>
|
||
<Column field="method" header="روش پرداخت">
|
||
<template #body="{ data }">
|
||
<Tag :value="data.method === 'online' ? 'درگاه آنلاین' : (data.method === 'card' ? 'کارت به کارت' : 'نقدی')" severity="info" />
|
||
</template>
|
||
</Column>
|
||
<Column field="receiptNumber" header="شماره فیش / پیگیری">
|
||
<template #body="{ data }">
|
||
<span dir="ltr">{{ toPersianDigits(data.receiptNumber || '-') }}</span>
|
||
</template>
|
||
</Column>
|
||
<Column field="createdAt" header="تاریخ ثبت">
|
||
<template #body="{ data }">
|
||
{{ formatJalali(data.createdAt || data.date) }}
|
||
</template>
|
||
</Column>
|
||
</DataTable>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Record New Transaction Modal -->
|
||
<Dialog v-model:visible="showTransactionModal" header="ثبت تراکنش جدید" modal :style="{ width: '450px' }">
|
||
<div class="flex flex-column gap-3 py-2">
|
||
<div class="flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">مبلغ واریزی (تومان) *</label>
|
||
<InputNumber v-model="trxForm.amount" class="w-full text-sm" suffix=" تومان" />
|
||
</div>
|
||
<div class="flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">روش پرداخت *</label>
|
||
<Dropdown v-model="trxForm.method" :options="methodOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
|
||
</div>
|
||
<div class="flex flex-column gap-2">
|
||
<label class="font-semibold text-sm">شماره فیش / پیگیری *</label>
|
||
<InputText v-model.trim="trxForm.receiptNumber" class="w-full text-sm" dir="ltr" />
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<Button label="انصراف" text severity="secondary" @click="showTransactionModal = false" />
|
||
<Button label="ثبت تراکنش" icon="pi pi-check" severity="success" :loading="isSubmittingTrx" @click="handleRecordTransaction" />
|
||
</template>
|
||
</Dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted } from 'vue';
|
||
import { useRoute } from 'vue-router';
|
||
import { paymentApi } from '@/api/paymentApi';
|
||
import { usePersianDate } from '@/composables/usePersianDate';
|
||
import { useToast } from '@/composables/useToast';
|
||
import PageHeader from '@/components/common/PageHeader.vue';
|
||
import PermissionGate from '@/components/common/PermissionGate.vue';
|
||
import StatusTag from '@/components/common/StatusTag.vue';
|
||
import Button from 'primevue/button';
|
||
import Tag from 'primevue/tag';
|
||
import DataTable from 'primevue/datatable';
|
||
import Column from 'primevue/column';
|
||
import Dialog from 'primevue/dialog';
|
||
import Dropdown from 'primevue/select';
|
||
import InputText from 'primevue/inputtext';
|
||
import InputNumber from 'primevue/inputnumber';
|
||
|
||
const route = useRoute();
|
||
const paymentId = route.params.id;
|
||
const { toPersianDigits, formatJalali } = usePersianDate();
|
||
const { showSuccess, showError } = useToast();
|
||
|
||
const payment = ref(null);
|
||
const showTransactionModal = ref(false);
|
||
const isSubmittingTrx = ref(false);
|
||
|
||
const methodOptions = [
|
||
{ label: 'کارت به کارت', value: 'card' },
|
||
{ label: 'درگاه آنلاین', value: 'online' },
|
||
{ label: 'نقدی / شبا', value: 'cash' }
|
||
];
|
||
|
||
const trxForm = reactive({
|
||
amount: 500000,
|
||
method: 'card',
|
||
receiptNumber: ''
|
||
});
|
||
|
||
const fetchPayment = async () => {
|
||
try {
|
||
const res = await paymentApi.getOne(paymentId);
|
||
payment.value = res.data || res;
|
||
} catch (err) {
|
||
showError(err);
|
||
}
|
||
};
|
||
|
||
const handleRecordTransaction = async () => {
|
||
if (!trxForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
|
||
if (!trxForm.receiptNumber) { showError('لطفا شماره فیش یا پیگیری را وارد کنید'); return; }
|
||
|
||
isSubmittingTrx.value = true;
|
||
try {
|
||
await paymentApi.recordTransaction(paymentId, trxForm);
|
||
showSuccess('تراکنش جدید با موفقیت ثبت شد');
|
||
showTransactionModal.value = false;
|
||
fetchPayment();
|
||
} catch (err) {
|
||
showError(err);
|
||
} finally {
|
||
isSubmittingTrx.value = false;
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
fetchPayment();
|
||
});
|
||
</script>
|