initial commit
This commit is contained in:
280
resources/js/pages/user-permission/AddUser.vue
Normal file
280
resources/js/pages/user-permission/AddUser.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<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:''
|
||||
}
|
||||
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,
|
||||
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
|
||||
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="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">
|
||||
|
||||
|
||||
</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>
|
296
resources/js/pages/user-permission/EditUser.vue
Normal file
296
resources/js/pages/user-permission/EditUser.vue
Normal file
@@ -0,0 +1,296 @@
|
||||
<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:''
|
||||
}
|
||||
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 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.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,
|
||||
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.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="Add 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">
|
||||
|
||||
|
||||
</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>
|
179
resources/js/pages/user-permission/FolderTreeItem.vue
Normal file
179
resources/js/pages/user-permission/FolderTreeItem.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div class="folder-tree-item">
|
||||
<div
|
||||
@click="toggleExpanded"
|
||||
:class="['folder-tree-item-header', { 'is-expanded': isExpanded }]"
|
||||
>
|
||||
<v-checkbox
|
||||
:model-value="isSelected"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="togglePermission(item)"
|
||||
@click.stop
|
||||
class="folder-tree-checkbox"
|
||||
></v-checkbox>
|
||||
<v-icon v-if="item.children" :class="isExpanded ? 'folder-yellow' : 'folder-light-gray'">
|
||||
{{ isExpanded ? 'ri-folder-open-line' : 'ri-folder-line' }}
|
||||
</v-icon>
|
||||
<v-icon v-else color="blue-grey">ri-file-list-line</v-icon>
|
||||
<span class="folder-tree-item-name">{{ item.text }}</span>
|
||||
</div>
|
||||
<v-expand-transition>
|
||||
<div v-if="item.children && isExpanded" class="folder-tree-item-children">
|
||||
<folder-tree-item
|
||||
v-for="child in item.children"
|
||||
:key="child.id"
|
||||
:item="child"
|
||||
:selected-permissions="selectedPermissions"
|
||||
@toggle-permission="togglePermission"
|
||||
/>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FolderTreeItem',
|
||||
props: {
|
||||
item: Object,
|
||||
selectedPermissions: Array,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const isExpanded = ref(false);
|
||||
|
||||
const isSelected = computed(() => {
|
||||
if (props.item.children) {
|
||||
return props.item.children.every(child => {
|
||||
if (child.children) {
|
||||
return child.children.every(subChild =>
|
||||
(subChild.state && subChild.state.selected) ||
|
||||
props.selectedPermissions.includes(subChild.id)
|
||||
);
|
||||
}
|
||||
return (child.state && child.state.selected) ||
|
||||
props.selectedPermissions.includes(child.id);
|
||||
});
|
||||
}
|
||||
return (props.item.state && props.item.state.selected) ||
|
||||
props.selectedPermissions.includes(props.item.id);
|
||||
});
|
||||
|
||||
const isIndeterminate = computed(() => {
|
||||
if (props.item.children) {
|
||||
const totalCount = props.item.children.reduce((count, child) => {
|
||||
if (child.children) {
|
||||
return count + child.children.length;
|
||||
}
|
||||
return count + 1;
|
||||
}, 0);
|
||||
|
||||
const selectedCount = props.item.children.reduce((count, child) => {
|
||||
if (child.children) {
|
||||
return count + child.children.filter(subChild =>
|
||||
(subChild.state && subChild.state.selected) ||
|
||||
props.selectedPermissions.includes(subChild.id)
|
||||
).length;
|
||||
}
|
||||
return count + ((child.state && child.state.selected) ||
|
||||
props.selectedPermissions.includes(child.id) ? 1 : 0);
|
||||
}, 0);
|
||||
|
||||
return selectedCount > 0 && selectedCount < totalCount;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
const toggleExpanded = () => {
|
||||
if (props.item.children) {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePermission = (item) => {
|
||||
const newSelected = !isSelected.value;
|
||||
|
||||
const updateItemState = (item, selected) => {
|
||||
if (item.children) {
|
||||
item.children.forEach(child => {
|
||||
if (child.state) {
|
||||
child.state.selected = selected;
|
||||
} else {
|
||||
child.state = { selected };
|
||||
}
|
||||
updateItemState(child, selected);
|
||||
});
|
||||
} else {
|
||||
if (item.state) {
|
||||
item.state.selected = selected;
|
||||
} else {
|
||||
item.state = { selected };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateItemState(item, newSelected);
|
||||
|
||||
const updateSelectedPermissions = (item, selected) => {
|
||||
if (item.children) {
|
||||
item.children.forEach(child => updateSelectedPermissions(child, selected));
|
||||
} else {
|
||||
const index = props.selectedPermissions.indexOf(item.id);
|
||||
if (selected && index === -1) {
|
||||
props.selectedPermissions.push(item.id);
|
||||
} else if (!selected && index > -1) {
|
||||
props.selectedPermissions.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateSelectedPermissions(item, newSelected);
|
||||
|
||||
emit('update:selected-permissions', [...props.selectedPermissions]);
|
||||
};
|
||||
|
||||
return {
|
||||
isExpanded,
|
||||
isSelected,
|
||||
isIndeterminate,
|
||||
toggleExpanded,
|
||||
togglePermission,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.folder-tree-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.folder-tree-item-header.is-expanded .folder-yellow {
|
||||
color: #ffca28;
|
||||
}
|
||||
|
||||
.folder-tree-item-header .folder-light-gray {
|
||||
color: #90a4ae;
|
||||
}
|
||||
|
||||
.folder-tree-item-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-item-children {
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
138
resources/js/pages/user-permission/TreeItem.vue
Normal file
138
resources/js/pages/user-permission/TreeItem.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="folder-tree-item">
|
||||
<div
|
||||
@click="toggleExpanded"
|
||||
:class="['folder-tree-item-header', { 'is-expanded': isExpanded }]"
|
||||
>
|
||||
<v-checkbox
|
||||
:model-value="isSelected"
|
||||
@change="togglePermission(item)"
|
||||
@click.stop
|
||||
class="folder-tree-checkbox"
|
||||
></v-checkbox>
|
||||
<v-icon v-if="item.children" :class="isExpanded ? 'folder-yellow' : 'folder-light-gray'">
|
||||
{{ isExpanded ? 'ri-folder-open-line' : 'ri-folder-line' }}
|
||||
</v-icon>
|
||||
<v-icon v-else color="blue-grey">ri-file-list-line</v-icon>
|
||||
<span class="folder-tree-item-name">{{ item.text }}</span>
|
||||
</div>
|
||||
<v-expand-transition>
|
||||
<div v-if="item.children && isExpanded" class="folder-tree-item-children">
|
||||
<folder-tree-item
|
||||
v-for="child in item.children"
|
||||
:key="child.id"
|
||||
:item="child"
|
||||
:selected-permissions="selectedPermissions"
|
||||
@update:selected-permissions="updateSelectedPermissions"
|
||||
/>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FolderTreeItem',
|
||||
props: {
|
||||
item: Object,
|
||||
selectedPermissions: Array,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const isExpanded = ref(false);
|
||||
|
||||
const isSelected = computed(() => {
|
||||
if (props.item.children) {
|
||||
return props.item.children.every(child => props.selectedPermissions.includes(child.id));
|
||||
}
|
||||
return props.selectedPermissions.includes(props.item.id);
|
||||
});
|
||||
|
||||
const toggleExpanded = () => {
|
||||
if (props.item.children) {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePermission = (item) => {
|
||||
const toggleChildren = (item, isSelected) => {
|
||||
if (item.children) {
|
||||
item.children.forEach(child => {
|
||||
if (isSelected) {
|
||||
if (!props.selectedPermissions.includes(child.id)) {
|
||||
props.selectedPermissions.push(child.id);
|
||||
}
|
||||
} else {
|
||||
const index = props.selectedPermissions.indexOf(child.id);
|
||||
if (index > -1) {
|
||||
props.selectedPermissions.splice(index, 1);
|
||||
}
|
||||
}
|
||||
toggleChildren(child, isSelected);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (item.children) {
|
||||
const isSelectedValue = !isSelected.value;
|
||||
toggleChildren(item, isSelectedValue);
|
||||
} else {
|
||||
const index = props.selectedPermissions.indexOf(item.id);
|
||||
if (index > -1) {
|
||||
props.selectedPermissions.splice(index, 1);
|
||||
} else {
|
||||
props.selectedPermissions.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:selected-permissions', props.selectedPermissions);
|
||||
};
|
||||
|
||||
const updateSelectedPermissions = (newSelectedPermissions) => {
|
||||
emit('update:selected-permissions', newSelectedPermissions);
|
||||
};
|
||||
|
||||
return {
|
||||
isExpanded,
|
||||
isSelected,
|
||||
toggleExpanded,
|
||||
togglePermission,
|
||||
updateSelectedPermissions,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.folder-tree-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.folder-tree-item-header.is-expanded .folder-yellow {
|
||||
color: #ffca28;
|
||||
}
|
||||
|
||||
.folder-tree-item-header .folder-light-gray {
|
||||
color: #90a4ae;
|
||||
}
|
||||
|
||||
.folder-tree-item-name {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.folder-tree-item-children {
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
195
resources/js/pages/user-permission/assign-permission.vue
Normal file
195
resources/js/pages/user-permission/assign-permission.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import FolderTreeItem from './FolderTreeItem.vue';
|
||||
const route = useRoute();
|
||||
const store = useStore()
|
||||
const loading = ref(true)
|
||||
const searchQuery = ref('')
|
||||
const permissionsTree = ref([])
|
||||
const selectedPermissions = ref([])
|
||||
|
||||
const dummyPermissionsTree = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'User Management',
|
||||
children: [
|
||||
{ id: 2, name: 'Create User' },
|
||||
{ id: 3, name: 'Edit User' },
|
||||
{ id: 4, name: 'Delete User' },
|
||||
{ id: 5, name: 'View User' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Content Management',
|
||||
children: [
|
||||
{ id: 7, name: 'Create Post' },
|
||||
{ id: 8, name: 'Edit Post' },
|
||||
{ id: 9, name: 'Delete Post' },
|
||||
{ id: 10, name: 'Publish Post' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Settings',
|
||||
children: [
|
||||
{ id: 12, name: 'General Settings' },
|
||||
{ id: 13, name: 'Security Settings' },
|
||||
{ id: 14, name: 'Email Settings' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const selectedPermissionsIds = computed(() => {
|
||||
return selectedPermissions.value.join(',')
|
||||
})
|
||||
|
||||
const filteredPermissionsTree = computed(() => {
|
||||
if (!searchQuery.value) return permissionsTree.value
|
||||
|
||||
const searchLower = searchQuery.value.toLowerCase()
|
||||
|
||||
function filterTree(nodes) {
|
||||
return nodes.reduce((filtered, node) => {
|
||||
const nameMatch = node.text.toLowerCase().includes(searchLower)
|
||||
if (node.children) {
|
||||
const filteredChildren = filterTree(node.children)
|
||||
if (nameMatch || filteredChildren.length) {
|
||||
filtered.push({
|
||||
...node,
|
||||
children: filteredChildren
|
||||
})
|
||||
}
|
||||
} else if (nameMatch) {
|
||||
filtered.push(node)
|
||||
}
|
||||
return filtered
|
||||
}, [])
|
||||
}
|
||||
|
||||
return filterTree(permissionsTree.value)
|
||||
})
|
||||
|
||||
const fetchPermissions = async () => {
|
||||
await store.dispatch('permissionsRole', {
|
||||
id: route.params.id,
|
||||
});
|
||||
let permission = store.getters.getPermissionsRole;
|
||||
console.log(permission);
|
||||
|
||||
// Process the permissions to add state if not present
|
||||
const processPermissions = (items) => {
|
||||
return items.map(item => {
|
||||
if (item.children) {
|
||||
item.children = processPermissions(item.children);
|
||||
}
|
||||
if (!item.state) {
|
||||
item.state = { selected: false };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
};
|
||||
|
||||
permissionsTree.value = processPermissions(permission.children);
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
function searchPermissions() {
|
||||
// The filtering is handled by the computed property filteredPermissionsTree
|
||||
}
|
||||
|
||||
const savePermissions1 = async () => {
|
||||
console.log('Saving permissions:', selectedPermissionsIds.value)
|
||||
await store.dispatch('permissionsUpdate', {
|
||||
id: route.params.id,
|
||||
permisssions:selectedPermissions.value.join(',')
|
||||
})
|
||||
console.log('Saving permissions:', selectedPermissionsIds.value)
|
||||
// You would typically make an API call here to save the permissions
|
||||
}
|
||||
const savePermissions = async () => {
|
||||
const getSelectedIds = (items) => {
|
||||
return items.reduce((acc, item) => {
|
||||
if (item.children) {
|
||||
acc.push(...getSelectedIds(item.children));
|
||||
} else if (item.state && item.state.selected) {
|
||||
acc.push(item.id);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const selectedIds = getSelectedIds(permissionsTree.value);
|
||||
console.log('Saving permissions:', selectedIds.join(','));
|
||||
await store.dispatch('permissionsUpdate', {
|
||||
id: route.params.id,
|
||||
permisssions: selectedIds.join(',')
|
||||
});
|
||||
};
|
||||
function togglePermission(id) {
|
||||
const index = selectedPermissions.value.indexOf(id)
|
||||
if (index === -1) {
|
||||
selectedPermissions.value.push(id)
|
||||
} else {
|
||||
selectedPermissions.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
const updateSelectedPermissions = (newSelectedPermissions) => {
|
||||
selectedPermissions.value = newSelectedPermissions;
|
||||
};
|
||||
onMounted(() => {
|
||||
fetchPermissions()
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<h3>Assign Role Permissions</h3>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<h4 v-if="loading">Loading.....</h4>
|
||||
<div v-else>
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
label="Search Permission..."
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
@input="searchPermissions"
|
||||
></v-text-field>
|
||||
<folder-tree-item
|
||||
v-for="item in filteredPermissionsTree"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:selected-permissions="selectedPermissions"
|
||||
@update:selected-permissions="updateSelectedPermissions"
|
||||
@toggle-permission="togglePermission"
|
||||
/>
|
||||
<v-form @submit.prevent="savePermissions">
|
||||
<input type="hidden" name="permissions" :value="selectedPermissionsIds">
|
||||
<v-btn
|
||||
color="primary"
|
||||
type="submit"
|
||||
class="mt-4"
|
||||
>
|
||||
Save Permissions
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
|
Reference in New Issue
Block a user