feat: add withdraw request functionality and UI components
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import { toast } from 'vue3-toastify';
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue';
|
||||
import UiButton from '../../../components/ui/UiButton.vue';
|
||||
import CaseSpecsCard from '../../../components/dashboard/cases/CaseSpecsCard.vue';
|
||||
import ReportSectionCard from '../../../components/dashboard/cases/ReportSectionCard.vue';
|
||||
import FileUploadBox from '../../../components/dashboard/fastRequest/FileUploadBox.vue';
|
||||
import { modalityTitle } from '../../../data/fastRequestOptions';
|
||||
import { convertEnglishToPersian } from '../../../utils/numberUtils';
|
||||
|
||||
const props = defineProps({
|
||||
case: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
canReport: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const petRows = computed(() => [
|
||||
{ label: 'نام', value: props.case.pet?.name },
|
||||
{ label: 'نوع', value: props.case.pet?.type },
|
||||
{ label: 'جنسیت', value: props.case.pet?.gender },
|
||||
{ label: 'نژاد', value: props.case.pet?.breed },
|
||||
{ label: 'سن', value: props.case.pet?.age !== null && props.case.pet?.age !== undefined ? `${convertEnglishToPersian(props.case.pet.age)} سال` : null },
|
||||
{ label: 'وزن', value: props.case.pet?.weight ? `${convertEnglishToPersian(props.case.pet.weight)} کیلو گرم` : null },
|
||||
{ label: 'وضعیت عقیمسازی', value: props.case.pet?.sterilized === null ? null : (props.case.pet?.sterilized ? 'عقیم شده' : 'عقیم نشده') },
|
||||
]);
|
||||
|
||||
const requesterRows = computed(() => {
|
||||
const requester = props.case.requester || {};
|
||||
return [
|
||||
{ label: 'نوع', value: requester.level },
|
||||
{ label: 'نام و نام خانوادگی', value: requester.fullname },
|
||||
{ label: 'شماره موبایل', value: requester.phone ? convertEnglishToPersian(requester.phone) : null },
|
||||
{ label: 'تخصص', value: requester.specialty },
|
||||
{ label: 'کلینیک', value: requester.clinic_name },
|
||||
{ label: 'تلفن کلینیک', value: requester.clinic_phone ? convertEnglishToPersian(requester.clinic_phone) : null },
|
||||
{ label: 'محل اقامت', value: [requester.province, requester.city].filter(Boolean).join('، ') || null },
|
||||
].filter((row) => row.value);
|
||||
});
|
||||
|
||||
const remainingLabel = ref(null);
|
||||
let timerId = null;
|
||||
|
||||
const updateRemaining = () => {
|
||||
if (!props.case.processing_deadline) {
|
||||
remainingLabel.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const diff = Math.floor((new Date(props.case.processing_deadline).getTime() - Date.now()) / 1000);
|
||||
|
||||
if (diff <= 0) {
|
||||
remainingLabel.value = 'اتمام فرصت رسیدگی';
|
||||
return;
|
||||
}
|
||||
|
||||
const pad = (value) => String(value).padStart(2, '0');
|
||||
remainingLabel.value = convertEnglishToPersian(`${pad(Math.floor(diff / 3600))}:${pad(Math.floor((diff % 3600) / 60))}:${pad(diff % 60)}`) + ' تا پایان زمان ثبت گزارش';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateRemaining();
|
||||
timerId = setInterval(updateRemaining, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => clearInterval(timerId));
|
||||
|
||||
const openFiles = (files) => {
|
||||
files.forEach((file) => window.open(file.url, '_blank'));
|
||||
};
|
||||
|
||||
const reportForm = useForm({
|
||||
document_id: props.case.id,
|
||||
technique: props.case.report?.technique || '',
|
||||
finding: props.case.report?.finding || '',
|
||||
diagnose: props.case.report?.diagnose || '',
|
||||
recommendation: props.case.report?.recommendation || '',
|
||||
files: [],
|
||||
});
|
||||
|
||||
const submitReport = () => {
|
||||
reportForm.post('/dashboard/reports', {
|
||||
forceFormData: true,
|
||||
onError: () => toast.error('لطفا موارد مشخصشده را بررسی و اصلاح کنید.'),
|
||||
});
|
||||
};
|
||||
|
||||
const rejectModalVisible = ref(false);
|
||||
const rejectReason = ref('');
|
||||
const rejecting = ref(false);
|
||||
|
||||
const submitReject = () => {
|
||||
if (!rejectReason.value.trim()) {
|
||||
toast.error('لطفا دلیل رد درخواست را وارد کنید.');
|
||||
return;
|
||||
}
|
||||
|
||||
rejecting.value = true;
|
||||
Inertia.post(`/dashboard/cases/${props.case.id}/reject`, { reason: rejectReason.value }, {
|
||||
onFinish: () => {
|
||||
rejecting.value = false;
|
||||
rejectModalVisible.value = false;
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<div class="flex flex-col gap-4">
|
||||
<header class="bg-white border border-complement-50 rounded-2xl px-4 py-3 flex items-center justify-between flex-wrap gap-2">
|
||||
<h1 class="text-[13px] font-bold text-[#3E4C63]">پرونده شماره {{ convertEnglishToPersian(props.case.code) }}</h1>
|
||||
<div class="flex items-center gap-3 text-[11px]">
|
||||
<span v-if="canReport && remainingLabel" class="font-semibold text-[#7A1419]">{{ remainingLabel }}</span>
|
||||
<span v-else class="font-normal text-[#576989]">وضعیت فعلی</span>
|
||||
<span class="font-semibold text-[#3E4C63] border-r border-[#DEE3EB] pr-3">{{ props.case.status }}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-[16rem_1fr] max-lg:grid-cols-1 gap-4 items-start">
|
||||
<aside class="flex flex-col gap-4">
|
||||
<CaseSpecsCard title="مشخصات کیس" :rows="petRows" />
|
||||
<CaseSpecsCard v-if="canReport && requesterRows.length" title="مشخصات درخواستدهنده" :rows="requesterRows" />
|
||||
</aside>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<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>
|
||||
جزئیات درخواست
|
||||
</h2>
|
||||
<div class="flex items-center gap-3 flex-wrap text-[11px]">
|
||||
<p><span class="font-normal text-[#576989]">گزارش درخواستشده:</span> <span class="font-semibold text-[#7A1419]">{{ modalityTitle(props.case.modality) }}</span></p>
|
||||
<span class="text-[#DEE3EB]">|</span>
|
||||
<p><span class="font-normal text-[#576989]">نوع درخواست:</span> <span class="font-semibold text-[#7A1419]">{{ props.case.priority }}</span></p>
|
||||
<span class="text-[#DEE3EB]">|</span>
|
||||
<p><span class="font-normal text-[#576989]">زمان درخواست:</span> <span class="font-semibold text-[#3E4C63]">{{ props.case.submitted_at ? convertEnglishToPersian(props.case.submitted_at) : '-' }}</span></p>
|
||||
</div>
|
||||
<div class="text-[12px] leading-6">
|
||||
<p class="font-normal text-[#576989]">تاریخچه بیماری:</p>
|
||||
<p class="font-normal text-[#3E4C63] whitespace-pre-line">{{ props.case.note || 'توضیحی ثبت نشده است.' }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap pt-2 border-t border-[#EBEBEB]">
|
||||
<UiButton label="دانلود فایل درخواست" severity="secondary" size="small" :disabled="!props.case.primary_files.length" @click="openFiles(props.case.primary_files)" />
|
||||
<UiButton label="دانلود سوابق پزشکی قبلی" severity="secondary" size="small" :disabled="!props.case.history_files.length" @click="openFiles(props.case.history_files)" />
|
||||
<UiButton v-if="!canReport && props.case.report" label="دانلود فایل گزارش" severity="secondary" size="small" :disabled="!props.case.report.attachments.length" @click="openFiles(props.case.report.attachments)" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p v-if="props.case.rejection_reason" class="bg-[#F8E9EA] border border-[#E7C3C5] rounded-2xl p-4 text-[12px] font-normal text-[#7A1419] leading-6">
|
||||
دلیل رد درخواست: {{ props.case.rejection_reason }}
|
||||
</p>
|
||||
|
||||
<template v-if="canReport">
|
||||
<ReportSectionCard v-model="reportForm.technique" title="تکنیک (Technique)" editable placeholder="شرح تکنیک را وارد کنید" :error="reportForm.errors.technique" />
|
||||
<ReportSectionCard v-model="reportForm.finding" title="یافتهها (Finding)" editable placeholder="یافتهها را وارد کنید" :error="reportForm.errors.finding" />
|
||||
<ReportSectionCard v-model="reportForm.diagnose" title="تشخیص (Diagnose)" editable placeholder="تشخیص را وارد کنید" :error="reportForm.errors.diagnose" />
|
||||
<ReportSectionCard v-model="reportForm.recommendation" title="پیشنهادات (Recommendation)" editable placeholder="پیشنهادات را وارد کنید" :error="reportForm.errors.recommendation" />
|
||||
|
||||
<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>
|
||||
آپلود فایل و مستندات
|
||||
</h2>
|
||||
<FileUploadBox v-model="reportForm.files" title="آپلود فایل و مستندات گزارش" />
|
||||
</section>
|
||||
|
||||
<footer class="bg-white border border-complement-50 rounded-2xl p-4 flex items-center justify-between flex-wrap gap-3">
|
||||
<p class="text-[11px] font-normal text-[#576989]">با تایید گزارش و یا رد درخواست دیگر امکان ویرایش آن را نخواهید داشت.</p>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="button" class="text-[11px] font-semibold text-[#7A1419]" @click="rejectModalVisible = true">رد درخواست</button>
|
||||
<UiButton label="تایید و ثبت گزارش" severity="success" size="small" :loading="reportForm.processing" :disabled="reportForm.processing" @click="submitReport" />
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<ReportSectionCard :model-value="props.case.report?.technique || ''" title="تکنیک (Technique)" />
|
||||
<ReportSectionCard :model-value="props.case.report?.finding || ''" title="یافتهها (Finding)" />
|
||||
<ReportSectionCard :model-value="props.case.report?.diagnose || ''" title="تشخیص (Diagnose)" />
|
||||
<ReportSectionCard :model-value="props.case.report?.recommendation || ''" title="پیشنهادات (Recommendation)" />
|
||||
|
||||
<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>
|
||||
آپلود فایل و مستندات
|
||||
</h2>
|
||||
<div v-if="props.case.report?.attachments?.length" class="flex flex-wrap gap-4">
|
||||
<a v-for="file in props.case.report.attachments" :key="file.id" :href="file.url" target="_blank" class="relative w-[5rem] h-[5rem] rounded-lg border border-gray-200 overflow-hidden">
|
||||
<img :alt="file.filename" :src="file.url" class="absolute top-0 left-0 w-full h-full object-cover" />
|
||||
</a>
|
||||
</div>
|
||||
<p v-else class="w-full min-h-[5rem] bg-[#F8FAFC] border border-[#DEE3EB] rounded-xl p-4 text-[12px] font-normal text-[#576989] leading-6 flex items-center justify-center text-center">
|
||||
عکسی برای این پرونده آپلود نشده است.
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="rejectModalVisible" :modal="true" :closable="true" :dismissableMask="false" :style="{ width: '480px' }" 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-3 border-t border-[#EBEBEB] pt-4">
|
||||
<p class="text-[12px] font-normal text-[#3E4C63] leading-6">پس از رد درخواست، مبلغ فاکتور به کیف پول درخواستکننده بازگردانده میشود.</p>
|
||||
<textarea
|
||||
v-model="rejectReason"
|
||||
rows="4"
|
||||
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>
|
||||
<div class="w-full pt-3 flex items-center justify-start gap-2 border-t border-[#EBEBEB]">
|
||||
<UiButton label="رد درخواست" severity="danger" size="small" :loading="rejecting" :disabled="rejecting" @click="submitReject" />
|
||||
<UiButton label="انصراف" severity="secondary" size="small" :disabled="rejecting" @click="rejectModalVisible = false" />
|
||||
</div>
|
||||
</Dialog>
|
||||
</ClientLayout>
|
||||
</template>
|
||||
@@ -7,9 +7,10 @@ import UiButton from '../../components/ui/UiButton.vue';
|
||||
import EyeSvg from '../../components/svg/eye.svg';
|
||||
import UiCard from '../../components/ui/UiCard.vue';
|
||||
import RequestCard from '../../components/dashboard/RequestCard.vue';
|
||||
import { ref } from 'vue';
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Link, usePage } from '@inertiajs/inertia-vue3';
|
||||
import FastRequestModal from '../../components/dashboard/FastRequestModal.vue';
|
||||
import PaymentResultModal from '../../components/dashboard/PaymentResultModal.vue';
|
||||
import { useFastRequestStore } from '../../store/FastRequest';
|
||||
import { modalities } from '../../data/fastRequestOptions';
|
||||
import { caseColumns } from '../../data/casesTableColumns';
|
||||
@@ -22,6 +23,17 @@ const openFastRequestModal = (type) => {
|
||||
fastRequestStore.requestType = type;
|
||||
}
|
||||
|
||||
const paymentResultVisible = ref(false);
|
||||
const paymentResult = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
const result = usePage().props.value.flash?.payment_result;
|
||||
if (result) {
|
||||
paymentResult.value = result;
|
||||
paymentResultVisible.value = true;
|
||||
}
|
||||
})
|
||||
|
||||
defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
@@ -30,13 +42,17 @@ defineProps({
|
||||
cases: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
stats: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<ActivitiesWidget />
|
||||
<ActivitiesWidget :stats="stats" />
|
||||
<UiCard title="ثبت آنی درخواست جدید" variant="default" class="mt-4">
|
||||
<p class="w-full flex items-center justify-start font-[400] text-[11px]">فقط چند قدم ساده برای ثبت درخواست کافیه.</p>
|
||||
<div class="w-full h-[179px] grid grid-cols-5 gap-3 justify-start pt-[10px]">
|
||||
@@ -77,5 +93,6 @@ defineProps({
|
||||
<ActivitiesChart />
|
||||
</UiCard>
|
||||
<fast-request-modal v-model:visible="fastRequestModalVisible"></fast-request-modal>
|
||||
<payment-result-modal v-model:visible="paymentResultVisible" :result="paymentResult"></payment-result-modal>
|
||||
</ClientLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<script setup>
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue';
|
||||
|
||||
const props = defineProps({
|
||||
document: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<h1>{{ document.title }}</h1>
|
||||
<p>{{ document.description }}</p>
|
||||
<p>{{ document.created_at }}</p>
|
||||
<p>{{ document.updated_at }}</p>
|
||||
<p>{{ document.status }}</p>
|
||||
<p>{{ document.priority }}</p>
|
||||
<p>{{ document.note }}</p>
|
||||
<p>{{ document.modality }}</p>
|
||||
<p>{{ document.pet_id }}</p>
|
||||
<p>{{ document.requester_id }}</p>
|
||||
</ClientLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue';
|
||||
import TableGeneric from '../../../components/ui/TableGeneric.vue';
|
||||
import UiCard from '../../../components/ui/UiCard.vue';
|
||||
import UiButton from '../../../components/ui/UiButton.vue';
|
||||
import InvoiceModal from '../../../components/dashboard/fastRequest/InvoiceModal.vue';
|
||||
import { invoiceColumns } from '../../../data/invoicesTableColumns';
|
||||
|
||||
defineProps({
|
||||
invoices: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const selectedInvoice = ref(null);
|
||||
const invoiceModalVisible = ref(false);
|
||||
|
||||
const openInvoice = (invoice) => {
|
||||
selectedInvoice.value = invoice;
|
||||
invoiceModalVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<UiCard title="لیست فاکتورها" variant="default">
|
||||
<TableGeneric
|
||||
class="bg-white !rounded-2xl"
|
||||
:data="invoices.data"
|
||||
customStyle="!text-[11px] !font-normal"
|
||||
:searchable="false"
|
||||
:columns="invoiceColumns"
|
||||
:selectable="false"
|
||||
:pagination="invoices.meta"
|
||||
:model-value="[]"
|
||||
>
|
||||
<template #column-actions="{ data }">
|
||||
<div class="flex items-center justify-end">
|
||||
<UiButton
|
||||
v-if="data.status === 'در انتظار پرداخت'"
|
||||
label="پرداخت"
|
||||
severity="success"
|
||||
size="small"
|
||||
@click="openInvoice(data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</UiCard>
|
||||
|
||||
<InvoiceModal v-model:visible="invoiceModalVisible" :invoice="selectedInvoice" />
|
||||
</ClientLayout>
|
||||
</template>
|
||||
@@ -1,11 +1,42 @@
|
||||
<script setup>
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue'
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue';
|
||||
import TableGeneric from '../../../components/ui/TableGeneric.vue';
|
||||
import UiCard from '../../../components/ui/UiCard.vue';
|
||||
import EyeSvg from '../../../components/svg/eye.svg';
|
||||
import { Link } from '@inertiajs/inertia-vue3';
|
||||
import { reportColumns } from '../../../data/reportsTableColumns';
|
||||
|
||||
defineProps({
|
||||
reports: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<h1 class="text-2xl font-bold">
|
||||
Reports
|
||||
</h1>
|
||||
<UiCard title="گزارشات" variant="default">
|
||||
<TableGeneric
|
||||
class="bg-white !rounded-2xl"
|
||||
:data="reports.data"
|
||||
customStyle="!text-[11px] !font-normal"
|
||||
:searchable="false"
|
||||
:columns="reportColumns"
|
||||
:selectable="false"
|
||||
:pagination="reports.meta"
|
||||
:model-value="[]"
|
||||
>
|
||||
<template #column-actions="{ data }">
|
||||
<div class="flex items-center justify-end">
|
||||
<Link :href="`/dashboard/cases/${data.document?.id}`"
|
||||
class="text-primary-500 bg-complement-100 flex items-center justify-end p-1.5 rounded-md"
|
||||
>
|
||||
<EyeSvg class="w-4 h-4 text-primary-500" />
|
||||
</Link>
|
||||
</div>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</UiCard>
|
||||
</ClientLayout>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { usePage } from '@inertiajs/inertia-vue3';
|
||||
import { toast } from 'vue3-toastify';
|
||||
import ClientLayout from '../../../layouts/ClientLayout.vue';
|
||||
import TableGeneric from '../../../components/ui/TableGeneric.vue';
|
||||
import UiCard from '../../../components/ui/UiCard.vue';
|
||||
import UiInput from '../../../components/ui/UiInput.vue';
|
||||
import UiButton from '../../../components/ui/UiButton.vue';
|
||||
import WalletSvg from '../../../components/svg/wallet.svg';
|
||||
import WithdrawRequestCard from '../../../components/dashboard/wallet/WithdrawRequestCard.vue';
|
||||
import { walletTransactionColumns } from '../../../data/walletTransactionsTableColumns';
|
||||
import { withdrawRequestColumns } from '../../../data/withdrawRequestsTableColumns';
|
||||
import { convertEnglishToPersian, convertPersianToEnglish, formatPrice } from '../../../utils/numberUtils';
|
||||
|
||||
const props = defineProps({
|
||||
transactions: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
withdrawRequests: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
bankCards: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const { props: pageProps } = usePage();
|
||||
|
||||
const quickAmounts = [100000, 200000, 500000];
|
||||
|
||||
const amount = ref('');
|
||||
const depositing = ref(false);
|
||||
|
||||
const numericAmount = computed(() => Number(convertPersianToEnglish(amount.value).replace(/\D/g, '')) || 0);
|
||||
|
||||
const formattedBalance = computed(() => formatPrice(pageProps.value.auth?.wallet_balance || 0));
|
||||
|
||||
const pendingWithdrawAmount = computed(() => props.withdrawRequests
|
||||
.filter((request) => request.status === 'در انتظار بررسی')
|
||||
.reduce((sum, request) => sum + request.amount, 0));
|
||||
|
||||
const withdrawStatusClasses = (status) => {
|
||||
if (status === 'تایید شده') {
|
||||
return 'text-[#1F7A4D] bg-[#E6F4EC]';
|
||||
}
|
||||
|
||||
if (status === 'رد شده') {
|
||||
return 'text-[#7A1419] bg-[#F8E9EA]';
|
||||
}
|
||||
|
||||
return 'text-[#576989] bg-[#EEF2F8]';
|
||||
};
|
||||
|
||||
const formatCardNumber = (cardNumber) => convertEnglishToPersian(cardNumber.replace(/(\d{4})(?=\d)/g, '$1 - '));
|
||||
|
||||
const selectQuickAmount = (value) => {
|
||||
amount.value = String(value);
|
||||
};
|
||||
|
||||
const handleDeposit = async () => {
|
||||
if (numericAmount.value < 10000) {
|
||||
toast.error('حداقل مبلغ شارژ ۱۰٬۰۰۰ تومان است.');
|
||||
return;
|
||||
}
|
||||
|
||||
depositing.value = true;
|
||||
try {
|
||||
const { data } = await window.axios.post('/dashboard/wallet/deposit', { amount: numericAmount.value });
|
||||
window.location.href = data.url;
|
||||
} catch (error) {
|
||||
depositing.value = false;
|
||||
toast.error(error.response?.data?.message || 'خطا در اتصال به درگاه پرداخت. لطفا دوباره تلاش کنید.');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientLayout>
|
||||
<div class="flex flex-col gap-4">
|
||||
<UiCard title="کیف پول" variant="default">
|
||||
<div class="grid grid-cols-2 max-sm:grid-cols-1 gap-4">
|
||||
<div class="flex items-center gap-3 bg-[#EEF2F8] border border-[#DEE3EB] rounded-[12px] px-5 py-4">
|
||||
<WalletSvg class="w-6 h-6" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[11px] font-normal text-[#576989]">موجودی کیف پول</span>
|
||||
<strong class="text-[16px] font-bold text-[#3E4C63]">{{ formattedBalance }} تومان</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 bg-[#F8FAFC] border border-[#DEE3EB] rounded-[12px] px-5 py-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-[11px] font-normal text-[#576989]">مبلغ در انتظار برداشت</span>
|
||||
<strong class="text-[16px] font-bold text-[#3E4C63]">{{ formatPrice(pendingWithdrawAmount) }} تومان</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<UiCard title="شارژ کیف پول" variant="default">
|
||||
<form class="flex flex-col gap-4" @submit.prevent="handleDeposit">
|
||||
<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-[22rem] max-sm:!w-full"
|
||||
customClass="!border-dominant-50 !font-normal !text-[12px]"
|
||||
/>
|
||||
<UiButton
|
||||
label="افزایش موجودی"
|
||||
severity="success"
|
||||
size="small"
|
||||
:loading="depositing"
|
||||
:disabled="depositing"
|
||||
@click="handleDeposit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
v-for="value in quickAmounts"
|
||||
:key="value"
|
||||
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="selectQuickAmount(value)"
|
||||
>
|
||||
{{ formatPrice(value) }} تومان
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</UiCard>
|
||||
|
||||
<WithdrawRequestCard :bank-cards="bankCards" />
|
||||
|
||||
<UiCard title="درخواستهای برداشت" variant="default">
|
||||
<TableGeneric
|
||||
class="bg-white !rounded-2xl"
|
||||
:data="withdrawRequests"
|
||||
customStyle="!text-[11px] !font-normal"
|
||||
:searchable="false"
|
||||
:columns="withdrawRequestColumns"
|
||||
:selectable="false"
|
||||
:pagination="null"
|
||||
:model-value="[]"
|
||||
>
|
||||
<template #column-bank_card="{ data }">
|
||||
<span v-if="data.bank_card" dir="ltr">{{ formatCardNumber(data.bank_card.card_number) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #column-status="{ data }">
|
||||
<span class="text-[11px] font-normal rounded-md px-2 py-1" :class="withdrawStatusClasses(data.status)">
|
||||
{{ data.status }}
|
||||
</span>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</UiCard>
|
||||
|
||||
<UiCard title="تراکنشها" variant="default">
|
||||
<TableGeneric
|
||||
class="bg-white !rounded-2xl"
|
||||
:data="transactions.data"
|
||||
customStyle="!text-[11px] !font-normal"
|
||||
:searchable="false"
|
||||
:columns="walletTransactionColumns"
|
||||
:selectable="false"
|
||||
:pagination="transactions.meta"
|
||||
:model-value="[]"
|
||||
>
|
||||
<template #column-type="{ data }">
|
||||
<span
|
||||
class="text-[11px] font-normal rounded-md px-2 py-1"
|
||||
:class="data.type === 'واریز' ? 'text-[#1F7A4D] bg-[#E6F4EC]' : 'text-[#7A1419] bg-[#F8E9EA]'"
|
||||
>
|
||||
{{ data.type }}
|
||||
</span>
|
||||
</template>
|
||||
</TableGeneric>
|
||||
</UiCard>
|
||||
</div>
|
||||
</ClientLayout>
|
||||
</template>
|
||||
@@ -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>
|
||||
@@ -1,14 +1,47 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { Link, usePage } from '@inertiajs/inertia-vue3';
|
||||
import { useGlobalStore } from '../../store/Global';
|
||||
import UiInput from '../ui/UiInput.vue';
|
||||
import ArrowDownSvg from '../svg/arrowDown.svg';
|
||||
import GlobalSvg from '../svg/global.svg';
|
||||
import EditProfileModal from '../dashboard/EditProfileModal.vue';
|
||||
import { formatPrice } from '../../utils/numberUtils';
|
||||
const globalStore = useGlobalStore();
|
||||
const { props } = usePage();
|
||||
|
||||
const walletBalance = computed(() => formatPrice(props.value.auth?.wallet_balance || 0));
|
||||
|
||||
const toggleSidebar = () => {
|
||||
globalStore.sidebarOpen = !globalStore.sidebarOpen;
|
||||
};
|
||||
|
||||
const userMenuOpen = ref(false);
|
||||
const userMenuRef = ref(null);
|
||||
const editProfileVisible = ref(false);
|
||||
|
||||
const toggleUserMenu = () => {
|
||||
userMenuOpen.value = !userMenuOpen.value;
|
||||
};
|
||||
|
||||
const openEditProfile = () => {
|
||||
userMenuOpen.value = false;
|
||||
editProfileVisible.value = true;
|
||||
};
|
||||
|
||||
const closeOnClickOutside = (event) => {
|
||||
if (userMenuRef.value && !userMenuRef.value.contains(event.target)) {
|
||||
userMenuOpen.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => document.addEventListener('click', closeOnClickOutside));
|
||||
onBeforeUnmount(() => document.removeEventListener('click', closeOnClickOutside));
|
||||
|
||||
const menuItems = [
|
||||
{ label: 'کیف پول', icon: 'fi-rr-wallet', href: '/dashboard/wallet' },
|
||||
{ label: 'فاکتورها', icon: 'fi-rr-receipt', href: '/dashboard/invoices' },
|
||||
{ label: 'برنامه شیفتها', icon: 'fi-rr-calendar-clock', href: '/dashboard/shifts' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -39,17 +72,63 @@ const toggleSidebar = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Link href="/dashboard/wallet" class="flex items-center gap-2.5 rounded-lg bg-dominant-light border border-dominant-50 ps-1.5 pe-3 py-1.5 transition-colors hover:border-dominant-100">
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-lg bg-white border border-dominant-50 text-dominant-400">
|
||||
<i class="fi fi-rr-wallet flex text-[15px]"></i>
|
||||
</span>
|
||||
<span class="flex flex-col max-sm:hidden">
|
||||
<span class="text-[10px] font-normal text-dominant-200">موجودی کیف پول</span>
|
||||
<span class="text-[12px] font-bold text-dominant-500">{{ walletBalance }} <span class="text-[10px] font-normal text-dominant-300">تومان</span></span>
|
||||
</span>
|
||||
</Link>
|
||||
<div class="flex items-center justify-center border-l-[1px] !border-dominant-50 pl-4">
|
||||
<GlobalSvg />
|
||||
</div>
|
||||
<div class="grid grid-cols-2a justify-start items-center gap-2">
|
||||
<span class="text-dominant-300 text-[12px] font-normal">
|
||||
{{ globalStore.current_user.fullname }}
|
||||
</span>
|
||||
<ArrowDownSvg class="!w-4 !h-4 flex -mt-0.5"/>
|
||||
<div ref="userMenuRef" class="relative">
|
||||
<button type="button" class="flex items-center gap-2 cursor-pointer" @click="toggleUserMenu">
|
||||
<span class="flex items-center justify-center w-9 h-9 rounded-full bg-dominant-light border border-dominant-50 overflow-hidden">
|
||||
<img v-if="globalStore.current_user.profile_url" :src="globalStore.current_user.profile_url" alt="تصویر پروفایل" class="w-full h-full object-cover" />
|
||||
<i v-else class="fi fi-rr-user flex text-sm text-dominant-300"></i>
|
||||
</span>
|
||||
<span class="text-dominant-300 text-[12px] font-normal max-sm:hidden">
|
||||
{{ globalStore.current_user.fullname }}
|
||||
</span>
|
||||
<i class="fi fi-rr-angle-small-down flex text-sm text-dominant-300 transition-transform duration-200" :class="{ 'rotate-180': userMenuOpen }"></i>
|
||||
</button>
|
||||
<Transition
|
||||
enter-active-class="transition duration-150 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-100 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-1"
|
||||
>
|
||||
<nav v-if="userMenuOpen" class="absolute left-0 top-full mt-2 w-52 rounded-xl bg-white border border-complement-100 shadow-all py-2 z-30">
|
||||
<ul class="flex flex-col">
|
||||
<li>
|
||||
<button type="button" class="w-full flex items-center gap-2.5 px-4 py-2.5 text-[12px] font-normal text-complement-900 cursor-pointer transition-colors hover:bg-complement-50" @click="openEditProfile">
|
||||
<i class="fi fi-rr-user-pen flex text-[14px] text-complement-700"></i>
|
||||
ویرایش حساب کاربری
|
||||
</button>
|
||||
</li>
|
||||
<li v-for="item in menuItems" :key="item.href">
|
||||
<Link :href="item.href" class="flex items-center gap-2.5 px-4 py-2.5 text-[12px] font-normal text-complement-900 transition-colors hover:bg-complement-50" @click="userMenuOpen = false">
|
||||
<i class="fi flex text-[14px] text-complement-700" :class="item.icon"></i>
|
||||
{{ item.label }}
|
||||
</Link>
|
||||
</li>
|
||||
<li class="border-t border-complement-100 mt-1 pt-1">
|
||||
<Link href="/dashboard/logout" class="flex items-center gap-2.5 px-4 py-2.5 text-[12px] font-normal text-dominant-400 transition-colors hover:bg-dominant-light">
|
||||
<i class="fi fi-rr-sign-out-alt flex text-[14px]"></i>
|
||||
خروج از حساب
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<EditProfileModal v-model:visible="editProfileVisible" />
|
||||
</header>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import LogoutSvg from "../svg/logout.svg"
|
||||
import CalendarSvg from "../svg/calendar.svg"
|
||||
import StatusUpSvg from "../svg/statusUp.svg"
|
||||
import PetSvg from "../svg/pet.svg"
|
||||
import WalletSvg from "../svg/wallet.svg"
|
||||
import InvoiceSvg from "../svg/invoice.svg"
|
||||
|
||||
interface SidebarItem extends MenuItem {
|
||||
label: string;
|
||||
@@ -87,6 +89,20 @@ const items: Ref<SidebarItem[]> = ref([
|
||||
key: 'reports',
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
label: "کیف پول",
|
||||
icon: WalletSvg,
|
||||
to: "/dashboard/wallet",
|
||||
key: 'wallet',
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
label: "فاکتورها",
|
||||
icon: InvoiceSvg,
|
||||
to: "/dashboard/invoices",
|
||||
key: 'invoices',
|
||||
permission: null,
|
||||
},
|
||||
{
|
||||
label: "شیفت ها",
|
||||
icon: CalendarSvg,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5 5.25V12.75C16.5 15 15.375 16.5 12.75 16.5H5.25C2.625 16.5 1.5 15 1.5 12.75V5.25C1.5 3 2.625 1.5 5.25 1.5H12.75C15.375 1.5 16.5 3 16.5 5.25Z" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.4375 5.8125H12.5625" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.4375 9H12.5625" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.4375 12.1875H9.75" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 691 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="220" height="180" viewBox="0 0 220 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="46" cy="34" r="4" fill="#F5A9AB"/>
|
||||
<circle cx="178" cy="28" r="3" fill="#F5A9AB"/>
|
||||
<circle cx="188" cy="120" r="4" fill="#F5A9AB"/>
|
||||
<circle cx="34" cy="128" r="3" fill="#F5A9AB"/>
|
||||
<circle cx="110" cy="164" r="3" fill="#F5A9AB"/>
|
||||
<circle cx="110" cy="90" r="52" fill="#E5484D"/>
|
||||
<path d="M92 72L128 108M128 72L92 108" stroke="white" stroke-width="9" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
@@ -0,0 +1,19 @@
|
||||
<svg width="220" height="180" viewBox="0 0 220 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="38" y="24" width="4" height="14" rx="2" transform="rotate(-24 38 24)" fill="#3E6D9C"/>
|
||||
<rect x="176" y="18" width="4" height="12" rx="2" transform="rotate(32 176 18)" fill="#E5484D"/>
|
||||
<rect x="24" y="96" width="4" height="12" rx="2" transform="rotate(58 24 96)" fill="#E5484D"/>
|
||||
<rect x="188" y="92" width="4" height="14" rx="2" transform="rotate(-40 188 92)" fill="#3E6D9C"/>
|
||||
<rect x="66" y="150" width="4" height="12" rx="2" transform="rotate(20 66 150)" fill="#8BC34A"/>
|
||||
<rect x="150" y="152" width="4" height="12" rx="2" transform="rotate(-28 150 152)" fill="#E5484D"/>
|
||||
<circle cx="30" cy="58" r="3" fill="#8BC34A"/>
|
||||
<circle cx="58" cy="12" r="3" fill="#E5484D"/>
|
||||
<circle cx="118" cy="8" r="3" fill="#64B5F6"/>
|
||||
<circle cx="196" cy="52" r="3" fill="#8BC34A"/>
|
||||
<circle cx="200" cy="136" r="3" fill="#64B5F6"/>
|
||||
<circle cx="96" cy="168" r="3" fill="#E5484D"/>
|
||||
<circle cx="20" cy="140" r="3" fill="#64B5F6"/>
|
||||
<circle cx="164" cy="120" r="2.5" fill="#E5484D"/>
|
||||
<circle cx="52" cy="118" r="2.5" fill="#8BC34A"/>
|
||||
<circle cx="110" cy="90" r="52" fill="#5384AD"/>
|
||||
<path d="M88 91L103 106L133 76" stroke="white" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.75 9.9075H10.995C10.29 9.9075 9.7125 9.33 9.7125 8.625C9.7125 7.92 10.29 7.3425 10.995 7.3425H12.75" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.235 8.61H11.145" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.1875 2.25H10.6875C12.5325 2.25 12.9375 2.7 12.9375 4.005V7.3425" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.65 11.055C4.65 11.4675 4.3125 11.805 3.9 11.805C3.4875 11.805 3.15 11.4675 3.15 11.055C3.15 10.6425 3.4875 10.305 3.9 10.305C4.3125 10.305 4.65 10.6425 4.65 11.055Z" fill="#965457"/>
|
||||
<path d="M1.5 8.8125V12.375C1.5 14.445 3.18 16.125 5.25 16.125H12.75C14.82 16.125 16.5 14.445 16.5 12.375V6.375C16.5 4.305 14.82 2.625 12.75 2.625" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.5 8.8125V5.25C1.5 4.0575 2.235 2.9925 3.3525 2.5725" stroke="#965457" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -7,50 +7,39 @@ const props = defineProps([
|
||||
'perPage',
|
||||
'links',
|
||||
])
|
||||
|
||||
const pageItemClass = 'flex items-center justify-center w-8 h-8 rounded-lg border text-[11px] font-semibold transition-colors';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="pagination" v-if="lastPage > 1">
|
||||
<nav class="flex items-center gap-1.5 p-4" v-if="lastPage > 1">
|
||||
<template v-for="(link , index) in links" :key="index">
|
||||
<div class="paginate" v-if="link.label == 'قبلی'">
|
||||
<Link v-if="link.url" :href="link.url">
|
||||
<i class="fi fi-rr-angle-small-right flex text-xl"></i>
|
||||
<template v-if="link.label == 'قبلی'">
|
||||
<Link v-if="link.url" :href="link.url" :class="pageItemClass" class="border-complement-100 text-complement-700 hover:bg-complement-50">
|
||||
<i class="fi fi-rr-angle-small-right flex text-base"></i>
|
||||
</Link>
|
||||
</div>
|
||||
<div class="paginate" v-else-if="link.label == 'بعدی'">
|
||||
<Link v-if="link.url" :href="link.url">
|
||||
<i class="fi fi-rr-angle-small-left flex text-xl"></i>
|
||||
<span v-else :class="pageItemClass" class="border-complement-100 text-complement-200">
|
||||
<i class="fi fi-rr-angle-small-right flex text-base"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="link.label == 'بعدی'">
|
||||
<Link v-if="link.url" :href="link.url" :class="pageItemClass" class="border-complement-100 text-complement-700 hover:bg-complement-50">
|
||||
<i class="fi fi-rr-angle-small-left flex text-base"></i>
|
||||
</Link>
|
||||
</div>
|
||||
<div class="paginate flex items-center" :class="{'!text-white !bg-client-primary' : currentPage == link.label }" v-else>
|
||||
<Link v-if="link.url" :href="link.url" class="flex pt-1">{{link.label}}</Link>
|
||||
<Link v-else href="" class="flex">{{link.label}}</Link>
|
||||
</div>
|
||||
<span v-else :class="pageItemClass" class="border-complement-100 text-complement-200">
|
||||
<i class="fi fi-rr-angle-small-left flex text-base"></i>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Link
|
||||
v-if="link.url"
|
||||
:href="link.url"
|
||||
:class="[pageItemClass, currentPage == link.label ? 'bg-dominant border-dominant text-white' : 'border-complement-100 text-complement-900 hover:bg-complement-50']"
|
||||
>
|
||||
{{ link.label }}
|
||||
</Link>
|
||||
<span v-else :class="pageItemClass" class="border-transparent text-complement-700">{{ link.label }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="postcss">
|
||||
section.pagination{
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
section.pagination div.paginate:first-child a{
|
||||
border-top-right-radius: .4rem;
|
||||
border-bottom-right-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate:last-child a{
|
||||
border-top-left-radius: .4rem;
|
||||
border-bottom-left-radius: .4rem;
|
||||
}
|
||||
section.pagination div.paginate a{
|
||||
display: flex;
|
||||
height: 2.2rem;
|
||||
width: 2.2rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
@apply text-client-Fontcolor
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -133,6 +133,12 @@ const buttonClasses = computed(() => {
|
||||
if (props.severity === 'secondary') {
|
||||
classes.push('!font-[600] !text-[11px] !rounded-[6px]')
|
||||
}
|
||||
if (props.severity === 'success') {
|
||||
classes.push('ui-button--success !font-[600] !text-[11px] !rounded-[6px]')
|
||||
}
|
||||
if (props.severity === 'danger') {
|
||||
classes.push('!font-[600] !text-[11px] !rounded-[6px]')
|
||||
}
|
||||
|
||||
return classes.join(' ')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { modalityTitle } from './fastRequestOptions'
|
||||
import { convertEnglishToPersian, formatPrice } from '../utils/numberUtils'
|
||||
|
||||
export const invoiceColumns = [
|
||||
{
|
||||
title: 'شماره فاکتور',
|
||||
key: 'number',
|
||||
render: (row) => convertEnglishToPersian(row.number)
|
||||
},
|
||||
{
|
||||
title: 'شماره پرونده',
|
||||
key: 'document',
|
||||
render: (row) => row.document?.code ?? '-'
|
||||
},
|
||||
{
|
||||
title: 'نوع درخواست',
|
||||
key: 'modality',
|
||||
render: (row) => row.document ? modalityTitle(row.document.modality) : '-'
|
||||
},
|
||||
{
|
||||
title: 'مبلغ',
|
||||
key: 'amount',
|
||||
render: (row) => `${formatPrice(row.amount)} تومان`
|
||||
},
|
||||
{
|
||||
title: 'وضعیت',
|
||||
key: 'status'
|
||||
},
|
||||
{
|
||||
title: 'روش پرداخت',
|
||||
key: 'payment_method',
|
||||
render: (row) => row.payment_method ?? '-'
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت',
|
||||
key: 'created_at',
|
||||
render: (row) => row.created_at ? convertEnglishToPersian(new Date(row.created_at).toLocaleDateString('fa-IR')) : '-'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
key: 'actions',
|
||||
width: 100,
|
||||
style: 'grid grid-cols-1',
|
||||
slot: true,
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
import { modalityTitle } from './fastRequestOptions'
|
||||
import { convertEnglishToPersian } from '../utils/numberUtils'
|
||||
|
||||
export const reportColumns = [
|
||||
{
|
||||
title: 'شماره پرونده',
|
||||
key: 'code',
|
||||
render: (row) => row.document?.code || '-'
|
||||
},
|
||||
{
|
||||
title: 'نوع درخواست',
|
||||
key: 'modality',
|
||||
render: (row) => row.document ? modalityTitle(row.document.modality) : '-'
|
||||
},
|
||||
{
|
||||
title: 'وضعیت گزارش',
|
||||
key: 'status'
|
||||
},
|
||||
{
|
||||
title: 'پزشک',
|
||||
key: 'doctor',
|
||||
render: (row) => row.doctor || '-'
|
||||
},
|
||||
{
|
||||
title: 'تاریخ ثبت گزارش',
|
||||
key: 'submitted_at',
|
||||
render: (row) => row.submitted_at ? convertEnglishToPersian(row.submitted_at) : '-'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
key: 'actions',
|
||||
width: 80,
|
||||
style: 'grid grid-cols-1',
|
||||
slot: true,
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
import { convertEnglishToPersian, formatPrice } from '../utils/numberUtils'
|
||||
|
||||
export const walletTransactionColumns = [
|
||||
{
|
||||
title: 'نوع',
|
||||
key: 'type',
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
title: 'بابت',
|
||||
key: 'reason'
|
||||
},
|
||||
{
|
||||
title: 'مبلغ',
|
||||
key: 'amount',
|
||||
render: (row) => `${formatPrice(row.amount)} تومان`
|
||||
},
|
||||
{
|
||||
title: 'روش',
|
||||
key: 'via_gateway',
|
||||
render: (row) => row.via_gateway ? 'درگاه پرداخت' : 'کیف پول'
|
||||
},
|
||||
{
|
||||
title: 'شماره فاکتور',
|
||||
key: 'invoice_number',
|
||||
render: (row) => row.invoice_number ? convertEnglishToPersian(row.invoice_number) : '-'
|
||||
},
|
||||
{
|
||||
title: 'موجودی پس از تراکنش',
|
||||
key: 'balance_after',
|
||||
render: (row) => `${formatPrice(row.balance_after)} تومان`
|
||||
},
|
||||
{
|
||||
title: 'تاریخ',
|
||||
key: 'created_at',
|
||||
render: (row) => row.created_at ? convertEnglishToPersian(row.created_at) : '-'
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
import { convertEnglishToPersian, formatPrice } from '../utils/numberUtils'
|
||||
|
||||
export const withdrawRequestColumns = [
|
||||
{
|
||||
title: 'مبلغ',
|
||||
key: 'amount',
|
||||
render: (row) => `${formatPrice(row.amount)} تومان`
|
||||
},
|
||||
{
|
||||
title: 'کارت مقصد',
|
||||
key: 'bank_card',
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
title: 'وضعیت',
|
||||
key: 'status',
|
||||
slot: true,
|
||||
},
|
||||
{
|
||||
title: 'تاریخ درخواست',
|
||||
key: 'created_at',
|
||||
render: (row) => convertEnglishToPersian(row.created_at)
|
||||
},
|
||||
{
|
||||
title: 'تاریخ بررسی',
|
||||
key: 'reviewed_at',
|
||||
render: (row) => row.reviewed_at ? convertEnglishToPersian(row.reviewed_at) : '-'
|
||||
},
|
||||
{
|
||||
title: 'توضیح',
|
||||
key: 'admin_note',
|
||||
render: (row) => row.admin_note || '-'
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user