Browse Source

fix: sorter UI (#8393)

* feat: use inline sorter instead of dropdown

* chore: lint

* fix: comparison logic for useUserSort
pull/8400/head
Mert E 7 months ago committed by GitHub
parent
commit
838c716a08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 32
      packages/nc-gui/components/account/HeaderWithSorter.vue
  2. 33
      packages/nc-gui/components/account/UserList.vue
  3. 79
      packages/nc-gui/components/account/UserMenu.vue
  4. 29
      packages/nc-gui/components/project/AccessSettings.vue
  5. 30
      packages/nc-gui/components/workspace/CollaboratorsList.vue
  6. 26
      packages/nc-gui/composables/useUserSorts.ts
  7. 4
      packages/nc-gui/utils/iconUtils.ts

32
packages/nc-gui/components/account/HeaderWithSorter.vue

@ -0,0 +1,32 @@
<script setup lang="ts">
const { header, field, toggleSort } = defineProps<{
header: string
activeSort: { field?: string; direction?: string }
field: UsersSortType['field']
toggleSort: Function
}>()
</script>
<template>
<div class="flex items-center space-x-2 cursor-pointer text-gray-700" @click="toggleSort(field)">
<span>
{{ header }}
</span>
<div class="flex flex-col">
<GeneralIcon
icon="arrowDropUp"
class="text-sm mb-[-10px] text-[16px]"
:class="{
'text-primary': activeSort.field === field && activeSort.direction === 'asc',
}"
/>
<GeneralIcon
icon="arrowDropDown"
class="text-sm text-[16px]"
:class="{
'text-primary': activeSort.field === field && activeSort.direction === 'desc',
}"
/>
</div>
</div>
</template>

33
packages/nc-gui/components/account/UserList.vue

@ -28,7 +28,7 @@ const { user: loggedInUser } = useGlobal()
const { copy } = useCopy()
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Org')
const { sorts, loadSorts, handleGetSortedData, toggleSort } = useUserSorts('Org')
const users = ref<UserType[]>([])
@ -198,21 +198,22 @@ const openDeleteModal = (user: UserType) => {
</div>
<div class="w-full rounded-md max-w-250 h-[calc(100%-12rem)] rounded-md overflow-hidden mt-5">
<div class="flex w-full bg-gray-50 border-1 rounded-t-md">
<div
class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6 flex items-center space-x-2"
data-rec="true"
>
<span>
{{ $t('objects.users') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" />
</div>
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start flex items-center space-x-2" data-rec="true">
<span>
{{ $t('general.access') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div>
<LazyAccountHeaderWithSorter
class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6"
:header="$t('objects.users')"
:active-sort="sorts"
field="email"
:toggle-sort="toggleSort"
/>
<LazyAccountHeaderWithSorter
class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start"
:header="$t('general.access')"
:active-sort="sorts"
field="roles"
:toggle-sort="toggleSort"
/>
<div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4" data-rec="true">
{{ $t('labels.action') }}
</div>

79
packages/nc-gui/components/account/UserMenu.vue

@ -1,79 +0,0 @@
<script lang="ts" setup>
import { iconMap } from '#imports'
import type { UsersSortType } from '~/lib'
const { field, direction, handleUserSort } = defineProps<{
field: UsersSortType['field']
direction?: UsersSortType['direction']
handleUserSort: Function
}>()
const isOpen = ref(false)
const sortUserBy = (direction?: UsersSortType['direction']) => {
handleUserSort({
field,
direction,
})
isOpen.value = false
}
</script>
<template>
<a-dropdown
v-model:visible="isOpen"
:trigger="['click']"
placement="bottomLeft"
overlay-class-name="nc-user-menu-column-operations !border-1 rounded-lg !shadow-xl"
@click.stop="isOpen = !isOpen"
>
<div>
<GeneralIcon
:icon="direction === 'asc' || direction === 'desc' ? 'sortDesc' : 'arrowDown'"
class="text-grey h-full text-grey nc-user-menu-trigger cursor-pointer outline-0 mr-2 transition-none"
:style="{ transform: direction === 'asc' ? 'rotate(180deg)' : undefined }"
/>
</div>
<template #overlay>
<NcMenu class="flex flex-col gap-1 border-gray-200 nc-user-menu-column-options">
<NcMenuItem @click="sortUserBy('asc')">
<div class="nc-column-insert-after nc-user-menu-item">
<component
:is="iconMap.sortDesc"
class="text-gray-700 !rotate-180 !w-4.25 !h-4.25"
:style="{
transform: 'rotate(180deg)',
}"
/>
<!-- Sort Ascending -->
{{ $t('general.sortAsc') }}
</div>
</NcMenuItem>
<NcMenuItem @click="sortUserBy('desc')">
<div class="nc-column-insert-before nc-user-menu-item">
<component :is="iconMap.sortDesc" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25" />
<!-- Sort Descending -->
{{ $t('general.sortDesc') }}
</div>
</NcMenuItem>
</NcMenu>
</template>
</a-dropdown>
</template>
<style scoped>
.nc-user-menu-item {
@apply flex items-center gap-2;
}
.nc-user-menu-column-options {
.nc-icons {
@apply !w-5 !h-5;
}
}
:deep(.ant-dropdown-menu-item) {
@apply !hover:text-black text-gray-700;
}
</style>

29
packages/nc-gui/components/project/AccessSettings.vue

@ -14,7 +14,7 @@ const { activeProjectId, bases } = storeToRefs(basesStore)
const { orgRoles, baseRoles, loadRoles } = useRoles()
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Project')
const { sorts, loadSorts, handleGetSortedData, toggleSort } = useUserSorts('Project')
const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN])
@ -222,19 +222,22 @@ watch(currentBase, () => {
<div v-else class="nc-collaborators-list mt-6 h-full">
<div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]">
<div class="flex flex-row bg-gray-50 min-h-12 items-center border-b-1">
<div class="text-gray-700 users-email-grid flex items-center space-x-2">
<span>
{{ $t('objects.users') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" />
</div>
<LazyAccountHeaderWithSorter
class="users-email-grid"
:header="$t('objects.users')"
:active-sort="sorts"
field="email"
:toggle-sort="toggleSort"
/>
<LazyAccountHeaderWithSorter
class="user-access-grid"
:header="$t('general.role')"
:active-sort="sorts"
field="roles"
:toggle-sort="toggleSort"
/>
<div class="text-gray-700 user-access-grid flex items-center space-x-2">
<span>
{{ $t('general.role') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div>
<div class="text-gray-700 date-joined-grid">{{ $t('title.dateJoined') }}</div>
</div>

30
packages/nc-gui/components/workspace/CollaboratorsList.vue

@ -18,7 +18,7 @@ const currentWorkspace = computed(() => {
return props.workspaceId ? workspaces.value.get(props.workspaceId) : _activeWorkspace.value
})
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Workspace')
const { sorts, loadSorts, handleGetSortedData, toggleSort } = useUserSorts('Workspace')
const userSearchText = ref('')
@ -117,18 +117,22 @@ onMounted(async () => {
<div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-4rem)]">
<div class="flex flex-row bg-gray-50 min-h-11 items-center border-b-1">
<div class="py-3 px-6"><NcCheckbox v-model:checked="selectAll" /></div>
<div class="text-gray-700 w-[30rem] users-email-grid flex items-center space-x-2">
<span>
{{ $t('objects.users') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" :handle-user-sort="saveOrUpdate" field="email" />
</div>
<div class="text-gray-700 w-full flex-1 px-6 py-3 flex items-center space-x-2">
<span>
{{ $t('general.access') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div>
<LazyAccountHeaderWithSorter
class="text-gray-700 w-[30rem] users-email-grid"
:header="$t('objects.users')"
:active-sort="sorts"
field="email"
:toggle-sort="toggleSort"
/>
<LazyAccountHeaderWithSorter
class="text-gray-700 w-full flex-1 px-6 py-3"
:header="$t('general.access')"
:active-sort="sorts"
field="roles"
:toggle-sort="toggleSort"
/>
<div class="text-gray-700 w-full flex-1 px-6 py-3">{{ $t('title.dateJoined') }}</div>
<div class="text-gray-700 w-full text-right flex-1 px-6 py-3">{{ $t('labels.actions') }}</div>
</div>

26
packages/nc-gui/composables/useUserSorts.ts

@ -131,20 +131,14 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project' | 'Organi
return userRoleOrder.indexOf(roleB) - userRoleOrder.indexOf(roleA)
}
}
case 'email': {
case 'email':
case 'title': {
if (sortsConfig.direction === 'asc') {
return a[sortsConfig.field]?.localeCompare(b[sortsConfig.field])
} else {
return b[sortsConfig.field]?.localeCompare(a[sortsConfig.field])
}
}
case 'title': {
if (sortsConfig.direction === 'asc') {
return a[sortsConfig.field] - b[sortsConfig.field]
} else {
return b[sortsConfig.field] - a[sortsConfig.field]
}
}
}
return 0
@ -182,5 +176,19 @@ export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project' | 'Organi
return true
}
return { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData }
const toggleSort = (field: 'email' | 'roles' | 'title' | 'id') => {
if (sorts.value.field === field) {
saveOrUpdate({
field,
...(sortDirection.value[field] === 'asc' ? { direction: 'desc' } : {}),
})
} else {
saveOrUpdate({
field,
direction: 'asc',
})
}
}
return { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData, toggleSort }
}

4
packages/nc-gui/utils/iconUtils.ts

@ -61,6 +61,8 @@ import Down from '~icons/material-symbols/keyboard-arrow-down-rounded'
import PhTriangleFill from '~icons/ph/triangle-fill'
import LcSend from '~icons/lucide/send'
import MdiPuzzle from '~icons/mdi/puzzle'
import MaterialSymbolsArrowDropUp from '~icons/material-symbols/arrow-drop-up'
import MaterialSymbolsArrowDropDown from '~icons/material-symbols/arrow-drop-down'
import HasManyIcon from '~icons/nc-icons/hasmany'
import ManytoManyIcon from '~icons/nc-icons/manytomany'
@ -597,6 +599,8 @@ export const iconMap = {
ncSettings: NcSettings,
ncHelp: NcHelp,
puzzle: MdiPuzzle,
arrowDropUp: MaterialSymbolsArrowDropUp,
arrowDropDown: MaterialSymbolsArrowDropDown,
}
export const getMdiIcon = (type: string): any => {

Loading…
Cancel
Save