feat: settings

This commit is contained in:
Jannat Patel
2024-08-13 18:53:27 +05:30
parent b8eb9fd717
commit ceecab395b
13 changed files with 605 additions and 119 deletions

View File

@@ -7,7 +7,7 @@
class="flex flex-col overflow-hidden"
:class="isSidebarCollapsed ? 'items-center' : ''"
>
<UserDropdown class="p-2" :isCollapsed="isSidebarCollapsed" />
<UserDropdown :isCollapsed="isSidebarCollapsed" />
<div class="flex flex-col" v-if="sidebarSettings.data">
<SidebarLink
v-for="link in sidebarLinks"

View File

@@ -13,6 +13,7 @@
<script setup>
import { Star } from 'lucide-vue-next'
import { ref } from 'vue'
const props = defineProps({
id: {
type: String,

View File

@@ -0,0 +1,214 @@
<template>
<div class="flex flex-col gap-4">
<div
v-for="section in sections"
:key="section.label"
class="first:border-t-0 first:pt-0"
:class="section.hideBorder ? '' : 'border-t pt-4'"
>
<div
v-if="!section.hideLabel"
class="flex h-7 mb-3 max-w-fit cursor-pointer items-center gap-2 text-base font-semibold leading-5"
>
{{ section.label }}
</div>
<div
class="grid gap-4"
:class="
section.columns
? 'grid-cols-' + section.columns
: 'grid-cols-2 sm:grid-cols-3'
"
>
<div v-for="field in section.fields" :key="field.name">
<div
v-if="
field.type == 'Check' ||
(field.read_only && data[field.name]) ||
!field.read_only ||
!field.hidden
"
>
<div
v-if="field.type != 'Check'"
class="mb-2 text-sm text-gray-600"
>
{{ __(field.label) }}
<span class="text-red-500" v-if="field.mandatory">*</span>
</div>
<FormControl
v-if="field.read_only && field.type !== 'Check'"
type="text"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
:disabled="true"
/>
<FormControl
v-else-if="field.type === 'Select'"
type="select"
class="form-control"
:class="field.prefix ? 'prefix' : ''"
:options="field.options"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
>
<template v-if="field.prefix" #prefix>
<IndicatorIcon :class="field.prefix" />
</template>
</FormControl>
<div
v-else-if="field.type == 'Check'"
class="flex items-center gap-2"
>
<FormControl
class="form-control"
type="checkbox"
v-model="data[field.name]"
@change="(e) => (data[field.name] = e.target.checked)"
:disabled="Boolean(field.read_only)"
/>
<label
class="text-sm text-gray-600"
@click="data[field.name] = !data[field.name]"
>
{{ __(field.label) }}
<span class="text-red-500" v-if="field.mandatory">*</span>
</label>
</div>
<Link
v-else-if="field.type === 'Link'"
class="form-control"
:value="data[field.name]"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:onCreate="field.create"
/>
<Link
v-else-if="field.type === 'User'"
class="form-control"
:value="getUser(data[field.name]).full_name"
:doctype="field.options"
@change="(v) => (data[field.name] = v)"
:placeholder="__(field.placeholder || field.label)"
:hideMe="true"
>
<template #prefix>
<UserAvatar class="mr-2" :user="data[field.name]" size="sm" />
</template>
<template #item-prefix="{ option }">
<UserAvatar class="mr-2" :user="option.value" size="sm" />
</template>
<template #item-label="{ option }">
<Tooltip :text="option.value">
<div class="cursor-pointer">
{{ getUser(option.value).full_name }}
</div>
</Tooltip>
</template>
</Link>
<div v-else-if="field.type === 'Dropdown'">
<NestedPopover>
<template #target="{ open }">
<Button
:label="data[field.name]"
class="dropdown-button flex w-full items-center justify-between rounded border border-gray-100 bg-gray-100 px-2 py-1.5 text-base text-gray-800 placeholder-gray-500 transition-colors hover:border-gray-200 hover:bg-gray-200 focus:border-gray-500 focus:bg-white focus:shadow-sm focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-gray-400"
>
<div class="truncate">{{ data[field.name] }}</div>
<template #suffix>
<FeatherIcon
:name="open ? 'chevron-up' : 'chevron-down'"
class="h-4 text-gray-600"
/>
</template>
</Button>
</template>
<template #body>
<div
class="my-2 space-y-1.5 divide-y rounded-lg border border-gray-100 bg-white p-1.5 shadow-xl"
>
<div>
<DropdownItem
v-if="field.options?.length"
v-for="option in field.options"
:key="option.name"
:option="option"
/>
<div v-else>
<div class="p-1.5 px-7 text-base text-gray-500">
{{ __('No {0} Available', [field.label]) }}
</div>
</div>
</div>
<div class="pt-1.5">
<Button
variant="ghost"
class="w-full !justify-start"
:label="__('Create New')"
@click="field.create()"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
</div>
</div>
</template>
</NestedPopover>
</div>
<DateTimePicker
v-else-if="field.type === 'Datetime'"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
input-class="border-none"
/>
<DatePicker
v-else-if="field.type === 'Date'"
v-model="data[field.name]"
:placeholder="__(field.placeholder || field.label)"
input-class="border-none"
/>
<FormControl
v-else-if="
['Small Text', 'Text', 'Long Text'].includes(field.type)
"
type="textarea"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else-if="['Int'].includes(field.type)"
type="number"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
/>
<FormControl
v-else
type="text"
:placeholder="__(field.placeholder || field.label)"
v-model="data[field.name]"
:disabled="Boolean(field.read_only)"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import NestedPopover from '@/components/NestedPopover.vue'
import DropdownItem from '@/components/DropdownItem.vue'
import IndicatorIcon from '@/components/Icons/IndicatorIcon.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import Link from '@/components/Controls/Link.vue'
import { usersStore } from '@/stores/users'
import { Tooltip, DatePicker, DateTimePicker } from 'frappe-ui'
const { getUser } = usersStore()
const props = defineProps({
sections: Array,
data: Object,
})
</script>

View File

@@ -1,25 +1,270 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Settings'),
size: 'xl',
actions: [
{
label: 'Save',
variant: 'solid',
onClick: (close) => {
saveSettings(close)
},
},
],
}"
>
<template #body-content> </template>
<Dialog v-model="show" :options="{ size: '6xl' }">
<template #body>
<div class="flex h-[calc(100vh_-_8rem)]">
<div class="flex w-52 shrink-0 flex-col bg-gray-50 p-2">
<h1 class="mb-3 px-2 pt-2 text-lg font-semibold">
{{ __('Settings') }}
</h1>
<div v-for="tab in tabs">
<div
v-if="!tab.hideLabel"
class="mb-2 mt-3 flex cursor-pointer gap-1.5 px-1 text-base font-medium text-gray-600 transition-all duration-300 ease-in-out"
>
<span>{{ __(tab.label) }}</span>
</div>
<nav class="space-y-1">
<SidebarLink
v-for="item in tab.items"
:link="item"
class="w-full"
:class="
activeTab?.label == item.label
? 'bg-white shadow-sm'
: 'hover:bg-gray-100'
"
@click="activeTab = item"
/>
</nav>
</div>
</div>
<div class="flex flex-1 flex-col overflow-y-auto">
<SettingDetails
v-if="activeTab && data.doc"
:fields="activeTab.fields"
:data="data"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog } from 'frappe-ui'
import { Dialog, createDocumentResource } from 'frappe-ui'
import { ref, computed, watch } from 'vue'
import SettingDetails from '../SettingDetails.vue'
import SidebarLink from '@/components/SidebarLink.vue'
const show = defineModel()
const doctype = ref('LMS Settings')
const activeTab = ref(null)
const data = createDocumentResource({
doctype: doctype.value,
name: doctype.value,
fields: ['*'],
cache: doctype.value,
auto: true,
})
const tabs = computed(() => {
let _tabs = [
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Payment Gateway',
icon: 'DollarSign',
fields: [
{
label: 'Razorpay Key',
name: 'razorpay_key',
type: 'text',
},
{
label: 'Razorpay Secret',
name: 'razorpay_secret',
type: 'password',
},
{
label: 'Default Currency',
name: 'default_currency',
type: 'Link',
doctype: 'Currency',
},
{
type: 'Column Break',
},
{
label: 'Apply GST for India',
name: 'apply_gst',
type: 'checkbox',
},
{
label: 'Show USD equivalent amount',
name: 'show_usd_equivalent',
type: 'checkbox',
},
{
label: 'Apply rounding on equivalent amount',
name: 'apply_rounding',
type: 'checkbox',
},
],
},
],
},
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Signup',
icon: 'LogIn',
fields: [
{
label: 'Show terms of use on signup page',
name: 'terms_of_use',
type: 'checkbox',
},
{
label: 'Terms of Use Page',
name: 'terms_page',
type: 'Link',
doctype: 'Web Page',
},
{
label: 'Ask user category during signup',
name: 'user_category',
type: 'checkbox',
},
{
type: 'Column Break',
},
{
label: 'Show privacy policy on signup page',
name: 'privacy_policy',
type: 'checkbox',
},
{
label: 'Privacy Policy Page',
name: 'privacy_policy_page',
type: 'Link',
doctype: 'Web Page',
},
{
type: 'Column Break',
},
{
label: 'Show cookie policy on signup page',
name: 'cookie_policy',
type: 'checkbox',
},
{
label: 'Cookie Policy Page',
name: 'cookie_policy_page',
type: 'Link',
doctype: 'Web Page',
},
],
},
],
},
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Sidebar',
icon: 'PanelLeftIcon',
fields: [
{
label: 'Courses',
name: 'courses',
type: 'checkbox',
},
{
label: 'Batches',
name: 'batches',
type: 'checkbox',
},
{
label: 'Certified Participants',
name: 'certified_participants',
type: 'checkbox',
},
{
type: 'Column Break',
},
{
label: 'Jobs',
name: 'jobs',
type: 'checkbox',
},
{
label: 'Statistics',
name: 'statistics',
type: 'checkbox',
},
{
label: 'Notifications',
name: 'notifications',
type: 'checkbox',
},
],
},
],
},
{
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Email Templates',
icon: 'MailPlus',
fields: [
{
label: 'Batch Confirmation Template',
name: 'batch_confirmation_template',
doctype: 'Email Template',
type: 'Link',
},
{
label: 'Certification Template',
name: 'certification_template',
doctype: 'Email Template',
type: 'Link',
},
{
label: 'Assignment Submission Template',
name: 'assignment_submission_template',
doctype: 'Email Template',
type: 'Link',
},
],
},
],
},
/* {
label: 'Settings',
hideLabel: true,
items: [
{
label: 'Members',
icon: "UserRoundPlus",
component: markRaw(MemberSettings),
},
],
}, */
]
return _tabs.map((tab) => {
tab.items = tab.items.filter((item) => {
if (item.condition) {
return item.condition()
}
return true
})
return tab
})
})
watch(show, () => {
if (show.value) {
activeTab.value = tabs.value[0].items[0]
} else {
activeTab.value = null
}
})
</script>

View File

@@ -0,0 +1,89 @@
<template>
<div class="flex flex-col justify-between h-full p-8">
<div class="flex space-x-10">
<div v-for="(column, index) in columns" :key="index">
<div class="flex flex-col space-y-4">
<div v-for="field in column" :class="width">
<Link
v-if="field.type == 'Link'"
v-model="field.value"
:doctype="field.doctype"
:label="field.label"
/>
<FormControl
v-else
:key="field.name"
v-model="field.value"
:label="field.label"
:type="field.type"
/>
</div>
</div>
</div>
</div>
<div class="flex flex-row-reverse mt-auto">
<Button variant="solid" :loading="data.save.loading" @click="update">
{{ __('Update') }}
</Button>
</div>
</div>
</template>
<script setup>
import { FormControl, Button } from 'frappe-ui'
import { computed, ref } from 'vue'
import Link from '@/components/Controls/Link.vue'
let width = ref('w-full')
const props = defineProps({
fields: {
type: Array,
required: true,
},
data: {
type: Object,
required: true,
},
})
const columns = computed(() => {
const cols = []
let currentColumn = []
props.fields.forEach((field) => {
if (field.type === 'Column Break') {
if (currentColumn.length > 0) {
cols.push(currentColumn)
currentColumn = []
}
} else {
if (field.type == 'checkbox') {
field.value = props.data.doc[field.name] ? true : false
} else {
field.value = props.data.doc[field.name]
}
currentColumn.push(field)
}
})
if (currentColumn.length > 0) {
cols.push(currentColumn)
}
if (cols.length == 3) {
width.value = 'w-64'
} else {
width.value = 'w-96'
}
return cols
})
const update = () => {
props.fields.forEach((f) => {
props.data.doc[f.name] = f.value
})
props.data.save.submit()
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<Dropdown :options="userDropdownOptions">
<Dropdown class="p-2" :options="userDropdownOptions">
<template v-slot="{ open }">
<button
class="flex h-12 py-2 items-center rounded-md duration-300 ease-in-out"
@@ -56,7 +56,10 @@
</button>
</template>
</Dropdown>
<SettingsModal v-model="showSettingsModal" />
<SettingsModal
v-if="userResource.data?.is_moderator"
v-model="showSettingsModal"
/>
</template>
<script setup>
@@ -121,7 +124,6 @@ const userDropdownOptions = [
showSettingsModal.value = true
},
condition: () => {
console.log(userResource)
return userResource.data?.is_moderator
},
},