feat: add admin transaction edit UI on payment detail page

Wire create and update flows to admin transaction endpoints with status, amount, and date fields.
This commit is contained in:
2026-08-16 08:01:37 +03:30
parent 45a1c94774
commit 9496587128
2 changed files with 160 additions and 8 deletions
+2 -1
View File
@@ -8,5 +8,6 @@ export const paymentApi = {
create: (data) => axiosInstance.post('/payments/admin/create', data),
update: (id, data) => axiosInstance.put(`/payments/admin/update/${id}`, data),
delete: (id) => axiosInstance.delete(`/payments/admin/delete/${id}`),
recordTransaction: (paymentId, data) => axiosInstance.post(`/payments/user/pay/${paymentId}`, data)
recordTransaction: (paymentId, data) => axiosInstance.post(`/payments/admin/transactions/${paymentId}`, data),
updateTransaction: (transactionId, data) => axiosInstance.put(`/payments/admin/transactions/${transactionId}`, data)
};
+158 -7
View File
@@ -108,6 +108,13 @@
<span class="white-space-pre-wrap">{{ data.notes || '—' }}</span>
</template>
</Column>
<Column header="عملیات" style="width: 5rem">
<template #body="{ data }">
<PermissionGate permission="payments:update">
<Button icon="pi pi-pencil" text rounded severity="secondary" size="small" @click="openEditTransaction(data)" />
</PermissionGate>
</template>
</Column>
</DataTable>
</div>
</div>
@@ -118,14 +125,26 @@
<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=" تومان" />
<InputNumber v-model="trxForm.amount" class="w-full text-sm" suffix=" تومان" :min="1" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm">وضعیت تراکنش *</label>
<Dropdown v-model="trxForm.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
</div>
<div v-if="trxForm.status === 'paid'" 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>
<label class="font-semibold text-sm">تاریخ سررسید *</label>
<DatePicker v-model="trxForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
</div>
<div v-if="trxForm.status === 'paid'" class="flex flex-column gap-2">
<label class="font-semibold text-sm">تاریخ پرداخت</label>
<DatePicker v-model="trxForm.date" class="w-full text-sm" placeholder="1403/01/01" />
</div>
<div v-if="trxForm.status === 'paid'" 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 class="flex flex-column gap-2">
@@ -145,6 +164,51 @@
<Button label="ثبت تراکنش" icon="pi pi-check" severity="success" :loading="isSubmittingTrx" @click="handleRecordTransaction" />
</template>
</Dialog>
<!-- Edit Transaction Modal -->
<Dialog v-model:visible="showEditModal" 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="editForm.amount" class="w-full text-sm" suffix=" تومان" :min="0" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm">وضعیت تراکنش *</label>
<Dropdown v-model="editForm.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
</div>
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
<label class="font-semibold text-sm">روش پرداخت</label>
<Dropdown v-model="editForm.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>
<DatePicker v-model="editForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
</div>
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
<label class="font-semibold text-sm">تاریخ پرداخت</label>
<DatePicker v-model="editForm.date" class="w-full text-sm" placeholder="1403/01/01" />
</div>
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
<label class="font-semibold text-sm">شماره فیش / پیگیری</label>
<InputText v-model.trim="editForm.receiptNumber" class="w-full text-sm" dir="ltr" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm" for="edit-trx-notes">یادداشت</label>
<Textarea
id="edit-trx-notes"
v-model="editForm.notes"
rows="3"
class="w-full text-sm"
maxlength="5000"
placeholder="یادداشت داخلی درباره این تراکنش…"
/>
</div>
</div>
<template #footer>
<Button label="انصراف" text severity="secondary" @click="showEditModal = false" />
<Button label="ذخیره تغییرات" icon="pi pi-check" severity="success" :loading="isSubmittingEdit" @click="handleUpdateTransaction" />
</template>
</Dialog>
</div>
</template>
@@ -167,6 +231,7 @@ import Dropdown from 'primevue/select';
import InputText from 'primevue/inputtext';
import InputNumber from 'primevue/inputnumber';
import Textarea from 'primevue/textarea';
import DatePicker from 'vue3-persian-datetime-picker';
const route = useRoute();
const paymentId = route.params.id;
@@ -175,7 +240,10 @@ const { showSuccess, showError } = useToast();
const payment = ref(null);
const showTransactionModal = ref(false);
const showEditModal = ref(false);
const isSubmittingTrx = ref(false);
const isSubmittingEdit = ref(false);
const editingTransactionId = ref(null);
const methodOptions = [
{ label: 'کارت به کارت', value: 'card' },
@@ -183,10 +251,30 @@ const methodOptions = [
{ label: 'نقدی / شبا', value: 'cash' }
];
const trxForm = reactive({
const statusOptions = [
{ label: 'پرداخت شده', value: 'paid' },
{ label: 'در انتظار پرداخت', value: 'pending' }
];
const defaultTrxForm = () => ({
amount: 500000,
status: 'paid',
method: 'card',
receiptNumber: '',
dueDate: '',
date: '',
notes: ''
});
const trxForm = reactive(defaultTrxForm());
const editForm = reactive({
amount: 0,
status: 'paid',
method: 'card',
receiptNumber: '',
dueDate: '',
date: '',
notes: ''
});
@@ -204,15 +292,31 @@ const fetchPayment = async () => {
const handleRecordTransaction = async () => {
if (!trxForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
if (!trxForm.receiptNumber) { showError('لطفا شماره فیش یا پیگیری را وارد کنید'); return; }
if (!trxForm.dueDate) { showError('لطفا تاریخ سررسید را وارد کنید'); return; }
if (trxForm.status === 'paid' && !trxForm.receiptNumber) {
showError('لطفا شماره فیش یا پیگیری را وارد کنید');
return;
}
isSubmittingTrx.value = true;
try {
await paymentApi.recordTransaction(paymentId, trxForm);
const payload = {
amount: trxForm.amount,
status: trxForm.status,
dueDate: trxForm.dueDate,
notes: trxForm.notes || undefined
};
if (trxForm.status === 'paid') {
payload.method = trxForm.method;
payload.receiptNumber = trxForm.receiptNumber;
if (trxForm.date) payload.date = trxForm.date;
}
const res = await paymentApi.recordTransaction(paymentId, payload);
payment.value = res.data || res;
showSuccess('تراکنش جدید با موفقیت ثبت شد');
showTransactionModal.value = false;
trxForm.notes = '';
fetchPayment();
Object.assign(trxForm, defaultTrxForm());
} catch (err) {
showError(err);
} finally {
@@ -220,6 +324,53 @@ const handleRecordTransaction = async () => {
}
};
const openEditTransaction = (trx) => {
editingTransactionId.value = trx._id || trx.id;
editForm.amount = trx.amount || 0;
editForm.status = trx.status || (trx.date ? 'paid' : 'pending');
editForm.method = trx.method || 'card';
editForm.receiptNumber = trx.receiptNumber || '';
editForm.dueDate = trx.dueDate || '';
editForm.date = trx.date || '';
editForm.notes = trx.notes || '';
showEditModal.value = true;
};
const handleUpdateTransaction = async () => {
if (!editForm.amount && editForm.amount !== 0) {
showError('لطفا مبلغ را وارد کنید');
return;
}
if (!editForm.dueDate) {
showError('لطفا تاریخ سررسید را وارد کنید');
return;
}
isSubmittingEdit.value = true;
try {
const payload = {
amount: editForm.amount,
status: editForm.status,
dueDate: editForm.dueDate,
notes: editForm.notes
};
if (editForm.status === 'paid') {
payload.method = editForm.method;
payload.receiptNumber = editForm.receiptNumber;
payload.date = editForm.date || undefined;
}
const res = await paymentApi.updateTransaction(editingTransactionId.value, payload);
payment.value = res.data || res;
showSuccess('تراکنش با موفقیت به‌روزرسانی شد');
showEditModal.value = false;
} catch (err) {
showError(err);
} finally {
isSubmittingEdit.value = false;
}
};
onMounted(() => {
fetchPayment();
});