feat: add new member

This commit is contained in:
Jannat Patel
2024-08-19 11:47:17 +05:30
parent 75f0e5b9f1
commit cdd46667f3
5 changed files with 212 additions and 134 deletions

View File

@@ -1,55 +1,83 @@
<template> <template>
<div class="text-base p-4"> <div class="text-base p-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<div class="font-semibold mb-1"> <div class="font-semibold mb-1">
{{ __(label) }} {{ __(label) }}
</div> </div>
<div class="text-xs text-gray-600"> <div class="text-xs text-gray-600">
{{ __(description) }} {{ __(description) }}
</div> </div>
</div> </div>
<FormControl v-model="search" :placeholder="__('Search')" type="text" :debounce="300"/> <div class="flex item-center space-x-2">
</div> <FormControl
<div class="my-4"> v-model="search"
:placeholder="__('Search')"
type="text"
:debounce="300"
/>
<Button @click="() => (showForm = true)">
<template #icon>
<component :is="icons['Plus']" class="h-3 w-3 stroke-1.5" />
</template>
</Button>
</div>
</div>
<div class="my-4">
<!-- Form to add new member -->
<div v-if="showForm" class="flex items-center space-x-2 mb-4">
<FormControl
v-model="member.email"
:placeholder="__('Email')"
type="email"
class="w-full"
/>
<FormControl
v-model="member.first_name"
:placeholder="__('First Name')"
type="test"
class="w-full"
/>
<Button @click="addMember()" variant="subtle">
{{ __('Add') }}
</Button>
</div>
<!-- Form to add new member --> <!-- Member list -->
<div v-if="showForm" class="flex items-center space-x-2 mb-4"> <div
<FormControl v-model="member.email" :placeholder="__('Email')" type="email" class="w-full"/> v-for="member in memberList"
<FormControl v-model="member.first_name" :placeholder="__('First Name')" type="test" class="w-full"/> class="grid grid-cols-5 grid-flow-row py-2 cursor-pointer"
<Button @click="addMember" variant="subtle"> >
{{ __('Add') }} <div
</Button> @click="openProfile(member.username)"
</div> class="flex items-center space-x-2 col-span-2"
>
<!-- Member list --> <Avatar
<div v-for="member in memberList" class="grid grid-cols-5 grid-flow-row py-2 cursor-pointer"> :image="member.user_image"
<div @click="openProfile(member.username)" class="flex items-center space-x-2 col-span-2"> :label="member.full_name"
<Avatar :image="member.user_image" :label="member.full_name" size="sm" /> size="sm"
<div> />
{{ member.full_name }} <div>
</div> {{ member.full_name }}
</div> </div>
<div class="text-sm text-gray-700 col-span-2"> </div>
{{ member.name }} <div class="text-sm text-gray-700 col-span-2">
</div> {{ member.name }}
<div class="text-sm text-gray-700 justify-self-end"> </div>
{{ getRole(member.role) }} <div class="text-sm text-gray-700 justify-self-end">
</div> {{ getRole(member.role) }}
</div> </div>
</div> </div>
<div v-if="hasNextPage" class="flex justify-center"> </div>
<Button variant="solid" @click="members.reload()"> <div v-if="hasNextPage" class="flex justify-center">
<template #prefix> <Button variant="solid" @click="members.reload()">
<component <template #prefix>
:is="icons['RefreshCw']" <component :is="icons['RefreshCw']" class="h-3 w-3 stroke-1.5" />
class="h-3 w-3 stroke-1.5" </template>
/> {{ __('Load More') }}
</template> </Button>
{{ __('Load More') }} </div>
</Button> </div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createResource, Avatar, Button, FormControl } from 'frappe-ui' import { createResource, Avatar, Button, FormControl } from 'frappe-ui'
@@ -58,72 +86,98 @@ import { useRouter } from 'vue-router'
import { ref, watch, reactive } from 'vue' import { ref, watch, reactive } from 'vue'
const router = useRouter() const router = useRouter()
const show = defineModel("show") const show = defineModel('show')
const search = ref('') const search = ref('')
const start = ref(0) const start = ref(0)
const memberList = ref([]) const memberList = ref([])
const hasNextPage = ref(false) const hasNextPage = ref(false)
const showForm = ref(true) const showForm = ref(false)
const member = reactive({ const member = reactive({
email: '', email: '',
first_name: '', first_name: '',
}) })
const props = defineProps({ const props = defineProps({
label: { label: {
type: String, type: String,
required: true, required: true,
}, },
description: { description: {
type: String, type: String,
default: '', default: '',
}, },
show: { show: {
type: Boolean, type: Boolean,
}, },
}) })
const members = createResource({ const members = createResource({
url: "lms.lms.api.get_members", url: 'lms.lms.api.get_members',
makeParams: () => { makeParams: () => {
return { return {
search: search.value, search: search.value,
start: start.value start: start.value,
} }
}, },
onSuccess(data) { onSuccess(data) {
memberList.value = memberList.value.concat(data) memberList.value = memberList.value.concat(data)
start.value = start.value + 20 start.value = start.value + 20
hasNextPage.value = data.length === 20 hasNextPage.value = data.length === 20
}, },
auto: true, auto: true,
}) })
const openProfile = (username) => { const openProfile = (username) => {
show.value = false show.value = false
router.push({ router.push({
name: 'Profile', name: 'Profile',
params: { params: {
username: username, username: username,
}, },
}) })
} }
const newMember = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'User',
first_name: member.first_name,
email: member.email,
},
}
},
auto: false,
onSuccess(data) {
show.value = false
router.push({
name: 'Profile',
params: {
username: data.username,
},
})
},
})
const addMember = () => {
newMember.reload()
}
watch(search, () => { watch(search, () => {
memberList.value = [] memberList.value = []
start.value = 0 start.value = 0
members.reload() members.reload()
}) })
const getRole = (role) => { const getRole = (role) => {
const map = { const map = {
'LMS Student': 'Student', 'LMS Student': 'Student',
'Course Creator': 'Instructor', 'Course Creator': 'Instructor',
'Moderator': 'Moderator', Moderator: 'Moderator',
'Batch Evaluator': 'Evaluator' 'Batch Evaluator': 'Evaluator',
} }
return map[role] return map[role]
} }
</script>
</script>

View File

@@ -28,13 +28,22 @@
</nav> </nav>
</div> </div>
</div> </div>
<div v-if="activeTab && data.doc" class="flex flex-1 flex-col overflow-y-auto"> <div
<Members v-if="activeTab.label === 'Members'" :label="activeTab.label" :description="activeTab.description" v-model:show="show"/> v-if="activeTab && data.doc"
class="flex flex-1 flex-col overflow-y-auto"
>
<Members
v-if="activeTab.label === 'Members'"
:label="activeTab.label"
:description="activeTab.description"
v-model:show="show"
/>
<SettingDetails <SettingDetails
v-else v-else
:fields="activeTab.fields" :fields="activeTab.fields"
:data="data"
:label="activeTab.label" :label="activeTab.label"
:description="activeTab.description"
:data="data"
/> />
</div> </div>
</div> </div>
@@ -66,9 +75,16 @@ const tabs = computed(() => {
label: 'Settings', label: 'Settings',
hideLabel: true, hideLabel: true,
items: [ items: [
{
label: 'Members',
description: 'Manage the members of your learning system',
icon: 'UserRoundPlus',
},
{ {
label: 'Payment Gateway', label: 'Payment Gateway',
icon: 'DollarSign', icon: 'DollarSign',
description:
'Configure the payment gateway and other payment related settings',
fields: [ fields: [
{ {
label: 'Razorpay Key', label: 'Razorpay Key',
@@ -115,6 +131,7 @@ const tabs = computed(() => {
{ {
label: 'Sidebar', label: 'Sidebar',
icon: 'PanelLeftIcon', icon: 'PanelLeftIcon',
description: 'Customize the sidebar as per your needs',
fields: [ fields: [
{ {
label: 'Courses', label: 'Courses',
@@ -160,6 +177,7 @@ const tabs = computed(() => {
{ {
label: 'Email Templates', label: 'Email Templates',
icon: 'MailPlus', icon: 'MailPlus',
description: 'Create email templates with the content you want',
fields: [ fields: [
{ {
label: 'Batch Confirmation Template', label: 'Batch Confirmation Template',
@@ -190,9 +208,11 @@ const tabs = computed(() => {
{ {
label: 'Signup', label: 'Signup',
icon: 'LogIn', icon: 'LogIn',
description:
'Customize the signup page to inform users about your terms and policies',
fields: [ fields: [
{ {
label: 'Show terms of use on signup page', label: 'Show terms of use on signup',
name: 'terms_of_use', name: 'terms_of_use',
type: 'checkbox', type: 'checkbox',
}, },
@@ -203,15 +223,7 @@ const tabs = computed(() => {
doctype: 'Web Page', doctype: 'Web Page',
}, },
{ {
label: 'Ask user category during signup', label: 'Show privacy policy on signup',
name: 'user_category',
type: 'checkbox',
},
{
type: 'Column Break',
},
{
label: 'Show privacy policy on signup page',
name: 'privacy_policy', name: 'privacy_policy',
type: 'checkbox', type: 'checkbox',
}, },
@@ -225,7 +237,7 @@ const tabs = computed(() => {
type: 'Column Break', type: 'Column Break',
}, },
{ {
label: 'Show cookie policy on signup page', label: 'Show cookie policy on signup',
name: 'cookie_policy', name: 'cookie_policy',
type: 'checkbox', type: 'checkbox',
}, },
@@ -235,21 +247,15 @@ const tabs = computed(() => {
type: 'Link', type: 'Link',
doctype: 'Web Page', doctype: 'Web Page',
}, },
{
label: 'Ask user category during signup',
name: 'user_category',
type: 'checkbox',
},
], ],
}, },
], ],
}, },
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Members',
description: 'Manage the members of your learning system',
icon: "UserRoundPlus",
},
],
},
] ]
return _tabs.map((tab) => { return _tabs.map((tab) => {

View File

@@ -1,6 +1,14 @@
<template> <template>
<div class="flex flex-col justify-between h-full p-8"> <div class="flex flex-col justify-between h-full p-4">
<div class="flex space-x-8"> <div>
<div class="font-semibold mb-1">
{{ __(label) }}
</div>
<div class="text-xs text-gray-600">
{{ __(description) }}
</div>
</div>
<div class="flex space-x-8 my-5">
<div v-for="(column, index) in columns" :key="index"> <div v-for="(column, index) in columns" :key="index">
<div class="flex flex-col space-y-4 w-60"> <div class="flex flex-col space-y-4 w-60">
<div v-for="field in column"> <div v-for="field in column">
@@ -43,6 +51,13 @@ const props = defineProps({
type: Object, type: Object,
required: true, required: true,
}, },
label: {
type: String,
required: true,
},
description: {
type: String,
},
}) })
const columns = computed(() => { const columns = computed(() => {

View File

@@ -563,19 +563,22 @@ def get_categories(doctype, filters):
return categoryOptions return categoryOptions
@frappe.whitelist() @frappe.whitelist()
def get_members(start=0, search=""): def get_members(start=0, search=""):
filters = { filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]}
"enabled": 1,
"name": ["not in", ["Administrator", "Guest"]]
}
if search: if search:
filters["full_name"] = ["like", f"%{search}%"] filters["full_name"] = ["like", f"%{search}%"]
print(filters) print(filters)
members = frappe.get_all("User", filters=filters, fields=["name", "full_name", "user_image", "username"], members = frappe.get_all(
page_length=20, start=start) "User",
filters=filters,
fields=["name", "full_name", "user_image", "username"],
page_length=20,
start=start,
)
for member in members: for member in members:
roles = frappe.get_roles(member.name) roles = frappe.get_roles(member.name)
@@ -589,6 +592,3 @@ def get_members(start=0, search=""):
member.role = "LMS Student" member.role = "LMS Student"
return members return members

View File

@@ -16,6 +16,9 @@ class CustomUser(User):
super().validate() super().validate()
self.validate_username_duplicates() self.validate_username_duplicates()
def after_insert(self):
self.add_roles("LMS Student")
def validate_username_duplicates(self): def validate_username_duplicates(self):
while not self.username or self.username_exists(): while not self.username or self.username_exists():
self.username = append_number_if_name_exists( self.username = append_number_if_name_exists(