feat: add withdraw request functionality and UI components
This commit is contained in:
@@ -1,18 +1,24 @@
|
||||
<script setup>
|
||||
import { Link } from '@inertiajs/inertia-vue3'
|
||||
import ActivityCard from './ActivityCard.vue'
|
||||
import { formatPrice } from '../../utils/numberUtils'
|
||||
|
||||
const props = defineProps({
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="grid grid-cols-4 max-lg:grid-cols-2 max-sm:grid-cols-1 gap-4">
|
||||
<ActivityCard title="کیس در انتظار بررسی"
|
||||
:count="5" link="/dashboard/doctor/cases" label-link="مشاهده و بررسی" />
|
||||
:count="stats.pending_cases" link="/dashboard/cases" label-link="مشاهده و بررسی" />
|
||||
<ActivityCard title="کیس بررسی شده در ماه"
|
||||
:count="999" link="/dashboard/doctor/cases" label-link="مشاهده گزارشات کیسها" />
|
||||
<ActivityCard title="تومان درآمد"
|
||||
:count="formatPrice(50000000)" link="/dashboard/doctor/cases" label-link="مشاهده گزارشات مالی " />
|
||||
<ActivityCard title="ساعت تا شیت بعدی"
|
||||
:count="4" link="/dashboard/doctor/cases" label-link="مشاهده شیفتها" />
|
||||
:count="stats.monthly_reviewed" link="/dashboard/reports" label-link="مشاهده گزارشات کیسها" />
|
||||
<ActivityCard :title="stats.is_doctor ? 'تومان درآمد' : 'تومان هزینه در ماه'"
|
||||
:count="formatPrice(stats.monthly_amount)" :link="stats.is_doctor ? '/dashboard/wallet' : '/dashboard/invoices'" label-link="مشاهده گزارشات مالی" />
|
||||
<ActivityCard v-if="stats.next_shift_hours !== null" title="ساعت تا شیفت بعدی"
|
||||
:count="stats.next_shift_hours" link="/dashboard/shifts" label-link="مشاهده شیفتها" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import { usePage } from '@inertiajs/inertia-vue3';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import UiButton from '../ui/UiButton.vue';
|
||||
import UiInput from '../ui/UiInput.vue';
|
||||
import { useGlobalStore } from '../../store/Global';
|
||||
|
||||
const visible = defineModel('visible');
|
||||
|
||||
const globalStore = useGlobalStore();
|
||||
const { props } = usePage();
|
||||
|
||||
const form = ref({
|
||||
fullname: '',
|
||||
birthdate: '',
|
||||
national_code: '',
|
||||
email: '',
|
||||
province: '',
|
||||
city: '',
|
||||
address: '',
|
||||
});
|
||||
|
||||
const avatarFile = ref(null);
|
||||
const avatarPreview = ref('');
|
||||
const avatarInput = ref(null);
|
||||
const errors = ref({});
|
||||
const submitting = ref(false);
|
||||
|
||||
const avatarUrl = computed(() => avatarPreview.value || globalStore.current_user.profile_url || '');
|
||||
|
||||
watch(visible, (isOpen) => {
|
||||
if (!isOpen) return;
|
||||
const user = props.value.auth?.User || {};
|
||||
form.value = {
|
||||
fullname: user.fullname || '',
|
||||
birthdate: user.birthdate || '',
|
||||
national_code: user.national_code || '',
|
||||
email: user.email || '',
|
||||
province: user.province || '',
|
||||
city: user.city || '',
|
||||
address: user.address || '',
|
||||
};
|
||||
avatarFile.value = null;
|
||||
avatarPreview.value = '';
|
||||
errors.value = {};
|
||||
});
|
||||
|
||||
const errorFor = (field) => {
|
||||
const error = errors.value[field];
|
||||
return Array.isArray(error) ? error[0] : (error || '');
|
||||
};
|
||||
|
||||
const openAvatarPicker = () => {
|
||||
avatarInput.value?.click();
|
||||
};
|
||||
|
||||
const handleAvatarChange = (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
avatarFile.value = file;
|
||||
avatarPreview.value = URL.createObjectURL(file);
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
submitting.value = true;
|
||||
Inertia.post('/dashboard/profile', {
|
||||
...form.value,
|
||||
avatar: avatarFile.value,
|
||||
}, {
|
||||
forceFormData: true,
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
globalStore.current_user = props.value.auth.User;
|
||||
visible.value = false;
|
||||
},
|
||||
onError: (formErrors) => {
|
||||
errors.value = formErrors;
|
||||
},
|
||||
onFinish: () => {
|
||||
submitting.value = false;
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="false" :maximizable="false" :style="{ width: '688px' , minHeight: '376px' }" class="fast-request-dialog" :pt="{header : '!bg-client-primaryBlue'}">
|
||||
<template #header>
|
||||
<div class="w-full flex items-center justify-start">
|
||||
<h2 class="text-[14px] font-[700] text-[#576989]">ویرایش حساب کاربری</h2>
|
||||
</div>
|
||||
</template>
|
||||
<form class="w-full flex flex-col gap-5 border-t border-[#EBEBEB] pt-5" @submit.prevent="submit">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative">
|
||||
<span class="flex items-center justify-center w-20 h-20 rounded-full bg-[#F6F7F9] border border-[#DEE3EB] overflow-hidden">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" alt="تصویر پروفایل" class="w-full h-full object-cover" />
|
||||
<i v-else class="fi fi-rr-user flex text-2xl text-[#576989]"></i>
|
||||
</span>
|
||||
<button type="button" class="absolute -bottom-1 -left-1 flex items-center justify-center w-7 h-7 rounded-full bg-dominant text-white border-2 border-white cursor-pointer" @click="openAvatarPicker">
|
||||
<i class="fi fi-rr-camera flex text-[11px]"></i>
|
||||
</button>
|
||||
<input ref="avatarInput" type="file" accept="image/png,image/jpeg" class="hidden" @change="handleAvatarChange" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[13px] font-[700] text-[#3E4C63]">تصویر پروفایل</span>
|
||||
<span class="text-[11px] font-[400] text-[#576989]">فرمتهای مجاز: png ،jpg — حداکثر ۱۰ مگابایت</span>
|
||||
<span v-if="errorFor('avatar')" class="text-[11px] text-[#7A1419]">{{ errorFor('avatar') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 max-sm:grid-cols-1 gap-4">
|
||||
<UiInput v-model="form.fullname" label="نام و نام خانوادگی" placeholder="نام و نام خانوادگی خود را وارد کنید" :error="errorFor('fullname')" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ui-input-label">تاریخ تولد</label>
|
||||
<CustomDatePicker v-model="form.birthdate" type="date" format="YYYY-MM-DD" display-format="jYYYY/jMM/jDD" class="w-full" />
|
||||
<span v-if="errorFor('birthdate')" class="text-[11px] text-[#7A1419]">{{ errorFor('birthdate') }}</span>
|
||||
</div>
|
||||
<UiInput v-model="form.national_code" label="کد ملی" placeholder="کد ملی خود را وارد کنید" convertPersianNumbers :error="errorFor('national_code')" />
|
||||
<UiInput v-model="form.email" label="ایمیل" type="email" placeholder="ایمیل خود را وارد کنید" direction="ltr" :error="errorFor('email')" />
|
||||
<UiInput v-model="form.province" label="استان" placeholder="استان محل سکونت" :error="errorFor('province')" />
|
||||
<UiInput v-model="form.city" label="شهر" placeholder="شهر محل سکونت" :error="errorFor('city')" />
|
||||
</div>
|
||||
<UiInput v-model="form.address" label="آدرس" placeholder="آدرس خود را وارد کنید" :error="errorFor('address')" />
|
||||
<div class="w-full pt-3 flex items-center justify-start gap-2 border-t border-[#EBEBEB]">
|
||||
<UiButton label="ذخیره تغییرات" severity="success" size="small" :loading="submitting" :disabled="submitting" @click="submit" />
|
||||
<UiButton label="انصراف" severity="secondary" size="small" :disabled="submitting" @click="visible = false" />
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import UiButton from '../../components/ui/UiButton.vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import ProgressBar from 'primevue/progressbar';
|
||||
import { toast } from 'vue3-toastify';
|
||||
@@ -9,11 +9,15 @@ import RequestCase from './fastRequest/RequestCase.vue';
|
||||
import RequestDetails from './fastRequest/RequestDetails.vue';
|
||||
import RequestDoctor from './fastRequest/RequestDoctor.vue';
|
||||
import RequestReview from './fastRequest/RequestReview.vue';
|
||||
import InvoiceModal from './fastRequest/InvoiceModal.vue';
|
||||
import { useFastRequestStore } from '../../store/FastRequest';
|
||||
|
||||
const visible = defineModel('visible');
|
||||
const fastRequestStore = useFastRequestStore();
|
||||
|
||||
const invoiceVisible = ref(false);
|
||||
const invoiceDocument = ref(null);
|
||||
|
||||
const steps = [
|
||||
{ label: 'نوع درخواست', component: RequestType },
|
||||
{ label: 'انتخاب کیس', component: RequestCase },
|
||||
@@ -49,8 +53,9 @@ const handleNext = async () => {
|
||||
|
||||
try {
|
||||
const document = await fastRequestStore.submit();
|
||||
toast.success(`درخواست شما با شماره پرونده ${document.code} با موفقیت ثبت شد.`);
|
||||
invoiceDocument.value = document;
|
||||
visible.value = false;
|
||||
invoiceVisible.value = true;
|
||||
} catch (error) {
|
||||
toast.error(error.response?.data?.message || 'خطا در ثبت درخواست. لطفا دوباره تلاش کنید.');
|
||||
}
|
||||
@@ -93,4 +98,5 @@ const handleNext = async () => {
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
<invoice-modal v-model:visible="invoiceVisible" :document="invoiceDocument"></invoice-modal>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import UiButton from '../ui/UiButton.vue';
|
||||
import PaymentSuccessSvg from '../svg/paymentSuccess.svg';
|
||||
import PaymentFailedSvg from '../svg/paymentFailed.svg';
|
||||
|
||||
const visible = defineModel('visible');
|
||||
|
||||
const props = defineProps({
|
||||
result: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const isPaid = computed(() => props.result?.status === 'paid');
|
||||
|
||||
const isWalletTopUp = computed(() => props.result?.purpose === 'WalletTopUp');
|
||||
|
||||
const closeModal = () => {
|
||||
visible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="false" :maximizable="false" :style="{ width: '688px' , minHeight: '376px' }" class="fast-request-dialog">
|
||||
<div v-if="result" class="w-full flex flex-col items-center justify-center gap-2 py-6">
|
||||
<component :is="isPaid ? PaymentSuccessSvg : PaymentFailedSvg" class="w-[180px] h-[150px]" />
|
||||
<h2 class="text-[15px] font-[700] text-[#3E4C63] mt-2">
|
||||
<template v-if="isPaid">{{ isWalletTopUp ? 'کیف پول شما با موفقیت شارژ شد.' : 'پرداخت شما با موفقیت انجام شد.' }}</template>
|
||||
<template v-else>متاسفانه پرداخت شما با خطا روبهرو شده است.</template>
|
||||
</h2>
|
||||
<p class="max-w-[480px] text-center text-[13px] font-[400] text-[#576989] leading-7">
|
||||
<template v-if="isPaid && isWalletTopUp">مبلغ پرداختی به موجودی کیف پول شما اضافه شد و میتوانید از آن برای پرداخت فاکتورها استفاده کنید.</template>
|
||||
<template v-else-if="isPaid">کارشناسان پرداخت و درخواست شما را به زودی بررسی خواهند کرد.</template>
|
||||
<template v-else>لطفا دوباره نسبت به پرداخت اقدام نمایید. در صورتیکه مبلغ از حساب شما کسر شده و یا فکر میکنید اشتباهی رخ داده است، با کارشناسان ما در تماس باشید.</template>
|
||||
</p>
|
||||
<p class="text-[13px] font-[700] text-[#3E4C63] mt-2">شماره پیگیری: {{ result.track_id }}</p>
|
||||
<div class="flex items-center justify-center gap-2 mt-4">
|
||||
<ui-button label="بازگشت به داشبورد" severity="success" size="small" @click="closeModal" />
|
||||
<ui-button v-if="!isPaid" label="تماس با پشتیبانی" severity="secondary" size="small" @click="closeModal" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
rows: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white border border-complement-50 rounded-2xl p-4">
|
||||
<h2 class="flex items-center gap-2 text-[12px] font-bold text-[#3E4C63]">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-[#7A1419]"></span>
|
||||
{{ title }}
|
||||
</h2>
|
||||
<ul class="mt-3 flex flex-col gap-2">
|
||||
<li v-for="row in rows" :key="row.label" class="flex items-start gap-2 text-[11px]">
|
||||
<span class="font-normal text-[#576989]">{{ row.label }}:</span>
|
||||
<span class="font-semibold text-[#3E4C63]">{{ row.value ?? '-' }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
const value = defineModel({ type: String, default: '' })
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="bg-white border border-complement-50 rounded-2xl p-4 flex flex-col gap-3">
|
||||
<h2 class="flex items-center gap-2 text-[12px] font-bold text-[#3E4C63]">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-[#7A1419]"></span>
|
||||
{{ title }}
|
||||
</h2>
|
||||
<textarea
|
||||
v-if="editable"
|
||||
v-model="value"
|
||||
rows="6"
|
||||
:placeholder="placeholder"
|
||||
class="w-full bg-[#F8FAFC] border border-[#DEE3EB] rounded-xl p-4 text-[12px] font-normal text-[#3E4C63] leading-6 outline-none focus:border-[#C1D0E8] resize-y"
|
||||
></textarea>
|
||||
<div v-else class="w-full min-h-[7rem] bg-[#F8FAFC] border border-[#DEE3EB] rounded-xl p-4 text-[12px] font-normal text-[#3E4C63] leading-6 whitespace-pre-line">
|
||||
{{ value || 'موردی ثبت نشده است.' }}
|
||||
</div>
|
||||
<p v-if="error" class="text-[11px] text-[#7A1419]">{{ error }}</p>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,8 +1,12 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import { usePage } from '@inertiajs/inertia-vue3';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import UiButton from '../../ui/UiButton.vue';
|
||||
import { toast } from 'vue3-toastify';
|
||||
import { modalityTitle } from '../../../data/fastRequestOptions';
|
||||
import { formatPrice } from '../../../utils/numberUtils';
|
||||
|
||||
const visible = defineModel('visible');
|
||||
|
||||
@@ -10,31 +14,69 @@ const props = defineProps({
|
||||
document: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
invoice: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const formattedAmount = computed(() => {
|
||||
return Number(props.document?.invoice?.amount || 0).toLocaleString('fa-IR');
|
||||
});
|
||||
const { props: pageProps } = usePage();
|
||||
|
||||
const handlePay = () => {
|
||||
visible.value = false;
|
||||
const paying = ref(false);
|
||||
const payingWithWallet = ref(false);
|
||||
|
||||
const currentInvoice = computed(() => props.invoice ?? props.document?.invoice ?? null);
|
||||
|
||||
const formattedAmount = computed(() => formatPrice(currentInvoice.value?.amount || 0));
|
||||
|
||||
const walletBalance = computed(() => Number(pageProps.value.auth?.wallet_balance || 0));
|
||||
|
||||
const formattedWalletBalance = computed(() => formatPrice(walletBalance.value));
|
||||
|
||||
const hasEnoughBalance = computed(() => walletBalance.value >= Number(currentInvoice.value?.amount || 0));
|
||||
|
||||
const handlePay = async () => {
|
||||
paying.value = true;
|
||||
try {
|
||||
const { data } = await window.axios.post('/dashboard/payments', { invoice_id: currentInvoice.value.id });
|
||||
window.location.href = data.url;
|
||||
} catch (error) {
|
||||
paying.value = false;
|
||||
toast.error(error.response?.data?.message || 'خطا در اتصال به درگاه پرداخت. لطفا دوباره تلاش کنید.');
|
||||
}
|
||||
}
|
||||
|
||||
const handleWalletPay = async () => {
|
||||
payingWithWallet.value = true;
|
||||
try {
|
||||
await window.axios.post(`/dashboard/invoices/${currentInvoice.value.id}/pay-wallet`);
|
||||
toast.success('فاکتور با موفقیت از کیف پول پرداخت شد.');
|
||||
visible.value = false;
|
||||
Inertia.reload();
|
||||
} catch (error) {
|
||||
toast.error(error.response?.data?.message || 'خطا در پرداخت از کیف پول. لطفا دوباره تلاش کنید.');
|
||||
} finally {
|
||||
payingWithWallet.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="false" :maximizable="false" :style="{ width: '1032px' }">
|
||||
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="false" :maximizable="false" :style="{ width: '688px' , minHeight: '376px' }" class="fast-request-dialog" :pt="{header : '!bg-client-primaryBlue'}">
|
||||
<template #header>
|
||||
<h2 class="text-[14px] font-[700] text-[#576989]">فاکتور پرداخت «{{ document?.invoice?.number }}»</h2>
|
||||
<div class="w-full flex items-center justify-start">
|
||||
<h2 class="text-[14px] font-[700] text-[#576989]">فاکتور پرداخت «{{ currentInvoice?.number }}»</h2>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="document" class="w-full flex flex-col gap-4 border-t border-[#EBEBEB] pt-4">
|
||||
<div class="w-full flex items-center gap-3 bg-[#EEF2F8] border border-[#DEE3EB] rounded-[12px] px-4 py-4">
|
||||
<i class="fi-rr-info text-[20px] text-[#576989]"></i>
|
||||
<p class="text-[13px] font-[400] text-[#3E4C63]">
|
||||
<div v-if="currentInvoice" class="w-full flex flex-col gap-4 border-t border-[#EBEBEB] pt-4">
|
||||
<div class="w-full flex items-center gap-3 bg-[#EEF2F8] border border-[#DEE3EB] rounded-[12px] px-4 py-3">
|
||||
<i class="fi-rr-info text-[18px] text-[#576989]"></i>
|
||||
<p class="text-[12px] font-[400] text-[#3E4C63] leading-6">
|
||||
یک فاکتور به مبلغ « <span class="font-[700]">{{ formattedAmount }} تومان</span> » برای شما ایجاد شد. لطفا جهت ادامه مراحل درخواست نسبت به پرداخت اقدام نمایید.
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-[13px] font-[400] text-[#3E4C63] leading-7">
|
||||
<p v-if="document" class="text-[13px] font-[400] text-[#3E4C63] leading-7">
|
||||
این فاکتور جهت پردازش درخواست
|
||||
<span class="font-[600] text-[#7A1419]">(({{ modalityTitle(document.modality) }}))</span>
|
||||
به شناسه
|
||||
@@ -48,9 +90,14 @@ const handlePay = () => {
|
||||
<br>
|
||||
بدیهیست که در صورت عدم تایید کارشناسان مبلغ مربوطه به کیف پول شما بازگردانده خواهد شد.
|
||||
</p>
|
||||
<p class="text-[12px] font-[400] text-[#576989]">
|
||||
موجودی کیف پول شما: <span class="font-[700] text-[#3E4C63]">{{ formattedWalletBalance }} تومان</span>
|
||||
<span v-if="!hasEnoughBalance" class="text-[#7A1419]"> (موجودی کیف پول کافی نیست)</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="w-full pt-3 flex items-center justify-start border-t border-[#EBEBEB]">
|
||||
<ui-button label="تایید و پرداخت" severity="success" size="small" @click="handlePay" />
|
||||
<div class="w-full pt-3 flex items-center justify-start gap-2 border-t border-[#EBEBEB]">
|
||||
<ui-button label="پرداخت با کیف پول" severity="primary" size="small" :loading="payingWithWallet" :disabled="payingWithWallet || paying || !hasEnoughBalance" @click="handleWalletPay" />
|
||||
<ui-button label="پرداخت از طریق درگاه" severity="success" size="small" :loading="paying" :disabled="paying || payingWithWallet" @click="handlePay" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useForm, usePage } from '@inertiajs/inertia-vue3';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import { toast } from 'vue3-toastify';
|
||||
import UiCard from '../../ui/UiCard.vue';
|
||||
import UiInput from '../../ui/UiInput.vue';
|
||||
import UiButton from '../../ui/UiButton.vue';
|
||||
import { convertEnglishToPersian, convertPersianToEnglish, formatPrice } from '../../../utils/numberUtils';
|
||||
|
||||
const props = defineProps({
|
||||
bankCards: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const { props: pageProps } = usePage();
|
||||
|
||||
const walletBalance = computed(() => Number(pageProps.value.auth?.wallet_balance || 0));
|
||||
|
||||
const amount = ref('');
|
||||
const selectedCardId = ref(props.bankCards[0]?.id ?? null);
|
||||
|
||||
const numericAmount = computed(() => Number(convertPersianToEnglish(amount.value).replace(/\D/g, '')) || 0);
|
||||
|
||||
const formatCardNumber = (cardNumber) => convertEnglishToPersian(cardNumber.replace(/(\d{4})(?=\d)/g, '$1 - '));
|
||||
|
||||
const selectFullBalance = () => {
|
||||
amount.value = String(walletBalance.value);
|
||||
};
|
||||
|
||||
const withdrawForm = useForm({
|
||||
amount: null,
|
||||
bank_card_id: null,
|
||||
});
|
||||
|
||||
const submitWithdraw = () => {
|
||||
if (numericAmount.value < 10000) {
|
||||
toast.error('حداقل مبلغ برداشت ۱۰٬۰۰۰ تومان است.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedCardId.value) {
|
||||
toast.error('لطفا کارت بانکی مقصد را انتخاب کنید.');
|
||||
return;
|
||||
}
|
||||
|
||||
withdrawForm.amount = numericAmount.value;
|
||||
withdrawForm.bank_card_id = selectedCardId.value;
|
||||
|
||||
withdrawForm.post('/dashboard/wallet/withdraw-requests', {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
amount.value = '';
|
||||
toast.success('درخواست برداشت شما ثبت شد و در انتظار بررسی است.');
|
||||
},
|
||||
onError: (errors) => toast.error(Object.values(errors)[0] || 'خطا در ثبت درخواست برداشت.'),
|
||||
});
|
||||
};
|
||||
|
||||
const cardModalVisible = ref(false);
|
||||
|
||||
const cardForm = useForm({
|
||||
card_number: '',
|
||||
holder_name: '',
|
||||
});
|
||||
|
||||
const submitCard = () => {
|
||||
cardForm.card_number = convertPersianToEnglish(cardForm.card_number).replace(/\D/g, '');
|
||||
|
||||
cardForm.post('/dashboard/wallet/bank-cards', {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
cardForm.reset();
|
||||
cardModalVisible.value = false;
|
||||
toast.success('کارت بانکی با موفقیت ثبت شد.');
|
||||
},
|
||||
onError: (errors) => toast.error(Object.values(errors)[0] || 'خطا در ثبت کارت بانکی.'),
|
||||
});
|
||||
};
|
||||
|
||||
const deleteCard = (card) => {
|
||||
Inertia.delete(`/dashboard/wallet/bank-cards/${card.id}`, {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
if (selectedCardId.value === card.id) {
|
||||
selectedCardId.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiCard title="درخواست برداشت" variant="default">
|
||||
<div class="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-start">
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="text-[11px] font-normal text-[#576989] leading-6">
|
||||
مبلغ مورد نظر پس از بررسی و تایید توسط کارشناسان، از کیف پول شما کسر و به کارت انتخابی واریز میشود.
|
||||
</p>
|
||||
<div class="flex items-end gap-3 max-sm:flex-col max-sm:items-stretch">
|
||||
<UiInput
|
||||
v-model="amount"
|
||||
label="مبلغ برداشت"
|
||||
placeholder="مبلغ مورد نظر را به تومان وارد کنید"
|
||||
prefixEnd="تومان"
|
||||
prefixClasses="!border-dominant-50"
|
||||
class="!w-full"
|
||||
customClass="!border-dominant-50 !font-normal !text-[12px]"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="text-[11px] font-normal text-[#576989] bg-[#F5F7FA] border border-[#DEE3EB] rounded-lg px-3 py-1.5 hover:bg-[#EEF2F8]"
|
||||
@click="selectFullBalance"
|
||||
>
|
||||
کل موجودی ({{ formatPrice(walletBalance) }} تومان)
|
||||
</button>
|
||||
</div>
|
||||
<UiButton
|
||||
label="ثبت درخواست برداشت"
|
||||
severity="success"
|
||||
size="small"
|
||||
class="self-start"
|
||||
:loading="withdrawForm.processing"
|
||||
:disabled="withdrawForm.processing"
|
||||
@click="submitWithdraw"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[11px] font-semibold text-[#3E4C63]">کارت بانکی مقصد</span>
|
||||
<button type="button" class="text-[11px] font-semibold text-[#365891]" @click="cardModalVisible = true">
|
||||
+ افزودن کارت
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="!bankCards.length" class="bg-[#F8FAFC] border border-dashed border-[#C1D0E8] rounded-xl p-4 text-[11px] font-normal text-[#576989] text-center leading-6">
|
||||
هنوز کارتی ثبت نکردهاید. برای ثبت درخواست برداشت ابتدا یک کارت بانکی اضافه کنید.
|
||||
</p>
|
||||
<label
|
||||
v-for="card in bankCards"
|
||||
:key="card.id"
|
||||
class="flex items-center gap-3 border rounded-xl px-4 py-3 cursor-pointer transition-colors"
|
||||
:class="selectedCardId === card.id ? 'border-[#365891] bg-[#EEF2F8]' : 'border-[#DEE3EB] bg-white hover:bg-[#F8FAFC]'"
|
||||
>
|
||||
<input
|
||||
v-model="selectedCardId"
|
||||
type="radio"
|
||||
name="bank_card"
|
||||
:value="card.id"
|
||||
class="accent-[#365891]"
|
||||
/>
|
||||
<span class="flex flex-col gap-1 flex-1">
|
||||
<span class="text-[12px] font-semibold text-[#3E4C63]" dir="ltr">{{ formatCardNumber(card.card_number) }}</span>
|
||||
<span class="text-[11px] font-normal text-[#576989]">{{ card.holder_name }}</span>
|
||||
</span>
|
||||
<button type="button" class="text-[11px] font-semibold text-[#7A1419]" @click.prevent="deleteCard(card)">حذف</button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="cardModalVisible" :modal="true" :closable="true" :dismissableMask="false" :style="{ width: '420px' }" class="fast-request-dialog" :pt="{ header: '!bg-client-primaryBlue' }">
|
||||
<template #header>
|
||||
<div class="w-full flex items-center justify-start">
|
||||
<h2 class="text-[14px] font-[700] text-[#576989]">افزودن کارت بانکی</h2>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full flex flex-col gap-4 border-t border-[#EBEBEB] pt-4">
|
||||
<UiInput
|
||||
v-model="cardForm.card_number"
|
||||
label="شماره کارت"
|
||||
placeholder="شماره کارت ۱۶ رقمی"
|
||||
maxlength="16"
|
||||
customClass="!border-dominant-50 !font-normal !text-[12px]"
|
||||
/>
|
||||
<UiInput
|
||||
v-model="cardForm.holder_name"
|
||||
label="نام صاحب حساب"
|
||||
placeholder="نام و نام خانوادگی صاحب کارت"
|
||||
customClass="!border-dominant-50 !font-normal !text-[12px]"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full pt-4 flex items-center justify-start gap-2 border-t border-[#EBEBEB] mt-4">
|
||||
<UiButton label="ثبت کارت" severity="success" size="small" :loading="cardForm.processing" :disabled="cardForm.processing" @click="submitCard" />
|
||||
<UiButton label="انصراف" severity="secondary" size="small" :disabled="cardForm.processing" @click="cardModalVisible = false" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</UiCard>
|
||||
</template>
|
||||
Reference in New Issue
Block a user