refactor: category list in settings
This commit is contained in:
@@ -1,16 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col min-h-0">
|
<div class="flex flex-col min-h-0 text-base">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<div class="text-xl font-semibold mb-5 text-ink-gray-9">
|
<div class="flex flex-col space-y-2">
|
||||||
{{ label }}
|
<div class="text-xl font-semibold text-ink-gray-9">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-ink-gray-5">
|
||||||
|
{{ __(description) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-5">
|
||||||
|
<div
|
||||||
|
class="flex items-center space-x-1 text-ink-amber-3 border border-outline-amber-1 bg-surface-amber-1 rounded-lg px-2 py-1"
|
||||||
|
v-if="saving"
|
||||||
|
>
|
||||||
|
<LoadingIndicator class="size-2" />
|
||||||
|
<span class="text-xs">
|
||||||
|
{{ __('saving...') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Button @click="() => showCategoryForm()">
|
||||||
|
<template #prefix>
|
||||||
|
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
|
||||||
|
<X v-else class="h-3 w-3 stroke-1.5" />
|
||||||
|
</template>
|
||||||
|
{{ showForm ? __('Close') : __('New') }}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button @click="() => showCategoryForm()">
|
|
||||||
<template #prefix>
|
|
||||||
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
|
|
||||||
<X v-else class="h-3 w-3 stroke-1.5" />
|
|
||||||
</template>
|
|
||||||
{{ showForm ? __('Close') : __('New') }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -29,13 +45,39 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-y-scroll">
|
<div class="overflow-y-scroll">
|
||||||
<div class="text-base space-y-2">
|
<div class="divide-y space-y-2">
|
||||||
<FormControl
|
<div
|
||||||
:value="cat.category"
|
v-for="(cat, index) in categories.data"
|
||||||
type="text"
|
:key="cat.name"
|
||||||
v-for="cat in categories.data"
|
class="pt-4 pb-1"
|
||||||
@change.stop="(e) => update(cat.name, e.target.value)"
|
>
|
||||||
/>
|
<div
|
||||||
|
v-if="editing?.name !== cat.name"
|
||||||
|
class="flex items-center justify-between group"
|
||||||
|
>
|
||||||
|
<div @dblclick="allowEdit(cat, index)">
|
||||||
|
{{ cat.category }}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
theme="red"
|
||||||
|
class="invisible group-hover:visible"
|
||||||
|
@click="deleteCategory(cat.name)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Trash2 class="size-4 stroke-1.5 text-ink-red-4" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<FormControl
|
||||||
|
v-else
|
||||||
|
:ref="(el) => (editInputRef[index] = el)"
|
||||||
|
v-model="editedValue"
|
||||||
|
type="text"
|
||||||
|
class="w-full"
|
||||||
|
@keyup.enter="saveChanges(cat.name, editedValue)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,16 +86,22 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
LoadingIndicator,
|
||||||
createListResource,
|
createListResource,
|
||||||
createResource,
|
createResource,
|
||||||
debounce,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { Plus, X } from 'lucide-vue-next'
|
import { Plus, Trash2, X } from 'lucide-vue-next'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { cleanError } from '@/utils'
|
||||||
|
|
||||||
const showForm = ref(false)
|
const showForm = ref(false)
|
||||||
const category = ref(null)
|
const category = ref(null)
|
||||||
const categoryInput = ref(null)
|
const categoryInput = ref(null)
|
||||||
|
const saving = ref(false)
|
||||||
|
const editing = ref(null)
|
||||||
|
const editedValue = ref('')
|
||||||
|
const editInputRef = ref([])
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
label: {
|
label: {
|
||||||
@@ -72,25 +120,20 @@ const categories = createListResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const newCategory = createResource({
|
|
||||||
url: 'frappe.client.insert',
|
|
||||||
makeParams(values) {
|
|
||||||
return {
|
|
||||||
doc: {
|
|
||||||
doctype: 'LMS Category',
|
|
||||||
category: category.value,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const addCategory = () => {
|
const addCategory = () => {
|
||||||
newCategory.submit(
|
categories.insert.submit(
|
||||||
{},
|
{
|
||||||
|
category: category.value,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
categories.reload()
|
categories.reload()
|
||||||
category.value = null
|
category.value = null
|
||||||
|
showForm.value = false
|
||||||
|
toast.success(__('Category added successfully'))
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
toast.error(__(cleanError(err.messages[0]) || 'Unable to add category'))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -115,6 +158,7 @@ const updateCategory = createResource({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const update = (name, value) => {
|
const update = (name, value) => {
|
||||||
|
saving.value = true
|
||||||
updateCategory.submit(
|
updateCategory.submit(
|
||||||
{
|
{
|
||||||
name: name,
|
name: name,
|
||||||
@@ -122,9 +166,51 @@ const update = (name, value) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
saving.value = false
|
||||||
categories.reload()
|
categories.reload()
|
||||||
|
editing.value = null
|
||||||
|
editedValue.value = ''
|
||||||
|
toast.success(__('Category updated successfully'))
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
saving.value = false
|
||||||
|
editing.value = null
|
||||||
|
editedValue.value = ''
|
||||||
|
toast.error(
|
||||||
|
__(cleanError(err.messages[0]) || 'Unable to update category')
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteCategory = (name) => {
|
||||||
|
saving.value = true
|
||||||
|
categories.delete.submit(name, {
|
||||||
|
onSuccess() {
|
||||||
|
saving.value = false
|
||||||
|
categories.reload()
|
||||||
|
toast.success(__('Category deleted successfully'))
|
||||||
|
},
|
||||||
|
onError(err) {
|
||||||
|
saving.value = false
|
||||||
|
toast.error(
|
||||||
|
__(cleanError(err.messages[0]) || 'Unable to delete category')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveChanges = (name, value) => {
|
||||||
|
saving.value = true
|
||||||
|
update(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowEdit = (cat, index) => {
|
||||||
|
editing.value = cat
|
||||||
|
editedValue.value = cat.category
|
||||||
|
setTimeout(() => {
|
||||||
|
editInputRef.value[index].$el.querySelector('input').focus()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex min-h-0 flex-col text-base">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
|
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
|
||||||
@@ -39,25 +39,27 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divide-y">
|
<div class="overflow-y-scroll">
|
||||||
<div
|
<div class="divide-y">
|
||||||
v-for="evaluator in evaluators.data"
|
<div
|
||||||
@click="openProfile(evaluator.username)"
|
v-for="evaluator in evaluators.data"
|
||||||
class="cursor-pointer"
|
@click="openProfile(evaluator.username)"
|
||||||
>
|
class="cursor-pointer"
|
||||||
<div class="flex items-center justify-between py-3">
|
>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center justify-between py-3">
|
||||||
<Avatar
|
<div class="flex items-center space-x-3">
|
||||||
:image="evaluator.user_image"
|
<Avatar
|
||||||
:label="evaluator.full_name"
|
:image="evaluator.user_image"
|
||||||
size="lg"
|
:label="evaluator.full_name"
|
||||||
/>
|
size="lg"
|
||||||
<div>
|
/>
|
||||||
<div class="text-base font-semibold text-ink-gray-9">
|
<div>
|
||||||
{{ evaluator.full_name }}
|
<div class="text-base font-semibold text-ink-gray-9">
|
||||||
</div>
|
{{ evaluator.full_name }}
|
||||||
<div class="text-xs text-ink-gray-5">
|
</div>
|
||||||
{{ evaluator.evaluator }}
|
<div class="text-xs text-ink-gray-5">
|
||||||
|
{{ evaluator.evaluator }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ const tabsStructure = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Categories',
|
label: 'Categories',
|
||||||
description: 'Manage the members of your learning system',
|
description: 'Double click to edit the category',
|
||||||
icon: 'Network',
|
icon: 'Network',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -561,3 +561,24 @@ export const openSettings = (category, close) => {
|
|||||||
settingsStore.activeTab = category
|
settingsStore.activeTab = category
|
||||||
settingsStore.isSettingsOpen = true
|
settingsStore.isSettingsOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cleanError = (message) => {
|
||||||
|
// Remove HTML tags but keep the text within the tags
|
||||||
|
|
||||||
|
const cleanMessage = message.replace(/<[^>]+>/g, (match) => {
|
||||||
|
return match.replace(/<\/?[^>]+(>|$)/g, '')
|
||||||
|
})
|
||||||
|
return cleanMessage
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/`/g, '`')
|
||||||
|
.replace(/=/g, '=')
|
||||||
|
.replace(///g, '/')
|
||||||
|
.replace(/,/g, ',')
|
||||||
|
.replace(/;/g, ';')
|
||||||
|
.replace(/:/g, ':')
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user