572 lines
20 KiB
Vue
572 lines
20 KiB
Vue
<script setup>
|
|
import { PerfectScrollbar } from 'vue3-perfect-scrollbar';
|
|
import { VForm } from 'vuetify/components/VForm';
|
|
import { useStore } from 'vuex';
|
|
const store = useStore()
|
|
const props = defineProps({
|
|
isDrawerOpen: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
})
|
|
const items = [
|
|
{
|
|
title: 'Basic',
|
|
subtitle: '',
|
|
},
|
|
{
|
|
title: 'Personal',
|
|
subtitle: '',
|
|
},
|
|
{
|
|
title: 'Professional',
|
|
subtitle: '',
|
|
},
|
|
]
|
|
const currentStep = ref(0)
|
|
const requiredAvailability = (value) =>
|
|
!!value || 'Availability is required';
|
|
|
|
|
|
|
|
const timeOptions = computed(() => {
|
|
const options = [];
|
|
|
|
// Loop through the hours from 12 to 23 (for 12:00 PM to 11:00 PM)
|
|
for (let hour = 12; hour < 24; hour++) {
|
|
// Loop through the minutes (0 and 30)
|
|
for (let minute of [0, 30]) {
|
|
// Construct the time string
|
|
const timeString = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
options.push(timeString);
|
|
}
|
|
}
|
|
|
|
// Add the time option for 24:00 (midnight)
|
|
options.push('24:00');
|
|
|
|
return options;
|
|
});
|
|
const selectedStateName = (abbreviation) => {
|
|
const selectedState = states.value.find(
|
|
(s) => s.abbreviation === abbreviation
|
|
);
|
|
return selectedState ? selectedState.name : abbreviation;
|
|
};
|
|
const states = ref([
|
|
{ name: 'Alabama', abbreviation: 'AL' },
|
|
{ name: 'Alaska', abbreviation: 'AK' },
|
|
{ name: 'Arizona', abbreviation: 'AZ' },
|
|
{ name: 'Arkansas', abbreviation: 'AR' },
|
|
{ name: 'Howland Island', abbreviation: 'UM-84' },
|
|
{ name: 'Delaware', abbreviation: 'DE' },
|
|
{ name: 'Maryland', abbreviation: 'MD' },
|
|
{ name: 'Baker Island', abbreviation: 'UM-81' },
|
|
{ name: 'Kingman Reef', abbreviation: 'UM-89' },
|
|
{ name: 'New Hampshire', abbreviation: 'NH' },
|
|
{ name: 'Wake Island', abbreviation: 'UM-79' },
|
|
{ name: 'Kansas', abbreviation: 'KS' },
|
|
{ name: 'Texas', abbreviation: 'TX' },
|
|
{ name: 'Nebraska', abbreviation: 'NE' },
|
|
{ name: 'Vermont', abbreviation: 'VT' },
|
|
{ name: 'Jarvis Island', abbreviation: 'UM-86' },
|
|
{ name: 'Hawaii', abbreviation: 'HI' },
|
|
{ name: 'Guam', abbreviation: 'GU' },
|
|
{ name: 'United States Virgin Islands', abbreviation: 'VI' },
|
|
{ name: 'Utah', abbreviation: 'UT' },
|
|
{ name: 'Oregon', abbreviation: 'OR' },
|
|
{ name: 'California', abbreviation: 'CA' },
|
|
{ name: 'New Jersey', abbreviation: 'NJ' },
|
|
{ name: 'North Dakota', abbreviation: 'ND' },
|
|
{ name: 'Kentucky', abbreviation: 'KY' },
|
|
{ name: 'Minnesota', abbreviation: 'MN' },
|
|
{ name: 'Oklahoma', abbreviation: 'OK' },
|
|
{ name: 'Pennsylvania', abbreviation: 'PA' },
|
|
{ name: 'New Mexico', abbreviation: 'NM' },
|
|
{ name: 'American Samoa', abbreviation: 'AS' },
|
|
{ name: 'Illinois', abbreviation: 'IL' },
|
|
{ name: 'Michigan', abbreviation: 'MI' },
|
|
{ name: 'Virginia', abbreviation: 'VA' },
|
|
{ name: 'Johnston Atoll', abbreviation: 'UM-67' },
|
|
{ name: 'West Virginia', abbreviation: 'WV' },
|
|
{ name: 'Mississippi', abbreviation: 'MS' },
|
|
{ name: 'Northern Mariana Islands', abbreviation: 'MP' },
|
|
{ name: 'United States Minor Outlying Islands', abbreviation: 'UM' },
|
|
{ name: 'Massachusetts', abbreviation: 'MA' },
|
|
{ name: 'Connecticut', abbreviation: 'CT' },
|
|
{ name: 'Florida', abbreviation: 'FL' },
|
|
{ name: 'District of Columbia', abbreviation: 'DC' },
|
|
{ name: 'Midway Atoll', abbreviation: 'UM-71' },
|
|
{ name: 'Navassa Island', abbreviation: 'UM-76' },
|
|
{ name: 'Indiana', abbreviation: 'IN' },
|
|
{ name: 'Wisconsin', abbreviation: 'WI' },
|
|
{ name: 'Wyoming', abbreviation: 'WY' },
|
|
{ name: 'South Carolina', abbreviation: 'SC' },
|
|
{ name: 'South Dakota', abbreviation: 'SD' },
|
|
{ name: 'Montana', abbreviation: 'MT' },
|
|
{ name: 'North Carolina', abbreviation: 'NC' },
|
|
{ name: 'Palmyra Atoll', abbreviation: 'UM-95' },
|
|
{ name: 'Puerto Rico', abbreviation: 'PR' },
|
|
{ name: 'Colorado', abbreviation: 'CO' },
|
|
{ name: 'Missouri', abbreviation: 'MO' },
|
|
{ name: 'New York', abbreviation: 'NY' },
|
|
{ name: 'Maine', abbreviation: 'ME' },
|
|
{ name: 'Tennessee', abbreviation: 'TN' },
|
|
{ name: 'Georgia', abbreviation: 'GA' },
|
|
{ name: 'Louisiana', abbreviation: 'LA' },
|
|
{ name: 'Nevada', abbreviation: 'NV' },
|
|
{ name: 'Iowa', abbreviation: 'IA' },
|
|
{ name: 'Idaho', abbreviation: 'ID' },
|
|
{ name: 'Rhode Island', abbreviation: 'RI' },
|
|
{ name: 'Washington', abbreviation: 'WA' },
|
|
{ name: 'Ohio', abbreviation: 'OH' },
|
|
// ... (add the rest of the states)
|
|
]);
|
|
const sortedStates = computed(() => {
|
|
return states.value.slice().sort((a, b) => {
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
});
|
|
const isPasswordVisible = ref(false)
|
|
const emit = defineEmits(['update:isDrawerOpen','providerAdded'])
|
|
const formatPhoneNumber = () => {
|
|
// Remove non-numeric characters from the input
|
|
const numericValue = phone.value.replace(/\D/g, '');
|
|
|
|
// Apply formatting logic
|
|
if (numericValue.length <= 10) {
|
|
phone.value = numericValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
|
|
} else {
|
|
// Limit the input to a maximum of 14 characters
|
|
const truncatedValue = numericValue.slice(0, 10);
|
|
phone.value = truncatedValue.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
|
|
}
|
|
};
|
|
const handleDrawerModelValueUpdate = val => {
|
|
emit('update:isDrawerOpen', val)
|
|
}
|
|
const genders = ref([
|
|
{ name: 'Male', abbreviation: 'Male' },
|
|
{ name: 'Female', abbreviation: 'Female' },
|
|
{ name: 'Other', abbreviation: 'Other' },
|
|
]);
|
|
const refVForm = ref()
|
|
const basicInfoForm = ref()
|
|
const personalInfoForm = ref()
|
|
const professionalInfoForm = ref()
|
|
|
|
const first_name = ref()
|
|
const last_name = ref()
|
|
const email = ref()
|
|
|
|
const city = ref()
|
|
const state = ref([])
|
|
const zip_code = ref()
|
|
const country = ref()
|
|
const phone = ref()
|
|
const dob = ref()
|
|
const gender = ref()
|
|
const password = ref()
|
|
const zip = ref(null)
|
|
const medicalLicenseNumbers = ref({})
|
|
const stateAddress = ref('AL')
|
|
const years_of_experience = ref(null)
|
|
const specialty = ref(null)
|
|
const availabilityFrom = ref(null)
|
|
const availabilityTo = ref(null)
|
|
const home_address = ref(null)
|
|
const errors = ref({
|
|
name: undefined,
|
|
email: undefined,
|
|
})
|
|
const isBillingAddress = ref(false)
|
|
const getCurrentDate = () => {
|
|
const today = new Date();
|
|
console.log("today", today);
|
|
const year = today.getFullYear();
|
|
let month = today.getMonth() + 1;
|
|
let day = today.getDate();
|
|
|
|
// Format the date to match the input type="date" format
|
|
month = month < 10 ? `0${month}` : month;
|
|
day = day < 10 ? `0${day}` : day;
|
|
|
|
return `${month}-${day}-${year}`;
|
|
};
|
|
const calculateAge = (dateOfBirth) => {
|
|
const today = new Date();
|
|
const birthDate = new Date(dateOfBirth);
|
|
let age = today.getFullYear() - birthDate.getFullYear();
|
|
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
|
|
if (
|
|
monthDiff < 0 ||
|
|
(monthDiff === 0 && today.getDate() < birthDate.getDate())
|
|
) {
|
|
age--;
|
|
}
|
|
|
|
return age;
|
|
};
|
|
const onSubmit = async () => {
|
|
const { valid } = await professionalInfoForm.value.validate()
|
|
if (valid) {
|
|
let formaData = {
|
|
first_name: first_name.value,
|
|
last_name: last_name.value,
|
|
email: email.value,
|
|
password: password.value,
|
|
home_address: home_address.value,
|
|
city: city.value,
|
|
state: stateAddress.value,
|
|
zip_code: zip.value,
|
|
medical_license_number: medicalLicenseNumbers.value,
|
|
years_of_experience: years_of_experience.value,
|
|
specialty: specialty.value,
|
|
gender: gender.value,
|
|
practice_state: state.value,
|
|
phone: phone.value,
|
|
availabilityFrom: availabilityFrom.value,
|
|
availabilityTo: availabilityTo.value
|
|
}
|
|
console.log(formaData)
|
|
await store.dispatch('providerAdd', {
|
|
first_name: first_name.value,
|
|
last_name: last_name.value,
|
|
email: email.value,
|
|
password: password.value,
|
|
home_address: home_address.value,
|
|
city: city.value,
|
|
state: stateAddress.value,
|
|
zip_code: zip.value,
|
|
medical_license_number: medicalLicenseNumbers.value,
|
|
years_of_experience: years_of_experience.value,
|
|
specialty: specialty.value,
|
|
gender: gender.value,
|
|
practice_state: state.value,
|
|
phone: phone.value,
|
|
availabilityFrom: availabilityFrom.value,
|
|
availabilityTo: availabilityTo.value
|
|
})
|
|
|
|
if (!store.getters.getErrorMsg) {
|
|
emit('providerAdded', 'success')
|
|
first_name.value = null
|
|
last_name.value = null
|
|
email.value = null
|
|
password.value = null
|
|
home_address.value = null
|
|
stateAddress.value = null
|
|
zip.value = null
|
|
city.value = null
|
|
//medical_license_number.value = {}
|
|
specialty.value = null
|
|
years_of_experience.value = null
|
|
phone.value = null
|
|
availabilityFrom.value = null
|
|
availabilityTo.value = null
|
|
state.value = []
|
|
medicalLicenseNumbers.value=null
|
|
emit('update:isDrawerOpen', false)
|
|
}
|
|
currentStep.value=0
|
|
|
|
}
|
|
}
|
|
|
|
const validateStep = async () => {
|
|
const { valid } = await refVForm.value.validate()
|
|
console.log(refVForm.value)
|
|
return valid
|
|
}
|
|
|
|
const handleNext = async (stepTitle) => {
|
|
console.log(stepTitle)
|
|
if (stepTitle == "Basic") {
|
|
const { valid } = await basicInfoForm.value.validate()
|
|
if (valid) {
|
|
currentStep.value++
|
|
}
|
|
}
|
|
if (stepTitle == "Personal") {
|
|
const { valid } = await personalInfoForm.value.validate()
|
|
if (valid) {
|
|
currentStep.value++
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
const resetForm = () => {
|
|
refVForm.value?.reset()
|
|
emit('update:isDrawerOpen', false)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<VNavigationDrawer
|
|
:model-value="props.isDrawerOpen"
|
|
temporary
|
|
location="end"
|
|
width="800"
|
|
@update:model-value="handleDrawerModelValueUpdate"
|
|
>
|
|
<!-- 👉 Header -->
|
|
<AppDrawerHeaderSection
|
|
title="Add Provider"
|
|
@cancel="$emit('update:isDrawerOpen', false)"
|
|
/>
|
|
<VDivider />
|
|
|
|
<VCard flat>
|
|
<PerfectScrollbar
|
|
:options="{ wheelPropagation: false }"
|
|
class="h-100"
|
|
>
|
|
<VCardText style="block-size: calc(100vh - 5rem);">
|
|
|
|
<VRow>
|
|
|
|
|
|
<VCol
|
|
cols="12"
|
|
md="12"
|
|
class="auth-card-v2 d-flex align-center justify-center pa-10"
|
|
style="background-color: rgb(var(--v-theme-surface));"
|
|
>
|
|
<VCard flat >
|
|
|
|
<AppStepper
|
|
v-model:current-step="currentStep"
|
|
:items="items"
|
|
:direction="$vuetify.display.smAndUp ? 'horizontal' : 'vertical'"
|
|
class="mb-12 w-100"
|
|
|
|
/>
|
|
|
|
|
|
<VWindow
|
|
v-model="currentStep"
|
|
class="disable-tab-transition"
|
|
style="max-inline-size: 850px;"
|
|
>
|
|
|
|
|
|
<VWindowItem>
|
|
|
|
<h4 class="text-h4">
|
|
Basic Information
|
|
</h4>
|
|
<p class="mb-5">
|
|
Enter Your Basic Information
|
|
</p>
|
|
<VForm
|
|
ref="basicInfoForm"
|
|
@submit.prevent=""
|
|
>
|
|
<VRow>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="first_name" label="First Name" type="name"
|
|
:rules="[requiredFirstName]" :error-messages="errors.first_name"
|
|
density="comfortable" />
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="last_name" label="Last Name" type="name"
|
|
:rules="[requiredLastName]" :error-messages="errors.last_name"
|
|
density="comfortable" />
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="email" label="Email" type="email"
|
|
:rules="[requiredEmail, emailValidator]" :error-messages="errors.email"
|
|
density="comfortable" />
|
|
</VCol>
|
|
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="password" label="Password" placeholder="············"
|
|
:rules="[requiredPassword,passwordValidator]" :type="isPasswordVisible ? 'text' : 'password'"
|
|
:append-inner-icon="isPasswordVisible ? 'bx-show' : 'bx-hide'"
|
|
@click:append-inner="isPasswordVisible = !isPasswordVisible"
|
|
|
|
density="comfortable" />
|
|
</VCol>
|
|
</VRow>
|
|
</VForm>
|
|
</VWindowItem>
|
|
<VWindowItem>
|
|
<h4 class="text-h4">
|
|
Personal Information
|
|
</h4>
|
|
<p class="mb-5">
|
|
Enter Your Personal Information
|
|
</p>
|
|
<VForm
|
|
ref="personalInfoForm"
|
|
@submit.prevent=""
|
|
>
|
|
<VRow>
|
|
|
|
<VCol cols="12" md="12">
|
|
<VTextField v-model="home_address" label="Home Address" :rules="[requiredAddress]"
|
|
density="comfortable" />
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="city" label="City" density="comfortable" :rules="[requiredCity]"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
|
|
<v-autocomplete clearable v-model="stateAddress" label="State" density="comfortable"
|
|
:items="sortedStates" item-title="name" item-value="abbreviation"
|
|
:rules="[requiredState]" >
|
|
</v-autocomplete>
|
|
</VCol>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="zip" label="Zip" density="comfortable" :rules="[requiredZip]"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="phone" label="Phone Number" pattern="^\(\d{3}\) \d{3}-\d{4}$"
|
|
:rules="[requiredPhone, validUSAPhone]"
|
|
placeholder="i.e. (000) 000-0000" @input="formatPhoneNumber" max="14"
|
|
density="comfortable" />
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<v-select v-model="gender" label="Gender" :items="genders" item-title="name" item-value="abbreviation"
|
|
:rules="[requiredValidator]" density="comfortable">
|
|
</v-select>
|
|
</VCol>
|
|
</VRow>
|
|
</VForm>
|
|
</VWindowItem>
|
|
|
|
|
|
<VWindowItem>
|
|
<h4 class="text-h4">
|
|
Professional Information
|
|
</h4>
|
|
<p class="mb-5">
|
|
Enter Your Professional Information
|
|
</p>
|
|
<VForm
|
|
ref="professionalInfoForm"
|
|
@submit.prevent=""
|
|
>
|
|
<VRow>
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="years_of_experience" label="Years of Experience" type="number"
|
|
:rules="[requiredYearsofExperience]"
|
|
density="comfortable" />
|
|
</VCol>
|
|
|
|
|
|
<VCol cols="12" md="6">
|
|
<VTextField v-model="specialty" label="Practice or Provider's Specialty" type="text"
|
|
:rules="[requiredSpecialty]"
|
|
density="comfortable" />
|
|
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
|
|
<VSelect v-model="availabilityFrom" label="Availability From" :items="timeOptions"
|
|
density="comfortable" :rules="[requiredAvailability]"
|
|
/>
|
|
</VCol>
|
|
<VCol cols="12" md="6">
|
|
<!-- timeOptions.filter((time) => time > availabilityFrom) -->
|
|
<VSelect v-model="availabilityTo" label="Availability To" :items="timeOptions"
|
|
density="comfortable" :rules="[requiredAvailability]"
|
|
/>
|
|
|
|
</VCol>
|
|
<VCol cols="12">
|
|
<v-select v-model="state" label="Region Currently Practicing" :items="sortedStates"
|
|
item-title="name" item-value="abbreviation" multiple :rules="[requiredState]"
|
|
density="comfortable">
|
|
</v-select>
|
|
</VCol>
|
|
|
|
<VCol cols="12" v-if="state.length > 0">
|
|
<div class="d-flex align-center mb-4">
|
|
<VIcon icon="ri-award-fill" size="32" class="mr-2" />
|
|
<h4 class="section-title">Medical License Number</h4>
|
|
</div>
|
|
<div v-for="(stat, index) in state" :key="index">
|
|
<VTextField v-model="medicalLicenseNumbers[stat]"
|
|
:label="`${selectedStateName(stat)}`" :rules="[requiredLicenseNumber]"
|
|
density="comfortable"
|
|
:error-messages="errors[`medicalLicenseNumber_${selectedStateName(stat)}`]"
|
|
class="mb-2" />
|
|
</div>
|
|
</VCol>
|
|
</VRow>
|
|
</VForm>
|
|
</VWindowItem>
|
|
|
|
|
|
</VWindow>
|
|
|
|
<div class="d-flex flex-wrap justify-space-between gap-x-4 gap-y-2 mt-5">
|
|
<VBtn
|
|
color="secondary"
|
|
variant="outlined"
|
|
:disabled="currentStep === 0"
|
|
@click="currentStep--"
|
|
>
|
|
<VIcon
|
|
icon="ri-arrow-left-line"
|
|
start
|
|
class="flip-in-rtl"
|
|
/>
|
|
Previous
|
|
</VBtn>
|
|
|
|
<VBtn
|
|
v-if="items.length - 1 === currentStep"
|
|
color="success"
|
|
append-icon="ri-check-line"
|
|
@click="onSubmit"
|
|
>
|
|
submit
|
|
</VBtn>
|
|
|
|
<VBtn
|
|
v-else
|
|
@click="handleNext(items[currentStep].title)"
|
|
>
|
|
Next
|
|
|
|
<VIcon
|
|
icon="ri-arrow-right-line"
|
|
end
|
|
class="flip-in-rtl"
|
|
/>
|
|
</VBtn>
|
|
</div>
|
|
|
|
</VCard>
|
|
</VCol>
|
|
</VRow>
|
|
|
|
</VCardText>
|
|
</PerfectScrollbar>
|
|
</VCard>
|
|
</VNavigationDrawer>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.v-navigation-drawer__content {
|
|
overflow-y: hidden !important;
|
|
}
|
|
.app-stepper .v-slide-group__content .stepper-step-line:not(.vertical) {
|
|
min-inline-size: 7rem !important;
|
|
inline-size: 100% !important;
|
|
}
|
|
|
|
</style>
|