1
0
forked from test/crm

style: better spacing

(cherry picked from commit 28ece820ed38dde603102ee5efe711bbc3fc058b)
This commit is contained in:
Shariq Ansari 2025-06-26 16:46:23 +05:30 committed by Mergify
parent 65b635eeb3
commit 4f1348b08c
3 changed files with 130 additions and 73 deletions

View File

@ -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>

View File

@ -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

View File

@ -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),
],
)
}