fix: improved members list and form

This commit is contained in:
Jannat Patel
2025-07-03 18:19:29 +05:30
parent f2c8788602
commit 954d0a0637
4 changed files with 76 additions and 68 deletions

View File

@@ -206,7 +206,7 @@ const progressColumns = computed(() => {
{ {
label: __('Member'), label: __('Member'),
key: 'member_name', key: 'member_name',
width: '50%', width: '60%',
icon: 'user', icon: 'user',
}, },
{ {

View File

@@ -5,9 +5,9 @@
<div class="text-xl font-semibold mb-1 text-ink-gray-9"> <div class="text-xl font-semibold mb-1 text-ink-gray-9">
{{ __(label) }} {{ __(label) }}
</div> </div>
<!-- <div class="text-xs text-ink-gray-5"> <div class="text-xs text-ink-gray-5 leading-5">
{{ __(description) }} {{ __(description) }}
</div> --> </div>
</div> </div>
<div class="flex item-center space-x-2"> <div class="flex item-center space-x-2">
<FormControl <FormControl
@@ -18,40 +18,20 @@
/> />
<Button @click="() => (showForm = !showForm)"> <Button @click="() => (showForm = !showForm)">
<template #prefix> <template #prefix>
<Plus v-if="!showForm" class="size-4 stroke-1.5" /> <Plus class="size-4 stroke-1.5" />
<X v-else class="size-4 stroke-1.5" />
</template> </template>
{{ showForm ? __('Close') : __('New') }} {{ __('New') }}
</Button> </Button>
</div> </div>
</div> </div>
<!-- Form to add new member -->
<div v-if="showForm" class="flex items-center space-x-2 my-4">
<FormControl
v-model="member.email"
:placeholder="__('Email')"
type="email"
class="w-full"
/>
<FormControl
v-model="member.first_name"
:placeholder="__('First Name')"
type="text"
class="w-full"
/>
<Button @click="addMember()" variant="subtle">
{{ __('Add') }}
</Button>
</div>
<div class="mt-2 pb-10 overflow-auto"> <div class="mt-2 pb-10 overflow-auto">
<!-- Member list --> <!-- Member list -->
<div class="overflow-y-scroll"> <div class="overflow-y-scroll">
<ul class="divide-y"> <ul class="divide-y">
<li <li
v-for="member in memberList" v-for="member in memberList"
class="grid grid-cols-3 gap-10 py-2 cursor-pointer" class="flex items-center justify-between py-2 cursor-pointer"
> >
<div <div
@click="openProfile(member.username)" @click="openProfile(member.username)"
@@ -60,27 +40,13 @@
<Avatar <Avatar
:image="member.user_image" :image="member.user_image"
:label="member.full_name" :label="member.full_name"
size="lg" size="xl"
/> />
<div class="space-y-1"> <div class="space-y-1">
<div class="flex"> <div class="flex">
<div class="text-ink-gray-9"> <div class="text-ink-gray-9">
{{ member.full_name }} {{ member.full_name }}
</div> </div>
<div
class="px-1"
v-if="member.role && getRole(member.role) !== 'Student'"
>
<Badge
:variant="'subtle'"
:ref_for="true"
theme="blue"
size="sm"
label="Badge"
>
{{ getRole(member.role) }}
</Badge>
</div>
</div> </div>
<div class="text-sm text-ink-gray-7"> <div class="text-sm text-ink-gray-7">
{{ member.name }} {{ member.name }}
@@ -88,12 +54,13 @@
</div> </div>
</div> </div>
<div <div
class="flex items-center justify-center text-ink-gray-7 text-sm" class="flex items-center space-x-1 bg-surface-gray-2 px-2 py-1.5 rounded-md"
v-if="member.role && member.role !== 'LMS Student'"
> >
<div v-if="member.last_active"> <Shield class="size-4 stroke-1.5" />
{{ dayjs(member.last_active).format('DD MMM, YYYY HH:mm a') }} <span>
</div> {{ getRole(member.role) }}
<div v-else>-</div> </span>
</div> </div>
</li> </li>
</ul> </ul>
@@ -111,20 +78,68 @@
</div> </div>
</div> </div>
</div> </div>
<Dialog
v-model="showForm"
:options="{
title: __('Add a new member'),
size: 'lg',
actions: [{
label: __('Add'),
variant: 'solid',
onClick({ close }: any) {
addMember(close)
}
}]
}"
>
<template #body-content>
<div class="flex items-center space-x-2">
<FormControl
v-model="member.email"
:label="__('Email')"
placeholder="jane@doe.com"
type="email"
class="w-full"
/>
<FormControl
v-model="member.first_name"
:label="__('First Name')"
placeholder="Jane"
type="text"
class="w-full"
/>
</div>
</template>
</Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createResource, Avatar, Button, FormControl, Badge } from 'frappe-ui' import {
Avatar,
Badge,
Button,
createResource,
Dialog,
FormControl,
} from 'frappe-ui'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ref, watch, reactive, inject } from 'vue' import { ref, watch, reactive, inject } from 'vue'
import { RefreshCw, Plus, X } from 'lucide-vue-next' import { RefreshCw, Plus, X, Shield } from 'lucide-vue-next'
import { useOnboarding } from 'frappe-ui/frappe' import { useOnboarding } from 'frappe-ui/frappe'
import type { User } from '@/components/Settings/types' import type { User } from '@/components/Settings/types'
type Member = {
username: string
full_name: string
name: string
role?: string
user_image?: string
}
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<Member[]>([])
const hasNextPage = ref(false) const hasNextPage = ref(false)
const showForm = ref(false) const showForm = ref(false)
const dayjs = inject('$dayjs') const dayjs = inject('$dayjs')
@@ -158,7 +173,7 @@ const members = createResource({
start: start.value, start: start.value,
} }
}, },
onSuccess(data) { onSuccess(data: Member[]) {
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
@@ -166,7 +181,7 @@ const members = createResource({
auto: true, auto: true,
}) })
const openProfile = (username) => { const openProfile = (username: string) => {
show.value = false show.value = false
router.push({ router.push({
name: 'Profile', name: 'Profile',
@@ -178,7 +193,7 @@ const openProfile = (username) => {
const newMember = createResource({ const newMember = createResource({
url: 'frappe.client.insert', url: 'frappe.client.insert',
makeParams(values) { makeParams() {
return { return {
doc: { doc: {
doctype: 'User', doctype: 'User',
@@ -188,13 +203,13 @@ const newMember = createResource({
} }
}, },
auto: false, auto: false,
onSuccess(data) { onSuccess(data: Member) {
show.value = false show.value = false
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students') if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
router.push({ router.push({
name: 'Profile', name: 'ProfileRoles',
params: { params: {
username: data.username, username: data.username,
}, },
@@ -202,8 +217,9 @@ const newMember = createResource({
}, },
}) })
const addMember = () => { const addMember = (close: () => void) => {
newMember.reload() newMember.reload()
close()
} }
watch(search, () => { watch(search, () => {
@@ -212,8 +228,8 @@ watch(search, () => {
members.reload() members.reload()
}) })
const getRole = (role) => { const getRole = (role: string) => {
const map = { const map: Record<string, string> = {
'LMS Student': 'Student', 'LMS Student': 'Student',
'Course Creator': 'Instructor', 'Course Creator': 'Instructor',
Moderator: 'Moderator', Moderator: 'Moderator',

View File

@@ -242,7 +242,8 @@ const tabsStructure = computed(() => {
items: [ items: [
{ {
label: 'Members', label: 'Members',
description: 'Manage the members of your learning system', description:
'Add new members or manage roles and permissions of existing members',
icon: 'UserRoundPlus', icon: 'UserRoundPlus',
}, },
{ {

View File

@@ -696,15 +696,6 @@ def get_categories(doctype, filters):
@frappe.whitelist() @frappe.whitelist()
def get_members(start=0, search=""): def get_members(start=0, search=""):
"""Get members for the given search term and start index.
Args: start (int): Start index for the query.
<<<<<<< HEAD
search (str): Search term to filter the results.
=======
search (str): Search term to filter the results.
>>>>>>> 4869bba7bbb2fb38477d6fc29fb3b5838e075577
Returns: List of members.
"""
filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]} filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]}
or_filters = {} or_filters = {}
@@ -1574,7 +1565,7 @@ def track_video_watch_duration(lesson, videos):
existing_record = frappe.db.get_value( existing_record = frappe.db.get_value(
"LMS Video Watch Duration", filters, ["name", "watch_time"], as_dict=True "LMS Video Watch Duration", filters, ["name", "watch_time"], as_dict=True
) )
if existing_record and existing_record.watch_time < video.get("watch_time"): if existing_record and flt(existing_record.watch_time) < flt(video.get("watch_time")):
frappe.db.set_value( frappe.db.set_value(
"LMS Video Watch Duration", "LMS Video Watch Duration",
filters, filters,