style: better spacing
This commit is contained in:
parent
cca420b1a0
commit
28ece820ed
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex h-full flex-col gap-6 p-8 text-ink-gray-8">
|
||||
<div class="flex h-full flex-col gap-6 p-6 text-ink-gray-8">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between">
|
||||
<div class="flex justify-between px-2 pt-2">
|
||||
<div class="flex flex-col gap-1 w-9/12">
|
||||
<h2 class="flex gap-2 text-xl font-semibold leading-none h-5">
|
||||
{{ __('Email templates') }}
|
||||
@ -56,7 +56,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="templates.data?.length > 10"
|
||||
class="flex items-center justify-between mb-4"
|
||||
class="flex items-center justify-between mb-4 px-2 pt-0.5"
|
||||
>
|
||||
<TextInput
|
||||
ref="searchRef"
|
||||
@ -79,20 +79,20 @@
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center p-2 text-sm text-ink-gray-5">
|
||||
<div class="flex items-center py-2 px-4 text-sm text-ink-gray-5">
|
||||
<div class="w-4/6">{{ __('Template name') }}</div>
|
||||
<div class="w-1/6">{{ __('For') }}</div>
|
||||
<div class="w-1/6">{{ __('Enabled') }}</div>
|
||||
</div>
|
||||
<div class="h-px border-t mx-2 border-outline-gray-1" />
|
||||
<ul class="overflow-y-auto">
|
||||
<div class="h-px border-t mx-4 border-outline-gray-modals" />
|
||||
<ul class="overflow-y-auto px-2">
|
||||
<template v-for="(template, i) in templatesList" :key="template.name">
|
||||
<li
|
||||
class="flex items-center justify-between px-2 py-3 cursor-pointer hover:bg-surface-gray-2 rounded"
|
||||
class="flex items-center justify-between p-3 cursor-pointer hover:bg-surface-menu-bar rounded"
|
||||
@click="() => emit('updateStep', 'edit-template', { ...template })"
|
||||
>
|
||||
<div class="flex flex-col w-4/6 pr-5">
|
||||
<div class="text-base font-medium text-ink-gray-7">
|
||||
<div class="text-base font-medium text-ink-gray-7 truncate">
|
||||
{{ template.name }}
|
||||
</div>
|
||||
<div class="text-p-base text-ink-gray-5 truncate">
|
||||
@ -110,6 +110,7 @@
|
||||
@click.stop
|
||||
/>
|
||||
<Dropdown
|
||||
class=""
|
||||
:options="getDropdownOptions(template)"
|
||||
placement="right"
|
||||
:button="{
|
||||
@ -126,7 +127,7 @@
|
||||
</li>
|
||||
<div
|
||||
v-if="templatesList.length !== i + 1"
|
||||
class="h-px border-t mx-2 border-outline-gray-1"
|
||||
class="h-px border-t mx-2 border-outline-gray-modals"
|
||||
/>
|
||||
</template>
|
||||
<!-- Load More Button -->
|
||||
@ -147,6 +148,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { TemplateOption } from '@/utils'
|
||||
import {
|
||||
TextInput,
|
||||
FormControl,
|
||||
@ -155,7 +157,7 @@ import {
|
||||
FeatherIcon,
|
||||
toast,
|
||||
} from 'frappe-ui'
|
||||
import { ref, computed, inject, h } from 'vue'
|
||||
import { ref, computed, inject } from 'vue'
|
||||
|
||||
const emit = defineEmits(['updateStep'])
|
||||
|
||||
@ -251,7 +253,7 @@ function getDropdownOptions(template) {
|
||||
option: __('Confirm Delete'),
|
||||
icon: 'trash-2',
|
||||
active: props.active,
|
||||
variant: 'danger',
|
||||
theme: 'danger',
|
||||
onClick: () => deleteTemplate(template),
|
||||
}),
|
||||
condition: () => confirmDelete.value,
|
||||
@ -260,28 +262,4 @@ function getDropdownOptions(template) {
|
||||
|
||||
return options.filter((option) => option.condition?.() || true)
|
||||
}
|
||||
|
||||
function TemplateOption({ active, option, variant, icon, onClick }) {
|
||||
return h(
|
||||
'button',
|
||||
{
|
||||
class: [
|
||||
active ? 'bg-surface-gray-2' : 'text-ink-gray-8',
|
||||
'group flex w-full gap-2 items-center rounded-md px-2 py-2 text-sm',
|
||||
variant == 'danger' ? 'text-ink-red-3 hover:bg-ink-red-1' : '',
|
||||
],
|
||||
onClick: onClick,
|
||||
},
|
||||
[
|
||||
icon
|
||||
? h(FeatherIcon, {
|
||||
name: icon,
|
||||
class: ['h-4 w-4 shrink-0'],
|
||||
'aria-hidden': true,
|
||||
})
|
||||
: null,
|
||||
h('span', { class: 'whitespace-nowrap' }, option),
|
||||
],
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex h-full flex-col gap-6 p-8 text-ink-gray-8">
|
||||
<div class="flex h-full flex-col gap-6 p-6 text-ink-gray-8">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between">
|
||||
<div class="flex justify-between px-2 pt-2">
|
||||
<div class="flex flex-col gap-1 w-9/12">
|
||||
<h2 class="flex gap-2 text-xl font-semibold leading-none h-5">
|
||||
{{ __('Users') }}
|
||||
@ -65,7 +65,7 @@
|
||||
>
|
||||
<div
|
||||
v-if="users.data?.crmUsers?.length > 10"
|
||||
class="flex items-center justify-between mb-4"
|
||||
class="flex items-center justify-between mb-4 px-2 pt-0.5"
|
||||
>
|
||||
<TextInput
|
||||
ref="searchRef"
|
||||
@ -89,7 +89,7 @@
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<ul class="divide-y divide-outline-gray-modals overflow-y-auto">
|
||||
<ul class="divide-y divide-outline-gray-modals overflow-y-auto px-2">
|
||||
<template v-for="user in usersList" :key="user.name">
|
||||
<li class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center">
|
||||
@ -112,6 +112,10 @@
|
||||
:options="getMoreOptions(user)"
|
||||
:button="{
|
||||
icon: 'more-horizontal',
|
||||
onblur: (e) => {
|
||||
e.stopPropagation()
|
||||
confirmRemove = false
|
||||
},
|
||||
}"
|
||||
placement="right"
|
||||
/>
|
||||
@ -120,6 +124,12 @@
|
||||
:button="{
|
||||
label: roleMap[user.role],
|
||||
iconRight: 'chevron-down',
|
||||
iconLeft:
|
||||
user.role === 'System Manager'
|
||||
? 'shield'
|
||||
: user.role === 'Sales Manager'
|
||||
? 'briefcase'
|
||||
: 'user-check',
|
||||
}"
|
||||
placement="right"
|
||||
/>
|
||||
@ -149,12 +159,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LucideCheck from '~icons/lucide/check'
|
||||
import AddExistingUserModal from '@/components/Modals/AddExistingUserModal.vue'
|
||||
import { activeSettingsPage } from '@/composables/settings'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { Avatar, TextInput, toast, call } from 'frappe-ui'
|
||||
import { ref, computed, h, onMounted } from 'vue'
|
||||
import { TemplateOption, DropdownOption } from '@/utils'
|
||||
import { Avatar, TextInput, toast, call, FeatherIcon } from 'frappe-ui'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
const { users, isAdmin, isManager } = usersStore()
|
||||
|
||||
@ -185,12 +195,36 @@ const usersList = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const confirmRemove = ref(false)
|
||||
|
||||
function getMoreOptions(user) {
|
||||
let options = [
|
||||
{
|
||||
label: __('Remove'),
|
||||
icon: 'trash-2',
|
||||
onClick: () => removeUser(user, true),
|
||||
component: (props) =>
|
||||
TemplateOption({
|
||||
option: __('Remove'),
|
||||
icon: 'trash-2',
|
||||
active: props.active,
|
||||
onClick: (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
confirmRemove.value = true
|
||||
},
|
||||
}),
|
||||
condition: () => !confirmRemove.value,
|
||||
},
|
||||
{
|
||||
label: __('Confirm Remove'),
|
||||
component: (props) =>
|
||||
TemplateOption({
|
||||
option: __('Confirm Remove'),
|
||||
icon: 'trash-2',
|
||||
active: props.active,
|
||||
theme: 'danger',
|
||||
onClick: () => removeUser(user, true),
|
||||
}),
|
||||
condition: () => confirmRemove.value,
|
||||
},
|
||||
]
|
||||
|
||||
@ -202,8 +236,9 @@ function getDropdownOptions(user) {
|
||||
{
|
||||
label: __('Admin'),
|
||||
component: (props) =>
|
||||
RoleOption({
|
||||
role: __('Admin'),
|
||||
DropdownOption({
|
||||
option: __('Admin'),
|
||||
icon: 'shield',
|
||||
active: props.active,
|
||||
selected: user.role === 'System Manager',
|
||||
onClick: () => updateRole(user, 'System Manager'),
|
||||
@ -213,8 +248,9 @@ function getDropdownOptions(user) {
|
||||
{
|
||||
label: __('Manager'),
|
||||
component: (props) =>
|
||||
RoleOption({
|
||||
role: __('Manager'),
|
||||
DropdownOption({
|
||||
option: __('Manager'),
|
||||
icon: 'briefcase',
|
||||
active: props.active,
|
||||
selected: user.role === 'Sales Manager',
|
||||
onClick: () => updateRole(user, 'Sales Manager'),
|
||||
@ -224,8 +260,9 @@ function getDropdownOptions(user) {
|
||||
{
|
||||
label: __('Sales User'),
|
||||
component: (props) =>
|
||||
RoleOption({
|
||||
role: __('Sales User'),
|
||||
DropdownOption({
|
||||
option: __('Sales User'),
|
||||
icon: 'user-check',
|
||||
active: props.active,
|
||||
selected: user.role === 'Sales User',
|
||||
onClick: () => updateRole(user, 'Sales User'),
|
||||
@ -236,28 +273,6 @@ function getDropdownOptions(user) {
|
||||
return options.filter((option) => option.condition?.() || true)
|
||||
}
|
||||
|
||||
function RoleOption({ active, role, onClick, selected }) {
|
||||
return h(
|
||||
'button',
|
||||
{
|
||||
class: [
|
||||
active ? 'bg-surface-gray-2' : 'text-ink-gray-8',
|
||||
'group flex w-full justify-between items-center rounded-md px-2 py-2 text-sm',
|
||||
],
|
||||
onClick: !selected ? onClick : null,
|
||||
},
|
||||
[
|
||||
h('span', { class: 'whitespace-nowrap' }, role),
|
||||
selected
|
||||
? h(LucideCheck, {
|
||||
class: ['h-4 w-4 shrink-0 text-ink-gray-7'],
|
||||
'aria-hidden': true,
|
||||
})
|
||||
: null,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
function updateRole(user, newRole) {
|
||||
if (user.role === newRole) return
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import LucideCheck from '~icons/lucide/check'
|
||||
import TaskStatusIcon from '@/components/Icons/TaskStatusIcon.vue'
|
||||
import TaskPriorityIcon from '@/components/Icons/TaskPriorityIcon.vue'
|
||||
import { usersStore } from '@/stores/users'
|
||||
import { gemoji } from 'gemoji'
|
||||
import { getMeta } from '@/stores/meta'
|
||||
import { toast, dayjsLocal, dayjs, getConfig } from 'frappe-ui'
|
||||
import { toast, dayjsLocal, dayjs, getConfig, FeatherIcon } from 'frappe-ui'
|
||||
import { h } from 'vue'
|
||||
|
||||
export function formatTime(seconds) {
|
||||
@ -465,3 +466,66 @@ export function runSequentially(functions) {
|
||||
return promise.then(() => fn())
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
export function DropdownOption({
|
||||
active,
|
||||
option,
|
||||
theme,
|
||||
icon,
|
||||
onClick,
|
||||
selected,
|
||||
}) {
|
||||
return h(
|
||||
'button',
|
||||
{
|
||||
class: [
|
||||
active ? 'bg-surface-gray-2' : 'text-ink-gray-8',
|
||||
'group flex w-full justify-between items-center rounded-md px-2 py-2 text-sm',
|
||||
theme == 'danger' ? 'text-ink-red-3 hover:bg-ink-red-1' : '',
|
||||
],
|
||||
onClick: !selected ? onClick : null,
|
||||
},
|
||||
[
|
||||
h('div', { class: 'flex gap-2' }, [
|
||||
icon
|
||||
? h(FeatherIcon, {
|
||||
name: icon,
|
||||
class: ['h-4 w-4 shrink-0'],
|
||||
'aria-hidden': true,
|
||||
})
|
||||
: null,
|
||||
h('span', { class: 'whitespace-nowrap' }, option),
|
||||
]),
|
||||
selected
|
||||
? h(LucideCheck, {
|
||||
class: ['h-4 w-4 shrink-0 text-ink-gray-7'],
|
||||
'aria-hidden': true,
|
||||
})
|
||||
: null,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function TemplateOption({ active, option, theme, icon, onClick }) {
|
||||
return h(
|
||||
'button',
|
||||
{
|
||||
class: [
|
||||
active ? 'bg-surface-gray-2 text-ink-gray-8' : 'text-ink-gray-7',
|
||||
'group flex w-full gap-2 items-center rounded-md px-2 py-2 text-sm',
|
||||
theme == 'danger' ? 'text-ink-red-3 hover:bg-ink-red-1' : '',
|
||||
],
|
||||
onClick: onClick,
|
||||
},
|
||||
[
|
||||
icon
|
||||
? h(FeatherIcon, {
|
||||
name: icon,
|
||||
class: ['h-4 w-4 shrink-0'],
|
||||
'aria-hidden': true,
|
||||
})
|
||||
: null,
|
||||
h('span', { class: 'whitespace-nowrap' }, option),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user