feat: Implement Fast Request feature with file upload and request management

This commit is contained in:
2026-08-26 17:40:45 +03:30
parent ba734679cf
commit 2d96978c38
33 changed files with 1294 additions and 852 deletions
@@ -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 { caseColumns } from '../../../data/casesTableColumns';
defineProps({
cases: {
type: Object,
required: true
}
})
</script>
<template>
<ClientLayout>
<h1 class="text-2xl font-bold">
Cases
</h1>
<UiCard title="لیست کیس‌ها" variant="default">
<TableGeneric
class="bg-white !rounded-2xl"
:data="cases.data"
customStyle="!text-[11px] !font-normal"
:searchable="false"
:columns="caseColumns"
:selectable="false"
:pagination="cases.meta"
:model-value="[]"
>
<template #column-actions="{ data }">
<div class="flex items-center justify-end">
<Link :href="`/dashboard/cases/${data.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>
@@ -11,6 +11,8 @@ import { ref } from 'vue';
import { Link } from '@inertiajs/inertia-vue3';
import FastRequestModal from '../../components/dashboard/FastRequestModal.vue';
import { useFastRequestStore } from '../../store/FastRequest';
import { modalities } from '../../data/fastRequestOptions';
import { caseColumns } from '../../data/casesTableColumns';
const fastRequestStore = useFastRequestStore();
const fastRequestModalVisible = ref(false);
@@ -24,90 +26,12 @@ defineProps({
user: {
type: Object,
required: true
},
cases: {
type: Object,
required: true
}
})
const data = ref([
{
id: 1,
file_number: '1234567890',
status: 'مشاهده نشد',
request_type: 'CT',
requester: 'محراب اسماعیل‌نیا',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'اتمام فرصت رسیدگی'
},
{
id: 2,
file_number: '1234567891',
status: 'مشاهده نشد',
request_type: 'CT',
requester: 'رضا گلیان',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: '۵۳ دقیقه'
},
{
id: 3,
file_number: '1234567892',
status: 'مشاهده نشد',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۳۲ ساعت'
},
{
id: 4,
file_number: '1234567893',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۲۴ ساعت'
},
{
id: 5,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
}
])
const columns = ref([
{
title: 'شماره پرونده',
key: 'file_number'
},
{
title: 'وضعیت',
key: 'status'
},
{
title: 'نوع درخواست',
key: 'request_type'
},
{
title: 'درخواست‌کننده',
key: 'requester'
},
{
title: 'تاریخ ثبت',
key: 'registration_date'
},
{
title: 'فرصت رسیدگی',
key: 'processing_deadline'
},
{
title: '',
key: 'actions',
width: 80,
style: 'grid grid-cols-1',
slot: true,
}
])
</script>
<template>
@@ -116,27 +40,31 @@ const columns = ref([
<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]">
<RequestCard title="CT Scan" icon="ct-scan" severity="primary" @click="openFastRequestModal('ct-scan')"/>
<RequestCard title="MRI" icon="mri" severity="primary" @click="openFastRequestModal('mri')"/>
<RequestCard title="سونوگرافی" icon="sonography" severity="primary" @click="openFastRequestModal('sonography')"/>
<RequestCard title="اکوکاردیوگرافی" icon="echo" severity="primary" @click="openFastRequestModal('echo')"/>
<RequestCard title="رادیولوژی" icon="radiology" severity="primary" @click="openFastRequestModal('radiology')"/>
<RequestCard
v-for="modality in modalities"
:key="modality.code"
:value="modality.code"
:title="modality.title"
:icon="modality.icon"
severity="primary"
@click="openFastRequestModal(modality.code)"
/>
</div>
</UiCard>
<UiCard title="آخرین کیس‌های در انتظار بررسی" variant="default" class="mt-4">
<TableGeneric
class="bg-white !rounded-2xl"
:data="data"
:data="cases.data"
customStyle="!text-[11px] !font-normal"
:searchable="false"
:columns="columns"
:columns="caseColumns"
:selectable="false"
:pagination="null"
:model-value="[]"
>
<template #column-actions="{ data }" class="">
<div class="flex items-center justify-end">
<Link to="/dashboard/doctor/cases/1"
<Link :href="`/dashboard/cases/${data.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" />
@@ -4,157 +4,17 @@ import UiCard from '../../../components/ui/UiCard.vue'
import TableGeneric from '../../../components/ui/TableGeneric.vue'
import EyeSvg from '../../../components/svg/eye.svg'
import { Link } from '@inertiajs/inertia-vue3'
import { ref } from 'vue'
import { useDocumentStore } from '../../../store/Document'
import { caseColumns } from '../../../data/casesTableColumns'
const documentStore = useDocumentStore();
const selectedItems = ref([])
const data = ref([
{
id: 1,
file_number: '1234567890',
status: 'مشاهده نشد',
request_type: 'CT',
requester: 'محراب اسماعیل‌نیا',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'اتمام فرصت رسیدگی'
},
{
id: 2,
file_number: '1234567891',
status: 'مشاهده نشد',
request_type: 'CT',
requester: 'رضا گلیان',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: '۵۳ دقیقه'
},
{
id: 3,
file_number: '1234567892',
status: 'مشاهده نشد',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۳۲ ساعت'
},
{
id: 4,
file_number: '1234567893',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۲۴ ساعت'
},
{
id: 5,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
},
{
id: 6,
file_number: '1234567893',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۲۴ ساعت'
},
{
id: 7,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
},
{
id: 8,
file_number: '1234567893',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۲۴ ساعت'
},
{
id: 9,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
},
{
id: 7,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
},
{
id: 8,
file_number: '1234567893',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'دکتر مریم مهدی‌پور',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۲۴ ساعت'
},
{
id: 9,
file_number: '1234567894',
status: 'در انتظار ثبت گزارش',
request_type: 'MRI',
requester: 'نام شخص و یا پزشک',
registration_date: '۱۴۰۴/۰۳/۰۲ ۱۴:۱۸',
processing_deadline: 'کمتر از ۵ ساعت'
defineProps({
documents: {
type: Object,
required: true
}
])
const columns = ref([
{
title: 'شماره پرونده',
key: 'file_number'
},
{
title: 'وضعیت',
key: 'status'
},
{
title: 'نوع درخواست',
key: 'request_type'
},
{
title: 'درخواست‌کننده',
key: 'requester'
},
{
title: 'تاریخ ثبت',
key: 'registration_date'
},
{
title: 'فرصت رسیدگی',
key: 'processing_deadline'
},
{
title: '',
key: 'actions',
width: 80,
style: 'grid grid-cols-1',
slot: true,
},
])
})
</script>
<template>
@@ -162,13 +22,13 @@ const columns = ref([
<UiCard title="کیس‌های در انتظار بررسی" variant="default" class="">
<TableGeneric
class="bg-white !rounded-2xl"
:data="data"
:data="documents.data"
customStyle="!text-[11px] !font-normal"
:searchable="false"
:columns="columns"
:columns="caseColumns"
:selectable="false"
:pagination="null"
:model-value="selectedItems"
:pagination="documents.meta"
:model-value="[]"
>
<template #column-actions="{ data }" class="">
<div class="flex items-center justify-end">
@@ -1,108 +1,96 @@
<script setup>
import UiButton from '../../components/ui/UiButton.vue';
import { ref, computed } from 'vue';
import Dialog from 'primevue/dialog';
import ProgressBar from 'primevue/progressbar';
import RequestType from './fastRequest/RequestType.vue';
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 UiButton from '../../components/ui/UiButton.vue';
import { computed, watch } from 'vue';
import Dialog from 'primevue/dialog';
import ProgressBar from 'primevue/progressbar';
import { toast } from 'vue3-toastify';
import RequestType from './fastRequest/RequestType.vue';
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 { useFastRequestStore } from '../../store/FastRequest';
const props = defineProps({
title: {
type: String,
required: true
},
icon: {
type: String,
required: true
}
});
const visible = defineModel('visible');
const fastRequestStore = useFastRequestStore();
const step = ref(1);
const stepRef = ref(null);
const steps = [
{ label: 'نوع درخواست', component: RequestType },
{ label: 'انتخاب کیس', component: RequestCase },
{ label: 'جزئیات درخواست', component: RequestDetails },
{ label: 'انتخاب پزشک', component: RequestDoctor },
{ label: 'مرور نهایی', component: RequestReview },
];
const steps = ref({
1: RequestType,
2: RequestCase,
3: RequestDetails,
4: RequestDoctor,
5: RequestReview,
});
const currentStepComponent = computed(() => {
return steps[fastRequestStore.step - 1]?.component || RequestType;
});
const currentStepComponent = computed(() => {
return steps.value[step.value] || RequestType
});
const handleNext = () => {
step.value++;
const isLastStep = computed(() => fastRequestStore.step === steps.length);
watch(visible, (isVisible) => {
if (isVisible) {
fastRequestStore.fetchCreateData();
} else {
fastRequestStore.reset();
}
});
const handleNext = async () => {
if (!fastRequestStore.canProceed) {
toast.error('لطفا اطلاعات این مرحله را کامل کنید.');
return;
}
if (!isLastStep.value) {
fastRequestStore.nextStep();
return;
}
try {
const document = await fastRequestStore.submit();
toast.success(`درخواست شما با شماره پرونده ${document.code} با موفقیت ثبت شد.`);
visible.value = false;
} catch (error) {
toast.error(error.response?.data?.message || 'خطا در ثبت درخواست. لطفا دوباره تلاش کنید.');
}
};
</script>
<template>
<Dialog :modal="true" :closable="true" :dismissableMask="true" :maximizable="false" :style="{ width: '688px' , minHeight: '376px' }" :pt="{header : '!bg-client-primaryBlue'}">
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="true" :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>
<div class="w-full h-[56px] flex items-center justify-between border-b border-[#EBEBEB] pb-3">
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center ">
<span class="w-[20px] h-[20px] bg-[#576989] rounded-full text-white flex items-center justify-center text-[10px] font-[600]">1</span>
<template v-for="(item, index) in steps" :key="item.label">
<ProgressBar v-if="index > 0" :value="fastRequestStore.step > index ? 100 : 0" :showValue="false" mode="determinate"></ProgressBar>
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center">
<span
class="w-[20px] h-[20px] rounded-full text-white flex items-center justify-center text-[10px] font-[600]"
:class="fastRequestStore.step >= index + 1 ? 'bg-[#576989]' : 'bg-[#C1D0E8]'"
>{{ index + 1 }}</span>
</div>
<span class="text-[10px] font-[600]" :class="fastRequestStore.step >= index + 1 ? 'text-[#576989]' : 'text-[#7188AD]'">{{ item.label }}</span>
</div>
<span class="text-[10px] font-[600] text-[#7188AD]">نوع درخواست</span>
</div>
<ProgressBar :value="value1" :showValue="false" mode="determinate"></ProgressBar>
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center ">
<span class="w-[20px] h-[20px] bg-[#576989] rounded-full text-white flex items-center justify-center text-[10px] font-[600]">2</span>
</div>
<span class="text-[10px] font-[600] text-[#7188AD]">انتخاب کیس</span>
</div>
<ProgressBar :value="value2" :showValue="false" mode="determinate"></ProgressBar>
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center ">
<span class="w-[20px] h-[20px] bg-[#576989] rounded-full text-white flex items-center justify-center text-[10px] font-[600]">3</span>
</div>
<span class="text-[10px] font-[600] text-[#7188AD]">جزئیات درخواست</span>
</div>
<ProgressBar :value="value3" :showValue="false" mode="determinate"></ProgressBar>
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center ">
<span class="w-[20px] h-[20px] bg-[#576989] rounded-full text-white flex items-center justify-center text-[10px] font-[600]">4</span>
</div>
<span class="text-[10px] font-[600] text-[#7188AD]">انتخاب پزشک</span>
</div>
<ProgressBar :value="value4" :showValue="false" mode="determinate"></ProgressBar>
<div class="w-auto h-[40px] grid grid-cols-1 items-center justify-center gap-1">
<div class="w-full h-[20px] flex items-center justify-center ">
<span class="w-[20px] h-[20px] bg-[#576989] rounded-full text-white flex items-center justify-center text-[10px] font-[600]">5</span>
</div>
<span class="text-[10px] font-[600] text-[#7188AD]">مرور نهایی</span>
</div>
</template>
</div>
<component :is="currentStepComponent" ref="stepRef" @validated="onValidated"/>
<component :is="currentStepComponent" />
<div class="w-full pt-3 flex items-center justify-end border-t border-[#EBEBEB]">
<ui-button label="تایید و مرحله بعد" severity="success" size="small" @click="handleNext" />
<div class="w-full pt-3 flex items-center justify-between border-t border-[#EBEBEB]">
<ui-button v-if="fastRequestStore.step > 1" label="مرحله قبل" severity="secondary" size="small" @click="fastRequestStore.prevStep" />
<span v-else></span>
<ui-button
:label="isLastStep ? 'ثبت نهایی درخواست' : 'تایید و مرحله بعد'"
severity="success"
size="small"
:loading="fastRequestStore.submitting"
:disabled="fastRequestStore.submitting"
@click="handleNext"
/>
</div>
</Dialog>
</template>
<style scoped>
:deep(.p-progressbar) {
min-width: 60px !important;
height: 4px;
}
:deep(.p-progressbar-value) {
background: #576989 !important;
}
</style>
@@ -2,46 +2,36 @@
import Dialog from 'primevue/dialog';
import UiButton from '../../ui/UiButton.vue';
import TableGeneric from '../../ui/TableGeneric.vue';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import CaseNumber from './CaseNumber.vue';
import { useFastRequestStore } from '../../../store/FastRequest';
const visible = defineModel('visible');
const fastRequestStore = useFastRequestStore();
const isChooseCaseNumber = ref(false);
const selectedPetId = ref(null);
const showChooseCaseNumber = () => {
isChooseCaseNumber.value = true;
}
const data = ref([
{
id: 1,
case_number: '1234567890',
case_name: 'مایلو',
case_type: 'سگ',
},
{
id: 2,
case_number: '1234567891',
case_name: 'تدی',
case_type: 'گربه',
},
{
id: 3,
case_number: '1234567892',
case_name: 'پونی',
case_type: 'خرگوش',
},
{
id: 4,
case_number: '1234567893',
case_name: 'روبیک',
case_type: 'همستر',
},
{
id: 5,
case_number: '1234567894',
case_name: 'کولا',
case_type: 'خوکچه هندی',
}
]);
const data = computed(() => fastRequestStore.pets.map((pet) => ({
id: pet.id,
case_number: pet.id,
case_name: pet.name,
case_type: pet.breed ? `${pet.type} (${pet.breed})` : pet.type,
})));
const selectPet = (petId) => {
selectedPetId.value = petId;
}
const confirmSelection = () => {
if (!selectedPetId.value) return;
fastRequestStore.selectArchivePet(selectedPetId.value);
visible.value = false;
}
const columns = ref([
{
@@ -65,9 +55,9 @@ const columns = ref([
}
]);
</script>
<template>
<Dialog :modal="true" :closable="true" :dismissableMask="true" :maximizable="false" :style="{ width: '554px' , minHeight: '326px' }">
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="true" :maximizable="false" :style="{ width: '554px' , minHeight: '326px' }" class="fast-request-dialog">
<template #header>
<h3>انتخاب کیس از آرشیو</h3>
</template>
@@ -76,7 +66,9 @@ const columns = ref([
<p>در صورتیکه شماره مرجع یک کیس را در اختیار داشته باشید می‌توانید درخواست را برای کیس مربوطه نیز ثبت کنید.</p>
</div>
<div class="h-[180px] overflow-y-scroll">
<p v-if="!data.length" class="w-full py-8 text-center text-[11px] font-[400] text-[#7188AD]">کیسی در آرشیو شما ثبت نشده است.</p>
<TableGeneric
v-else
class="bg-white !rounded-2xl"
:data="data"
customStyle="!text-[11px] !font-normal"
@@ -88,25 +80,20 @@ const columns = ref([
>
<template #column-actions="{ data }">
<div class="flex items-center justify-end">
<UiButton severity="secondary" size="small">
<template #icon>
</template>
</UiButton>
<UiButton
:severity="selectedPetId === data.id ? 'success' : 'secondary'"
size="small"
:label="selectedPetId === data.id ? 'انتخاب شد' : 'انتخاب'"
@click="selectPet(data.id)"
/>
</div>
</template>
</TableGeneric>
</div>
<div class="w-full pt-3 flex items-center justify-end gap-2 border-t border-[#EBEBEB]">
<ui-button label="افزودن از طریق شناسه کیس" severity="secondary" @click="showChooseCaseNumber"/>
<ui-button label="تایید و انتخاب" severity="success" size="small" @click="handleNext" />
<ui-button label="تایید و انتخاب" severity="success" size="small" :disabled="!selectedPetId" @click="confirmSelection" />
</div>
<case-number v-model:visible="isChooseCaseNumber"></case-number>
<case-number v-model:visible="isChooseCaseNumber" @selected="visible = false"></case-number>
</Dialog>
</template>
<style scoped>
</style>
@@ -1,45 +1,43 @@
<script setup>
import Dialog from 'primevue/dialog';
import InputText from 'primevue/inputtext';
import UiButton from '../../ui/UiButton.vue';
import { ref } from 'vue';
import { useFastRequestStore } from '../../../store/FastRequest';
const visible = defineModel('visible');
const emit = defineEmits(['selected']);
const fastRequestStore = useFastRequestStore();
const caseNumber = ref(null);
const confirmCaseNumber = () => {
if (!caseNumber.value) return;
fastRequestStore.selectArchivePet(Number(caseNumber.value));
caseNumber.value = null;
visible.value = false;
emit('selected');
}
</script>
<template>
<Dialog :modal="true" :closable="true" :dismissableMask="true" :maximizable="false" :style="{ width: '408px' , minHeight: '210px' }">
<Dialog v-model:visible="visible" :modal="true" :closable="true" :dismissableMask="true" :maximizable="false" :style="{ width: '408px' , minHeight: '210px' }" class="fast-request-dialog">
<template #header>
<h3>افزودن کیس از طریق شناسه مرجع</h3>
</template>
<div class="w-full py-2 flex flex-col items-start justify-center border-t border-[#EBEBEB] text-[11px] font-[600]">
<p>لطفا شناسه کیس که از پزشک و یا سرپرست کیس دریافت کرده‌اید را وارد نمایید.</p>
</div>
<Form ref="formRef" v-slot="$form" class="p-4 grid grid-cols-1 !w-full">
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col">
<InputText
type="text"
placeholder="شناسه کیس"
size="large"/>
</FormField>
</Form>
<form class="p-4 grid grid-cols-1 w-full" @submit.prevent="confirmCaseNumber">
<InputText
v-model="caseNumber"
type="number"
placeholder="شناسه کیس"
size="large"
/>
</form>
<div class="w-full pt-3 flex items-center justify-end gap-2 border-t border-[#EBEBEB]">
<ui-button label="تایید و افزودن" severity="success" size="small"/>
<ui-button label="تایید و افزودن" severity="success" size="small" :disabled="!caseNumber" @click="confirmCaseNumber"/>
</div>
</Dialog>
</template>
<style scoped>
:deep(.p-inputtext::placeholder) {
color: #6E91C9;
}
:deep(.p-inputtext) {
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 400 !important;
}
</style>
@@ -0,0 +1,68 @@
<script setup>
import { ref } from 'vue';
import FileUpload from 'primevue/fileupload';
import Badge from 'primevue/badge';
import { formatFileSize } from '../../../utils/fileUtils';
const files = defineModel({ type: Array, default: () => [] });
defineProps({
title: {
type: String,
required: true
}
});
const fileUpload = ref(null);
const openFileDialog = () => {
fileUpload.value.choose();
}
const onSelectedFiles = (event) => {
files.value = event.files;
}
const onRemoveFile = (removeFileCallback, index) => {
removeFileCallback(index);
files.value = fileUpload.value.files;
}
</script>
<template>
<FileUpload
ref="fileUpload"
class="w-full !border-dashed !border-[#C1D0E8] !min-h-[100px] flex items-center justify-center"
name="files[]"
:customUpload="true"
:showUploadButton="false"
:showCancelButton="false"
:showChooseButton="false"
:multiple="true"
accept="image/*"
:maxFileSize="10485760"
@select="onSelectedFiles"
>
<template #content="{ files: selectedFiles, removeFileCallback }">
<div v-if="selectedFiles.length > 0" class="flex flex-wrap gap-4 pt-4">
<div v-for="(file, index) of selectedFiles" :key="file.name + file.type + file.size" class="relative w-[5rem] h-[5rem] rounded-lg border border-gray-200 flex flex-col items-center overflow-hidden">
<img role="presentation" :alt="file.name" :src="file.objectURL" class="absolute top-0 left-0 w-full h-full object-cover"/>
<div class="absolute bottom-0 left-0 w-full flex flex-col items-center gap-1 bg-white/80 p-1">
<span class="w-full text-[10px] font-[400] text-center text-ellipsis whitespace-nowrap overflow-hidden">{{ file.name }}</span>
<span class="text-[9px]">{{ formatFileSize(file.size) }}</span>
</div>
<button type="button" class="absolute top-1 left-1 w-4 h-4 flex items-center justify-center rounded-full bg-white text-[#7A1419] text-[10px] cursor-pointer" @click.stop="onRemoveFile(removeFileCallback, index)">✕</button>
</div>
<button type="button" class="w-[5rem] h-[5rem] rounded-lg border border-dashed border-[#C1D0E8] text-[11px] text-[#365891] cursor-pointer" @click="openFileDialog">افزودن</button>
</div>
</template>
<template #empty>
<div class="w-full flex items-center justify-center flex-col cursor-pointer" @click="openFileDialog">
<p class="text-[11px] font-[600] text-[#365891]">{{ title }}</p>
<p class="mt-1 mb-0 text-[11px] font-[400] text-[#365891]">
جهت آپلود تصویر کلیک کنید و یا فایل خود را کشیده و در این قسمت رها (Drag and Drop) کنید.
</p>
</div>
</template>
</FileUpload>
</template>
@@ -1,172 +1,86 @@
<script setup>
import Select from 'primevue/select';
import InputText from 'primevue/inputtext';
import { ref } from 'vue';
import ArchiveCase from './ArchiveCase.vue';
import UiInput from '../../ui/UiInput.vue';
import { useFastRequestStore } from '../../../store/FastRequest';
import { petTypes, petGenders, neuteredOptions } from '../../../data/fastRequestOptions';
const fastRequestStore = useFastRequestStore();
const isChooseArchive = ref(false);
const showChooseArchive = () => {
isChooseArchive.value = true;
}
const genders = ref([
{ name: 'نر', code: 'male' },
{ name: 'ماده', code: 'female' }
]);
const types = ref([
{ name: 'سگ', code: 'dog' },
{ name: 'گربه', code: 'cat' },
{ name: 'پرنده', code: 'bird' },
{ name: 'خرگوش', code: 'rabbit' },
{ name: 'همستر', code: 'hamster' },
{ name: 'خوکچه هندی', code: 'guinea_pig' },
{ name: 'لاک پشت', code: 'turtle' },
{ name: 'مار', code: 'snake' },
{ name: 'مارمولک', code: 'lizard' },
]);
const neuteredStatus = ref([
{ name: 'عقیم سازی شده', code: 'neutered' },
{ name: 'عقیم سازی نشده', code: 'not_neutered' }
]);
</script>
<template>
<div class="w-full space-y-2 py-6">
<div class="w-full h-[12px] flex items-center justify-between">
<p class="flex h-full items-center justify-start text-[11px] font-[600] text-[#3E4C63]">مشخصات کیس(حیوان) مورد نظر را وارد نمایید.</p>
<button @click="showChooseArchive" class="flex h-full items-center justify-end text-[11px] font-[400] text-[#7A1419] cursor-pointer">انتخاب از آرشیو</button>
<button type="button" @click="showChooseArchive" class="flex h-full items-center justify-end text-[11px] font-[400] text-[#7A1419] cursor-pointer">انتخاب از آرشیو</button>
</div>
<p class="flex items-center justify-start text-[11px] font-[600] text-[#576989]">
در صورتی که قبلا این کیس در پلتفرم رادیکال ثبت شده، برای بررسی سوابق قبلی از دکمه «انتخاب از آرشیو» استفاده نمایید.
</p>
<Form ref="formRef" v-slot="$form" class="p-4 grid grid-cols-2 gap-4 !w-full">
<FormField v-slot="$field" name="name" initialValue="" class="flex flex-col">
<!-- <InputText
<div v-if="fastRequestStore.requestCase.petId" class="w-full flex items-center justify-between border border-[#DEE3EB] rounded-[8px] p-3">
<p class="text-[11px] font-[600] text-[#3E4C63]">
کیس انتخاب‌شده از آرشیو:
<span v-if="fastRequestStore.selectedPet" class="text-[#7A1419]">{{ fastRequestStore.selectedPet.name }} ({{ fastRequestStore.selectedPet.type }})</span>
<span v-else class="text-[#7A1419]">شناسه {{ fastRequestStore.requestCase.petId }}</span>
</p>
<button type="button" @click="fastRequestStore.clearArchivePet" class="text-[11px] font-[400] text-[#7A1419] cursor-pointer">حذف و ثبت کیس جدید</button>
</div>
<form v-else class="p-4 grid grid-cols-2 gap-4 w-full">
<InputText
v-model="fastRequestStore.requestCase.name"
type="text"
placeholder="نام"
size="large"/> -->
<UiInput
type="text"
size="large"
prefixStart="نام"
direction="ltr"
/>
</FormField>
<FormField v-slot="$field" name="type" initialValue="" class="flex flex-col">
<Select
:options="types"
size="large"
/>
<Select
v-model="fastRequestStore.requestCase.type"
:options="petTypes"
optionLabel="name"
optionValue="code"
placeholder="نوع"
/>
</FormField>
<FormField v-slot="$field" name="breed" initialValue="" class="flex flex-col">
<InputText
type="text"
placeholder="نژاد"
size="large"/>
</FormField>
<FormField v-slot="$field" name="gender" initialValue="" class="flex flex-col">
<Select :options="genders" optionLabel="name" optionValue="code" placeholder="جنسیت" size="small"/>
</FormField>
<FormField v-slot="$field" name="age" initialValue="" class="flex flex-col">
<InputText
type="text"
placeholder="سن"
size="large"
/>
</FormField>
<FormField v-slot="$field" name="birthday" initialValue="" class="flex flex-col">
<InputText
type="text"
placeholder="وزن"
size="large"
/>
</FormField>
<FormField v-slot="$field" name="sterilized" initialValue="" class="flex flex-col">
<Select
:options="neuteredStatus"
/>
<InputText
v-model="fastRequestStore.requestCase.breed"
type="text"
placeholder="نژاد"
size="large"
/>
<Select
v-model="fastRequestStore.requestCase.gender"
:options="petGenders"
optionLabel="name"
optionValue="code"
placeholder="جنسیت"
size="small"
/>
<InputText
v-model="fastRequestStore.requestCase.age"
type="number"
placeholder="سن (سال)"
size="large"
/>
<InputText
v-model="fastRequestStore.requestCase.weight"
type="number"
placeholder="وزن (کیلوگرم)"
size="large"
/>
<Select
v-model="fastRequestStore.requestCase.sterilized"
:options="neuteredOptions"
optionLabel="name"
optionValue="code"
placeholder="وضعیت عقیم سازی"
size="small"
/>
</FormField>
</Form>
/>
</form>
<archive-case v-model:visible="isChooseArchive"></archive-case>
</div>
</template>
<style scoped>
:deep(.p-select) {
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
height: 37px !important;
display: flex !important;
align-items: center !important;
}
:deep(.p-select-label) {
padding: 0 12px;
line-height: 1;
display: flex;
align-items: center;
height: 100%;
}
:deep(.p-select-label.p-placeholder) {
color: #6E91C9 !important;
font-size: 11px !important;
font-weight: 400 !important;
}
:deep(.p-inputtext) {
font-size: 11px !important;
font-weight: 400 !important;
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
}
:deep(.p-inputtext::placeholder) {
color: #6E91C9;
}
:deep(.vpd-main) {
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
overflow: hidden !important;
height: 45px !important;
}
:deep(.vpd-input-group) {
border: 0 !important;
font-size: 16px !important;
border-radius: 8px !important;
border: 0 !important;
font-weight: 400 !important;
height: 45px !important;
}
:deep(.vpd-input-group .vpd-placeholder) {
font-size: 13px !important;
font-weight: 400 !important;
color: #3E4C63 !important;
}
:deep(.vpd-input-group input) {
border: 0 !important;
}
:deep(.vpd-icon-btn) {
background: #576989 !important;
}
:deep(.vpd-content) {
border-radius: 12px !important;
overflow: hidden !important;
border: 0 !important;
}
:deep(.vpd-wrapper) {
background-color: rgba(0,0,0,.1) !important;
}
</style>
@@ -1,256 +1,37 @@
<script setup>
import { ref } from 'vue';
import { usePrimeVue } from 'primevue/config';
import { useToast } from "primevue/usetoast";
const $primevue = usePrimeVue();
const toast = useToast();
const totalSize = ref(0);
const totalSizePercent = ref(0);
const files = ref([]);
const fileUpload = ref(null)
const openFileDialog = () => {
fileUpload.value.choose()
}
const onRemoveTemplatingFile = (file, removeFileCallback, index) => {
removeFileCallback(index);
totalSize.value -= parseInt(formatSize(file.size));
totalSizePercent.value = totalSize.value / 10;
};
const onClearTemplatingUpload = (clear) => {
clear();
totalSize.value = 0;
totalSizePercent.value = 0;
};
const onSelectedFiles = (event) => {
files.value = event.files;
files.value.forEach((file) => {
totalSize.value += parseInt(formatSize(file.size));
});
};
const uploadEvent = (callback) => {
totalSizePercent.value = totalSize.value / 10;
callback();
};
const onTemplatedUpload = () => {
toast.add({ severity: "info", summary: "Success", detail: "File Uploaded", life: 3000 });
};
const formatSize = (bytes) => {
const k = 1024;
const dm = 3;
const sizes = $primevue.config.locale.fileSizeTypes;
if (bytes === 0) {
return `0 ${sizes[0]}`;
}
const i = Math.floor(Math.log(bytes) / Math.log(k));
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
return `${formattedSize} ${sizes[i]}`;
};
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import FileUploadBox from './FileUploadBox.vue';
import { useFastRequestStore } from '../../../store/FastRequest';
import { priorities, modalityTitle } from '../../../data/fastRequestOptions';
const fastRequestStore = useFastRequestStore();
</script>
<template>
<div class="w-full">
<p class="text-[11px] font-[400] text-[#3E4C63] py-3">جزئیات درخواست و اطلاعات مورد نیاز را با دقت و حوصله وار نمایید.</p>
<p class="text-[11px] font-[400] text-[#3E4C63] py-3">جزئیات درخواست و اطلاعات مورد نیاز را با دقت و حوصله وارد نمایید.</p>
<div class="w-full grid grid-cols-2 gap-2 items-center justify-between">
<Select
:options="types"
optionLabel="name"
optionValue="code"
placeholder="نوع درخواست"
/>
<span class="text-[11px] font-[400] text-[#7188AD]">زمان تخمینی ثبت گزارش: ۷۲ ساعت</span>
<Select
v-model="fastRequestStore.requestDetails.priority"
:options="priorities"
optionLabel="name"
optionValue="code"
placeholder="اولویت درخواست"
/>
<span class="text-[11px] font-[400] text-[#7188AD]">زمان تخمینی ثبت گزارش: ۷۲ ساعت</span>
</div>
<div class="w-full h-[118px] mt-3">
<Textarea
v-model="value1"
size="small"
placeholder="شرح درخواست و توضیحات"
rows="3"
class="w-full h-full" />
v-model="fastRequestStore.requestDetails.note"
size="small"
placeholder="شرح درخواست و توضیحات"
rows="3"
class="w-full h-full" />
</div>
<div class="w-full grid grid-cols-2 gap-2 items-center justify-between my-4">
<FileUpload class="w-full !border-dashed !border-[#C1D0E8] !min-h-[100px] flex items-center justify-center" ref="fileUpload" name="files[]" :customUpload="true" :showUploadButton="false" :showCancelButton="false" :showChooseButton="false" url="/api/upload" @upload="onTemplatedUpload($event)" :multiple="true" accept="image/*" :maxFileSize="1000000" @select="onSelectedFiles">
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
<div class="flex flex-col gap-8 pt-4">
<div v-if="files.length > 0">
<div class="flex flex-wrap gap-4">
<div v-for="(file, index) of files" :key="file.name + file.type + file.size" class="relative w-[5rem] h-[5rem] rounded-lg border border-gray-200 bg-amber-100 flex flex-col items-center gap-4 overflow-hidden">
<div class="w-full h-full absolute top-0 left-0">
<img role="presentation" :alt="file.name" :src="file.objectURL" class="w-full h-full object-cover"/>
</div>
<div class="w-full h-full flex flex-col items-center justify-center gap-2 absolute bottom-0 left-0">
<span class="w-full px-2 text-xs font-[400] text-ellipsis whitespace-nowrap overflow-hidden">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<Badge value="Pending" severity="warn" />
</div>
</div>
</div>
</div>
<div v-if="uploadedFiles.length > 0">
<h5>Completed</h5>
<div class="flex flex-wrap gap-4">
<div v-for="(file, index) of uploadedFiles" :key="file.name + file.type + file.size" class="p-8 rounded-border flex flex-col border border-surface items-center gap-4">
<div>
<img role="presentation" :alt="file.name" :src="file.objectURL" width="100" height="50" />
</div>
<span class="font-semibold text-ellipsis max-w-60 whitespace-nowrap overflow-hidden">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<Badge value="Completed" class="mt-4" severity="success" />
<Button icon="pi pi-times" @click="removeUploadedFileCallback(index)" variant="outlined" rounded severity="danger" />
</div>
</div>
</div>
</div>
</template>
<template #empty>
<div class="w-full flex items-center justify-center flex-col cursor-pointer" @click="openFileDialog">
<Button
icon="pi pi-images"
rounded
variant="outlined"
severity="secondary"
class="!hidden"
@click.stop="openFileDialog"/>
<p class="text-[11px] font-[600] text-[#365891]">سوابق پزشکی قبلی (اختیاری)</p>
<p class="mt-1 mb-0 text-[11px] font-[400] text-[#365891]">
جهت آپلود تصویر کلیک کنید و یا فایل خود کشیده و در این قسمت رها (Drag and Drup) کنید.
</p>
</div>
</template>
</FileUpload>
<FileUpload class="w-full !border-dashed !border-[#C1D0E8] !min-h-[100px] flex items-center justify-center" ref="fileUpload" name="files[]" :customUpload="true" :showUploadButton="false" :showCancelButton="false" :showChooseButton="false" url="/api/upload" @upload="onTemplatedUpload($event)" :multiple="true" accept="image/*" :maxFileSize="1000000" @select="onSelectedFiles">
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
<div class="flex flex-col gap-8 pt-4">
<div v-if="files.length > 0">
<div class="flex flex-wrap gap-4">
<div v-for="(file, index) of files" :key="file.name + file.type + file.size" class="relative w-[5rem] h-[5rem] rounded-lg border border-gray-200 bg-amber-100 flex flex-col items-center gap-4 overflow-hidden">
<div class="w-full h-full absolute top-0 left-0">
<img role="presentation" :alt="file.name" :src="file.objectURL" class="w-full h-full object-cover"/>
</div>
<div class="w-full h-full flex flex-col items-center justify-center gap-2 absolute bottom-0 left-0">
<span class="w-full px-2 text-xs font-[400] text-ellipsis whitespace-nowrap overflow-hidden">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<Badge value="Pending" severity="warn" />
</div>
</div>
</div>
</div>
<div v-if="uploadedFiles.length > 0">
<h5>Completed</h5>
<div class="flex flex-wrap gap-4">
<div v-for="(file, index) of uploadedFiles" :key="file.name + file.type + file.size" class="p-8 rounded-border flex flex-col border border-surface items-center gap-4">
<div>
<img role="presentation" :alt="file.name" :src="file.objectURL" width="100" height="50" />
</div>
<span class="font-semibold text-ellipsis max-w-60 whitespace-nowrap overflow-hidden">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<Badge value="Completed" class="mt-4" severity="success" />
<Button icon="pi pi-times" @click="removeUploadedFileCallback(index)" variant="outlined" rounded severity="danger" />
</div>
</div>
</div>
</div>
</template>
<template #empty>
<div class="w-full flex items-center justify-center flex-col cursor-pointer" @click="openFileDialog">
<Button
icon="pi pi-images"
rounded
variant="outlined"
severity="secondary"
class="!hidden"
@click.stop="openFileDialog"/>
<p class="text-[11px] font-[600] text-[#365891]">فایل «CT Scan»</p>
<p class="mt-1 mb-0 text-[11px] font-[400] text-[#365891]">
جهت آپلود تصویر کلیک کنید و یا فایل خود کشیده و در این قسمت رها (Drag and Drup) کنید.
</p>
</div>
</template>
</FileUpload>
<div class="w-full grid grid-cols-2 gap-2 items-start justify-between my-4">
<FileUploadBox v-model="fastRequestStore.historyFiles" title="سوابق پزشکی قبلی (اختیاری)" />
<FileUploadBox v-model="fastRequestStore.scanFiles" :title="`فایل «${modalityTitle(fastRequestStore.requestType)}»`" />
</div>
</div>
</template>
<style scoped>
:deep(.p-select) {
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
height: 37px !important;
display: flex !important;
align-items: center !important;
}
:deep(.p-select-label) {
padding: 0 12px;
line-height: 1;
display: flex;
align-items: center;
height: 100%;
}
:deep(.p-select-label.p-placeholder) {
color: #6E91C9 !important;
font-size: 11px !important;
font-weight: 400 !important;
}
:deep(.p-textarea) {
border: 1px solid #DEE3EB !important;
border-radius: 8px !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
}
:deep(.p-textarea::placeholder) {
color: #6E91C9 !important;
font-size: 13px !important;
font-weight: 400 !important;
}
.p-fileupload-content {
pointer-events: auto !important;
}
:deep(.p-fileupload-choose-button) {
display: none !important;
}
:deep(.p-fileupload-header) {
display: none !important;
}
:deep(.p-fileupload) {
width: 100% !important;
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
:deep(.p-fileupload-advanced) {
border: none !important;
border: 2px solid #C1D0E8 !important;
border-style: dashed !important;
border-radius: 6px !important;
}
</style>
@@ -1,29 +1,35 @@
<script setup>
import { useFastRequestStore } from '../../../store/FastRequest';
const fastRequestStore = useFastRequestStore();
const selectDoctor = (doctorId) => {
fastRequestStore.doctorId = doctorId;
}
</script>
<template>
<div class="w-full h-full grid grid-cols-3 gap-2 items-center justify-between py-4">
<div class="w-full bg-white h-[134px] flex flex-col items-center justify-center border-[2px] border-[#576989] rounded-[12px]">
<button
type="button"
@click="selectDoctor(null)"
class="w-full bg-white h-[134px] flex flex-col items-center justify-center rounded-[12px] cursor-pointer"
:class="fastRequestStore.doctorId === null ? 'border-[2px] border-[#576989]' : 'border-[1px] border-[#DEE3EB]'"
>
<h3 class="text-[13px] font-[600] text-[#576989]">انتخاب خودکار</h3>
<p class="w-[190px] text-center text-[11px] font-[600] text-[#3E4C63]">انتخاب در دسترس‌ ترین پزشک جهت بررسی سریع درخواست شما</p>
</div>
<div class="w-full bg-white h-[134px] flex flex-col items-center justify-center border-[1px] border-[#DEE3EB] rounded-[12px]">
<img src="/public/images/profile_default.png" alt="doctor" class="w-[65px] h-[65px] rounded-[50px] object-contain ">
<h3 class="text-[13px] font-[600] text-[#576989]">دکتر حسین حسینی</h3>
<p class="text-center text-[11px] font-[600] text-[#7188AD]">متخصص جراحی پوست و مو</p>
</div>
<div class="w-full bg-white h-[134px] flex flex-col items-center justify-center border-[1px] border-[#DEE3EB] rounded-[12px]">
<img src="/public/images/profile_default.png" alt="doctor" class="w-[65px] h-[65px] rounded-[50px] object-contain ">
<h3 class="text-[13px] font-[600] text-[#576989]">دکتر امین حسینی</h3>
<p class="text-center text-[11px] font-[600] text-[#7188AD]">متخصص و رادیولوژیست</p>
</div>
</button>
<button
v-for="doctor in fastRequestStore.doctors"
:key="doctor.id"
type="button"
@click="selectDoctor(doctor.id)"
class="w-full bg-white h-[134px] flex flex-col items-center justify-center rounded-[12px] cursor-pointer"
:class="fastRequestStore.doctorId === doctor.id ? 'border-[2px] border-[#576989]' : 'border-[1px] border-[#DEE3EB]'"
>
<img :src="doctor.profile_url || '/images/profile_default.png'" :alt="doctor.fullname" class="w-[65px] h-[65px] rounded-full object-cover">
<h3 class="text-[13px] font-[600] text-[#576989]">{{ doctor.fullname }}</h3>
<p class="text-center text-[11px] font-[600] text-[#7188AD]">{{ doctor.specialty }}</p>
</button>
</div>
</template>
<style scoped>
</style>
@@ -1,15 +1,22 @@
<script setup>
import { computed } from 'vue';
import { useFastRequestStore } from '../../../store/FastRequest';
import { modalityTitle, priorityTitle } from '../../../data/fastRequestOptions';
const fastRequestStore = useFastRequestStore();
const caseName = computed(() => fastRequestStore.selectedPet?.name || fastRequestStore.requestCase.name || 'کیس جدید');
const caseNumber = computed(() => fastRequestStore.requestCase.petId || 'جدید');
const doctorName = computed(() => fastRequestStore.selectedDoctor?.fullname || 'اولین پزشک در دسترس');
</script>
<template>
<div class="w-full h-full flex flex-col items-start justify-between gap-8 py-8">
<p class="text-[13px] font-[400] text-[#3E4C63]">
شما در حال تکمیل فرایند ثبت درخواست
<span class="font-[600] text-[#7A1419]">((CT Scan))</span>
شما در حال تکمیل فرایند ثبت درخواست
<span class="font-[600] text-[#7A1419]">(({{ modalityTitle(fastRequestStore.requestType) }}))</span>
با اولویت
<span class="font-[600] text-[#7A1419]">((اورژانسی))</span> برای کیس <span class="font-[600] text-[#7A1419]">((مایلو))</span> با شماره کیس <span class="font-[600] text-[#7A1419]">((۱۲۳۴۵۶))</span> می‌باشید. درخواست شما در ابتدا توسط کارشناسان ما بررسی و در صورت تایید به <span class="font-[600] text-[#7A1419]">((اولین پزشک در دسترس))</span> جهت بررسی و گزارش نویسی ارجاع خواهد شد.
<span class="font-[600] text-[#7A1419]">(({{ priorityTitle(fastRequestStore.requestDetails.priority) }}))</span> برای کیس <span class="font-[600] text-[#7A1419]">(({{ caseName }}))</span> با شماره کیس <span class="font-[600] text-[#7A1419]">(({{ caseNumber }}))</span> می‌باشید. درخواست شما در ابتدا توسط کارشناسان ما بررسی و در صورت تایید به <span class="font-[600] text-[#7A1419]">(({{ doctorName }}))</span> جهت بررسی و گزارش نویسی ارجاع خواهد شد.
</p>
<p class="text-[13px] font-[400] text-[#3E4C63]">
وضعیت و مراحل پرونده از طریق <span class="font-[600] text-[#7A1419]">((پیامک))</span> به شما اطلاع‌رسانی خواهد شد.
@@ -17,9 +24,3 @@
</div>
</template>
<style scoped>
</style>
@@ -1,23 +1,21 @@
<script setup>
import RequestCard from '../../../components/dashboard/RequestCard.vue';
import RequestCard from '../../../components/dashboard/RequestCard.vue';
import { modalities } from '../../../data/fastRequestOptions';
</script>
<template>
<div class="w-full space-y-3 py-6">
<p class="h-[17px] flex items-center justify-start text-[11px] font-[600] text-[#3E4C63]">نوع درخواست خود را مشخص کنید</p>
<div class="w-full h-[133px] grid grid-cols-5 gap-3 justify-start">
<RequestCard value="ct-scan" title="CT Scan" icon="ct-scan" severity="primary" :selectable="true"/>
<RequestCard value="mri" title="MRI" icon="mri" severity="primary" :selectable="true"/>
<RequestCard value="sonography" title="سونوگرافی" icon="sonography" severity="primary" :selectable="true"/>
<RequestCard value="echo" title="اکوکاردیوگرافی" icon="echo" severity="primary" :selectable="true"/>
<RequestCard value="radiology" title="رادیولوژی" icon="radiology" severity="primary" :selectable="true"/>
<RequestCard
v-for="modality in modalities"
:key="modality.code"
:value="modality.code"
:title="modality.title"
:icon="modality.icon"
severity="primary"
:selectable="true"
/>
</div>
</div>
</template>
<style scoped>
</style>
@@ -0,0 +1,39 @@
import { modalityTitle } from './fastRequestOptions'
import { convertEnglishToPersian } from '../utils/numberUtils'
export const caseColumns = [
{
title: 'شماره پرونده',
key: 'code'
},
{
title: 'وضعیت',
key: 'status'
},
{
title: 'نوع درخواست',
key: 'modality',
render: (row) => modalityTitle(row.modality)
},
{
title: 'درخواست‌کننده',
key: 'requester'
},
{
title: 'تاریخ ثبت',
key: 'submitted_at',
render: (row) => row.submitted_at ? convertEnglishToPersian(row.submitted_at) : '-'
},
{
title: 'فرصت رسیدگی',
key: 'processing_deadline',
render: (row) => row.processing_deadline ? convertEnglishToPersian(row.processing_deadline) : '-'
},
{
title: '',
key: 'actions',
width: 80,
style: 'grid grid-cols-1',
slot: true,
}
]
@@ -0,0 +1,44 @@
export const modalities = [
{ code: 'ct-scan', title: 'CT Scan', icon: 'ct-scan' },
{ code: 'mri', title: 'MRI', icon: 'mri' },
{ code: 'sonography', title: 'سونوگرافی', icon: 'sonography' },
{ code: 'echo', title: 'اکوکاردیوگرافی', icon: 'echo' },
{ code: 'radiology', title: 'رادیولوژی', icon: 'radiology' },
]
export const petTypes = [
{ name: 'سگ', code: 'Dog' },
{ name: 'گربه', code: 'Cat' },
{ name: 'پرنده', code: 'Bird' },
{ name: 'ماهی', code: 'Fish' },
{ name: 'خرگوش', code: 'Rabbit' },
{ name: 'همستر', code: 'Hamster' },
{ name: 'خوکچه هندی', code: 'GuineaPig' },
{ name: 'لاک پشت', code: 'Turtle' },
{ name: 'مار', code: 'Snake' },
{ name: 'مارمولک', code: 'Lizard' },
{ name: 'سایر', code: 'Other' },
]
export const petGenders = [
{ name: 'نر', code: 'Male' },
{ name: 'ماده', code: 'Female' },
]
export const neuteredOptions = [
{ name: 'عقیم سازی شده', code: true },
{ name: 'عقیم سازی نشده', code: false },
]
export const priorities = [
{ name: 'عادی', code: 'Medium' },
{ name: 'اورژانسی', code: 'High' },
]
export const modalityTitle = (code) => {
return modalities.find((modality) => modality.code === code)?.title || code
}
export const priorityTitle = (code) => {
return priorities.find((priority) => priority.code === code)?.name || code
}
+94 -12
View File
@@ -1,22 +1,104 @@
import { step } from '@primeuix/themes/aura/stepper'
import { defineStore } from 'pinia'
const emptyCase = () => ({
petId: null,
name: null,
type: null,
gender: null,
breed: null,
age: null,
weight: null,
sterilized: null,
})
export const useFastRequestStore = defineStore('FastRequestStore', {
state: () => ({
endpoints: {
create: '/dashboard/fast-requests/create',
store: '/dashboard/fast-requests',
},
step: 1,
requestType: null,
requestCase: {
name: null,
species: null,
gender: null,
age: null,
weight: null,
neutered: null,
requestCase: emptyCase(),
requestDetails: {
priority: null,
note: null,
},
requestDetails: null,
requestDoctor: null,
requestReview: null,
historyFiles: [],
scanFiles: [],
doctorId: null,
pets: [],
doctors: [],
submitting: false,
submittedDocument: null,
}),
getters: {
selectedPet: (state) => state.pets.find((pet) => pet.id === state.requestCase.petId) || null,
selectedDoctor: (state) => state.doctors.find((doctor) => doctor.id === state.doctorId) || null,
canProceed: (state) => {
switch (state.step) {
case 1:
return !!state.requestType
case 2:
return !!state.requestCase.petId || (!!state.requestCase.name && !!state.requestCase.type)
case 3:
return !!state.requestDetails.priority
default:
return true
}
},
},
actions: {
async fetchCreateData() {
const { data } = await window.axios.get(this.endpoints.create)
this.pets = data.pets
this.doctors = data.doctors
},
selectArchivePet(petId) {
this.requestCase = emptyCase()
this.requestCase.petId = petId
},
clearArchivePet() {
this.requestCase.petId = null
},
nextStep() {
if (this.step < 5) this.step++
},
prevStep() {
if (this.step > 1) this.step--
},
async submit() {
this.submitting = true
try {
const formData = new FormData()
formData.append('modality', this.requestType)
formData.append('priority', this.requestDetails.priority)
if (this.requestDetails.note) formData.append('note', this.requestDetails.note)
}
if (this.requestCase.petId) {
formData.append('pet_id', this.requestCase.petId)
} else {
Object.entries(this.requestCase).forEach(([key, value]) => {
if (key !== 'petId' && value !== null && value !== '') {
formData.append(`pet[${key}]`, key === 'sterilized' ? (value ? 1 : 0) : value)
}
})
}
if (this.doctorId) formData.append('doctor_id', this.doctorId)
this.historyFiles.forEach((file) => formData.append('history_files[]', file))
this.scanFiles.forEach((file) => formData.append('scan_files[]', file))
const { data } = await window.axios.post(this.endpoints.store, formData)
this.submittedDocument = data.document
return data.document
} finally {
this.submitting = false
}
},
reset() {
this.$reset()
},
},
})
+13
View File
@@ -0,0 +1,13 @@
const sizes = ['B', 'KB', 'MB', 'GB']
export const formatFileSize = (bytes) => {
if (!bytes) {
return `0 ${sizes[0]}`
}
const k = 1024
const i = Math.floor(Math.log(bytes) / Math.log(k))
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(1))
return `${formattedSize} ${sizes[i]}`
}