initial commit

This commit is contained in:
Inshal
2024-10-25 19:58:19 +05:00
commit 2046156f90
1558 changed files with 210706 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
<script setup>
import { passwordValidator } from '@/@core/utils/validators';
import avatar1 from '@images/avatars/avatar-1.png';
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 accountData = {
avatarImg: avatar1,
name: '',
first_name: '',
last_name: '',
email: '',
phone_no: '',
password: '',
status: '',
role_id: ''
}
const accountDataLocal = ref(structuredClone(accountData))
const refInputEl = ref()
const ImageBase64 = ref();
const statusList = ref([
{ name: 'Active', abbreviation: '1' },
{ name: 'De-Active', abbreviation: '0' },
]);
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', 'addedMessage'])
const formatPhoneNumber = () => {
// Remove non-numeric characters from the input
const numericValue = accountDataLocal.value.phone_no.replace(/\D/g, '');
// Apply formatting logic
if (numericValue.length <= 10) {
accountDataLocal.value.phone_no = 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);
accountDataLocal.value.phone_no = 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 first_name = ref()
const last_name = ref()
const email = ref()
const status = ref()
const password = ref()
const phone_no = ref()
const changeAvatar = file => {
const fileReader = new FileReader()
const { files } = file.target
if (files && files.length) {
fileReader.readAsDataURL(files[0])
fileReader.onload = () => {
if (typeof fileReader.result === 'string') {
accountDataLocal.value.avatarImg = fileReader.result
}
ImageBase64.value = fileReader.result.split(',')[1];
}
}
}
const onSubmit = async () => {
const { valid } = await refVForm.value.validate()
if (valid) {
await store.dispatch('adminUserSave', {
name: accountDataLocal.value.first_name,
email: accountDataLocal.value.email,
last_name: accountDataLocal.value.last_name,
phone_no: accountDataLocal.value.phone_no,
password: accountDataLocal.value.password,
role_id: accountDataLocal.value.role_id,
profile_pic: ImageBase64.value, //ecelData,
})
if (!store.getters.getErrorMsg) {
emit('addedMessage', 'success')
accountDataLocal.value.email = null
accountDataLocal.value.name = null
accountDataLocal.value.first_name = null
accountDataLocal.value.phone_no = null
accountDataLocal.value.last_name = null
accountDataLocal.value.password = null
accountDataLocal.value.role_id = null
ImageBase64.value = null
accountDataLocal.value.avatarImg = avatar1
emit('update:isDrawerOpen', false)
}
}
}
const resetForm = () => {
refVForm.value?.reset()
emit('update:isDrawerOpen', false)
}
const roleData = ref([]);
const useSortedRole = () => {
const isLoading = ref(false);
const error = ref(null);
const sortedRole = computed(() => {
const allOption = { id: '', role: 'Select Any' };
const sortedData = roleData.value.slice().sort((a, b) => {
return a.role.localeCompare(b.role);
});
return [allOption, ...sortedData];
});
const fetchRoleData = async () => {
isLoading.value = true;
error.value = null;
try {
await store.dispatch('getAllRolesList');
roleData.value = store.getters.getRolesList || [];
console.log('Fetched Role data:', roleData.value);
} catch (e) {
console.error('Error fetching Role data:', e);
error.value = 'Failed to fetch Role data';
} finally {
isLoading.value = false;
}
};
onBeforeMount(fetchRoleData);
return { sortedRole, isLoading, error, fetchRoleData };
};
const { sortedRole, isLoading, error, fetchRoleData } = useSortedRole();
</script>
<template>
<VNavigationDrawer :model-value="props.isDrawerOpen" temporary location="end" width="800"
@update:model-value="handleDrawerModelValueUpdate">
<!-- 👉 Header -->
<AppDrawerHeaderSection title="Add User" @cancel="$emit('update:isDrawerOpen', false)" />
<VDivider />
<VCard flat>
<PerfectScrollbar :options="{ wheelPropagation: false }" class="h-100">
<VCardText style="block-size: calc(100vh - 5rem);">
<VForm ref="refVForm" @submit.prevent="">
<VRow>
<div class="d-flex mb-10">
<!-- 👉 Avatar -->
<VAvatar rounded size="100" class="me-6" :image="accountDataLocal.avatarImg" />
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn color="primary" @click="refInputEl?.click()">
<VIcon icon="ri-upload-cloud-line" class="d-sm-none" />
<span class="d-none d-sm-block">Upload Logo</span>
</VBtn>
<input ref="refInputEl" type="file" name="file" accept=".jpeg,.png,.jpg,GIF" hidden
@input="changeAvatar">
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</div>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.first_name" label="First Name" :rules="[requiredValidator]"
placeholder="First Name" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.last_name" label="Last Name" :rules="[requiredValidator]"
placeholder="Last Name" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.email" label="Email Address"
:rules="[requiredValidator, emailValidator]" placeholder="johndoe@email.com" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.password" label="Password" placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'ri-eye-off-line' : 'ri-eye-line'"
@click:append-inner="isPasswordVisible = !isPasswordVisible"
:rules="[requiredValidator, passwordValidator]" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.phone_no" label="Phone Number" placeholder="+1 (917) 543-9876"
:rules="[requiredPhone, validUSAPhone]" @input="formatPhoneNumber" max="14"
pattern="^\(\d{3}\) \d{3}-\d{4}$" />
</VCol>
<VCol cols="12" md="6">
<VAutocomplete v-model="accountDataLocal.role_id" label="Role" placeholder="Role" density="comfortable"
:items="sortedRole" item-title="role" item-value="id" :loading="isLoading" :error-messages="error"
:rules="[requiredValidator]" />
</VCol>
<VCol cols="12">
<div class="d-flex justify-start">
<VBtn type="submit" color="primary" class="me-4" @click="onSubmit">
Save
</VBtn>
<VBtn color="error" variant="outlined" @click="resetForm">
Discard
</VBtn>
</div>
</VCol>
</VRow>
</VForm>
</VCardText>
</PerfectScrollbar>
</VCard>
</VNavigationDrawer>
</template>
<style lang="scss">
.v-navigation-drawer__content {
overflow-y: hidden !important;
}
</style>

View File

@@ -0,0 +1,265 @@
<script setup>
import avatar1 from '@images/avatars/avatar-1.png';
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,
},
userData: {
type: Object,
required: true,
}
})
const accountData = {
avatarImg: avatar1,
name: '',
first_name: '',
last_name: '',
email: '',
phone_no: '',
password: '',
status: '',
role_id: ''
}
const itemId = ref()
const accountDataLocal = ref(structuredClone(accountData))
const refInputEl = ref()
const ImageBase64 = ref();
const statusList = ref([
{ name: 'Active', abbreviation: '1' },
{ name: 'De-Active', abbreviation: '0' },
]);
const roleData = ref([]);
const useSortedRole = () => {
const isLoading = ref(false);
const error = ref(null);
const sortedRole = computed(() => {
const allOption = { id: '', role: 'Select Any' };
const sortedData = roleData.value.slice().sort((a, b) => {
return a.role.localeCompare(b.role);
});
return [allOption, ...sortedData];
});
const fetchRoleData = async () => {
isLoading.value = true;
error.value = null;
try {
await store.dispatch('getAllRolesList');
roleData.value = store.getters.getRolesList || [];
console.log('Fetched Role data:', roleData.value);
} catch (e) {
console.error('Error fetching Role data:', e);
error.value = 'Failed to fetch Role data';
} finally {
isLoading.value = false;
}
};
onBeforeMount(fetchRoleData);
return { sortedRole, isLoading, error, fetchRoleData };
};
const { sortedRole, isLoading, error, fetchRoleData } = useSortedRole();
const sortedStates = computed(() => {
return states.value.slice().sort((a, b) => {
return a.name.localeCompare(b.name);
});
});
const getUser = computed(async () => {
if (props.userData) {
itemId.value = props.userData.id
accountDataLocal.value.email = props.userData.email
accountDataLocal.value.name = props.userData.name
accountDataLocal.value.first_name = props.userData.name
accountDataLocal.value.phone_no = props.userData.phone_no
accountDataLocal.value.last_name = props.userData.last_name
accountDataLocal.value.role_id = props.userData.role_id
accountDataLocal.value.avatarImg = props.userData.image_path
}
});
const isPasswordVisible = ref(false)
const emit = defineEmits(['update:isDrawerOpen', 'addedMessage'])
const formatPhoneNumber = () => {
// Remove non-numeric characters from the input
const numericValue = accountDataLocal.value.phone_no.replace(/\D/g, '');
// Apply formatting logic
if (numericValue.length <= 10) {
accountDataLocal.value.phone_no = 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);
accountDataLocal.value.phone_no = 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 changeAvatar = file => {
const fileReader = new FileReader()
const { files } = file.target
if (files && files.length) {
fileReader.readAsDataURL(files[0])
fileReader.onload = () => {
if (typeof fileReader.result === 'string') {
accountDataLocal.value.avatarImg = fileReader.result
}
ImageBase64.value = fileReader.result.split(',')[1];
}
}
}
const onSubmit = async () => {
const { valid } = await refVForm.value.validate()
if (valid) {
await store.dispatch('adminUpdateUser', {
id: itemId.value,
name: accountDataLocal.value.first_name,
email: accountDataLocal.value.email,
last_name: accountDataLocal.value.last_name,
phone_no: accountDataLocal.value.phone_no,
role_id: accountDataLocal.value.role_id,
password: accountDataLocal.value.password,
profile_pic: ImageBase64.value, //ecelData,
})
if (!store.getters.getErrorMsg) {
emit('addedMessage', 'success')
accountDataLocal.value.email = null
accountDataLocal.value.name = null
accountDataLocal.value.first_name = null
accountDataLocal.value.phone_no = null
accountDataLocal.value.role_id = null
accountDataLocal.value.last_name = null
accountDataLocal.value.password = null
ImageBase64.value = null
accountDataLocal.value.avatarImg = avatar1
emit('update:isDrawerOpen', false)
}
}
}
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="Edit User" @cancel="$emit('update:isDrawerOpen', false)" />
<VDivider />
<VCard flat>
<PerfectScrollbar :options="{ wheelPropagation: false }" class="h-100">
<VCardText style="block-size: calc(100vh - 5rem);" v-if="getUser">
<VForm ref="refVForm" @submit.prevent="">
<VRow>
<div class="d-flex mb-10">
<!-- 👉 Avatar -->
<VAvatar rounded size="100" class="me-6" :image="accountDataLocal.avatarImg" />
<!-- 👉 Upload Photo -->
<form class="d-flex flex-column justify-center gap-4">
<div class="d-flex flex-wrap gap-4">
<VBtn color="primary" @click="refInputEl?.click()">
<VIcon icon="ri-upload-cloud-line" class="d-sm-none" />
<span class="d-none d-sm-block">Upload Logo</span>
</VBtn>
<input ref="refInputEl" type="file" name="file" accept=".jpeg,.png,.jpg,GIF" hidden
@input="changeAvatar">
</div>
<p class="text-body-1 mb-0">
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</div>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.first_name" label="First Name" :rules="[requiredValidator]"
placeholder="First Name" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.last_name" label="Last Name" :rules="[requiredValidator]"
placeholder="Last Name" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.email" label="Email Address"
:rules="[requiredValidator, emailValidator]" placeholder="johndoe@email.com" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.password" label="Password" placeholder="············"
:type="isPasswordVisible ? 'text' : 'password'"
:append-inner-icon="isPasswordVisible ? 'ri-eye-off-line' : 'ri-eye-line'"
@click:append-inner="isPasswordVisible = !isPasswordVisible" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountDataLocal.phone_no" label="Phone Number" placeholder="+1 (917) 543-9876"
:rules="[requiredPhone, validUSAPhone]" @input="formatPhoneNumber" max="14"
pattern="^\(\d{3}\) \d{3}-\d{4}$" />
</VCol>
<VCol cols="12" md="6">
<VAutocomplete v-model="accountDataLocal.role_id" label="Role" placeholder="Role" density="comfortable"
:items="sortedRole" item-title="role" item-value="id" :loading="isLoading" :error-messages="error"
:rules="[requiredValidator]" />
</VCol>
<VCol cols="12">
<div class="d-flex justify-start">
<VBtn type="submit" color="primary" class="me-4" @click="onSubmit">
Save
</VBtn>
<VBtn color="error" variant="outlined" @click="resetForm">
Discard
</VBtn>
</div>
</VCol>
</VRow>
</VForm>
</VCardText>
</PerfectScrollbar>
</VCard>
</VNavigationDrawer>
</template>
<style lang="scss">
.v-navigation-drawer__content {
overflow-y: hidden !important;
}
</style>

View File

@@ -0,0 +1,335 @@
<script setup>
import API from '@/api';
import { ADMIN_USER_LIST_API } from '@/constants';
import AddUser from '@/pages/admin-users/AddUser.vue';
import EditUser from '@/pages/admin-users/EditUser.vue';
import debounce from 'lodash.debounce';
import { useStore } from 'vuex';
const isAddCustomerDrawerOpen = ref(false)
const isEditCustomerDrawerOpen = ref(false)
const store = useStore()
const editDialog = ref(false)
const addDialog = ref(false)
const deleteDialog = ref(false)
const search = ref('')
const refVForm = ref(null)
const refVFormAdd = ref(null)
const selectDropdown = ref(false)
const selectdataList = ref(null)
const defaultItem = ref({
id: -1,
title: '',
slug: '',
list_one_title: '',
list_sub_title: '',
list_two_title: '',
price: '',
currency:""
})
const requiredImageValidator = (value) => !!value || 'Please select an image file.'
const requiredExcelValidator = (value) => !!value || 'Please select an Excel file.'
const imageFile = ref(null)
const excelFile = ref(null)
const editedItem = ref(defaultItem.value)
const editedIndex = ref(-1)
const medicineList = ref([])
const isLoading = ref(false)
const currencySign = ref('$');
// headers
const headers = [
{
title: 'ID',
key: 'id',
},
{
title: 'Name',
key: 'name',
},
{
title: 'email',
key: 'email',
},
{
title: 'Phone',
key: 'phone_no',
},
{
title: 'ACTIONS',
key: 'actions',
},
]
const resolveStatusVariant = status => {
if (status === 1)
return {
color: 'primary',
text: 'Current',
}
else if (status === 2)
return {
color: 'success',
text: 'Professional',
}
else if (status === 3)
return {
color: 'error',
text: 'Rejected',
}
else if (status === 4)
return {
color: 'warning',
text: 'Resigned',
}
else
return {
color: 'info',
text: 'Applied',
}
}
const editItem = async (item) => {
isEditCustomerDrawerOpen.value = true
await store.dispatch('singleUserEdit', {
id: item.id,
})
editedItem.value = store.getters.getSingleUser
console.log(store.getters.getSingleUser)
// editDialog.value = true
}
const addItem = item => {
addDialog.value = true
}
const deleteItem = item => {
editedIndex.value = medicineList.value.indexOf(item)
editedItem.value = { ...item }
deleteDialog.value = true
}
const closeDelete = () => {
deleteDialog.value = false
editedIndex.value = -1
editedItem.value = { ...defaultItem.value }
}
const deleteItemConfirm = async () => {
await store.dispatch('medicineDelete',{
id: editedItem.value.id
})
closeDelete()
loadItems({ page: 1, itemsPerPage: itemsPerPage.value, sortBy: [] });
defaultItem.value.id= null
defaultItem.value.title= null
defaultItem.value.slug= null
defaultItem.value.list_one_title= null
defaultItem.value.list_sub_title= null
defaultItem.value.list_two_title= null
defaultItem.value.price =null
defaultItem.value.currency=null
}
const serverItems = ref([]);
const loading = ref(true);
const totalItems = ref(0);
const itemsPerPage = ref(5);
const loadItems = debounce( async ( { page, itemsPerPage, sortBy }) => {
const payload = {
page,
itemsPerPage,
sortBy,
filters:{
},
search:search.value,
}
console.log("records",page, itemsPerPage, sortBy , ADMIN_USER_LIST_API);
loading.value = true;
const data = await API.getDataTableRecord(ADMIN_USER_LIST_API, payload, headers);
console.log('patientData',data);
serverItems.value = data.items;
totalItems.value = data.total;
loading.value = false;
},500);
onMounted(() => {
})
const handleParentAdded = async (msg) => {
if (msg == 'success') {
store.dispatch('updateIsLoading', true)
loadItems({ page: 1, itemsPerPage: itemsPerPage.value, sortBy: [] });
store.dispatch('updateIsLoading', false)
}
// You can also trigger a toast or snackbar here to show the message
// For example, if using Vuetify:
// showSnackbar(msg)
}
</script>
<template>
<v-row>
<v-col cols="12" md="12">
<VCard title="Users">
<VCardText >
<VRow>
<VCol cols="12" md="8" class="d-flex align-center">
<VBtn color="primary" prepend-icon="ri-add-line" @click="isAddCustomerDrawerOpen = !isAddCustomerDrawerOpen" v-if="$can('read', 'Admin Add')">
New User
</VBtn>
</VCol>
<VCol cols="12" md="4" class="d-flex justify-end">
<VTextField
v-model="search"
label="Search"
placeholder="Search ..."
append-inner-icon="ri-search-line"
single-line
hide-details
dense
outlined
/>
</VCol>
</VRow>
</VCardText>
<v-data-table-server
v-model:items-per-page="itemsPerPage"
:headers="headers"
:items="serverItems"
:items-length="totalItems"
:loading="loading"
:search="search"
@update:options="loadItems"
>
<!-- full name -->
<template #item.name="{ item }">
<div class="d-flex align-center">
<!-- avatar -->
<VAvatar
size="32"
:color="item.image_path ? '' : 'primary'"
:class="item.image_path ? '' : 'v-avatar-light-bg primary--text'"
:variant="!item.image_path ? 'tonal' : undefined"
>
<VImg
v-if="item.image_path"
:src="item.image_path"
/>
<span
v-else
class="text-sm"
>{{ avatarText(item.name) }}</span>
</VAvatar>
<div class="d-flex flex-column ms-3">
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.name +' '+item.last_name }}</span>
</div>
</div>
</template>
<!-- status -->
<template #item.status="{ item }">
<VChip
:color="resolveStatusVariant(item.status).color"
density="comfortable"
>
{{ resolveStatusVariant(item.status).text }}
</VChip>
</template>
<!-- Actions -->
<template #item.actions="{ item }">
<div class="d-flex gap-1">
<IconBtn
size="small"
@click="editItem(item )"
v-if="$can('read', 'Admin Edit')"
>
<VIcon icon="ri-pencil-line" />
</IconBtn>
<IconBtn
size="small"
@click="deleteItem(item)"
style="display: none;"
>
<VIcon icon="ri-delete-bin-line" />
</IconBtn>
</div>
</template>
</v-data-table-server>
<!-- </VDataTable> -->
</VCard>
</v-col>
<AddUser v-model:is-drawer-open="isAddCustomerDrawerOpen" @addedMessage="handleParentAdded" />
<EditUser v-model:is-drawer-open="isEditCustomerDrawerOpen" :user-data="store.getters.getSingleUser" @addedMessage="handleParentAdded" />
</v-row>
<!-- 👉 Delete Dialog -->
<VDialog
v-model="deleteDialog"
max-width="500px"
>
<VCard>
<VCardTitle>
Are you sure you want to delete this item?
</VCardTitle>
<VCardActions>
<VSpacer />
<VBtn
color="error"
variant="outlined"
@click="closeDelete"
>
Cancel
</VBtn>
<VBtn
color="success"
variant="elevated"
@click="deleteItemConfirm"
>
OK
</VBtn>
<VSpacer />
</VCardActions>
</VCard>
</VDialog>
</template>
<style scoped>
.custom-button {
width: 100%;
height: 48px; /* This value should match the height of your input fields */
}
</style>