diff --git a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue index 843a1a81ea..730ac4e96c 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupByTable.vue @@ -57,6 +57,7 @@ function addEmptyRow(group: Group, addAfter?: number, metaValue = meta.value) { } return acc }, {} as Record) + group.count = group.count + 1 group.rows.splice(addAfter, 0, { row: { @@ -77,18 +78,30 @@ const formattedData = computed(() => { return [] as Row[] }) -const { deleteRow, deleteSelectedRows, deleteRangeOfRows, updateOrSaveRow, bulkUpdateRows, selectedAllRecords, removeRowIfNew } = - useData({ - meta, - viewMeta: view, - formattedData, - paginationData: ref(vGroup.value.paginationData), - callbacks: { - changePage: (p: number) => props.loadGroupPage(vGroup.value, p), - loadData: () => props.loadGroupData(vGroup.value, true), - globalCallback: () => props.redistributeRows?.(), - }, - }) +const { + deleteRow: _deleteRow, + deleteSelectedRows, + deleteRangeOfRows, + updateOrSaveRow, + bulkUpdateRows, + selectedAllRecords, + removeRowIfNew, +} = useData({ + meta, + viewMeta: view, + formattedData, + paginationData: ref(vGroup.value.paginationData), + callbacks: { + changePage: (p: number) => props.loadGroupPage(vGroup.value, p), + loadData: () => props.loadGroupData(vGroup.value, true), + globalCallback: () => props.redistributeRows?.(), + }, +}) + +const deleteRow = async (rowIndex: number) => { + vGroup.value.count = vGroup.value.count - 1 + await _deleteRow(rowIndex) +} const reloadTableData = async (params: void | { shouldShowLoading?: boolean | undefined; offset?: number | undefined }) => { await props.loadGroupData(vGroup.value, true, { diff --git a/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue b/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue index cec1b4e1f6..e34f3fd1f3 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue @@ -210,7 +210,7 @@ eventBus.on(async (event, column) => { @click.stop > diff --git a/packages/nc-gui/composables/useViewGroupBy.ts b/packages/nc-gui/composables/useViewGroupBy.ts index 1f219b4bbc..1f161b79a4 100644 --- a/packages/nc-gui/composables/useViewGroupBy.ts +++ b/packages/nc-gui/composables/useViewGroupBy.ts @@ -1,5 +1,5 @@ -import { UITypes } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk' +import { UITypes } from 'nocodb-sdk' import type { Ref } from 'vue' import { message } from 'ant-design-vue' @@ -207,6 +207,18 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( }, existing) } + const getSortParams = (sort: string) => { + if (sort === 'asc') { + return '+' + } else if (sort === 'desc') { + return '-' + } else if (sort === 'count-asc') { + return '~+' + } else if (sort === 'count-desc') { + return '~-' + } + } + async function loadGroups(params: any = {}, group?: Group) { try { group = group || rootGroup.value @@ -246,7 +258,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), where: `${nestedWhere}`, - sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, + sort: `${getSortParams(groupby.sort)}${groupby.column.title}`, column_name: groupby.column.title, } as any) : await api.public.dataGroupBy( @@ -256,7 +268,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( limit: group.paginationData.pageSize ?? groupByGroupLimit.value, ...params, where: nestedWhere, - sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, + sort: `${getSortParams(groupby.sort)}${groupby.column.title}`, column_name: groupby.column.title, sortsArr: sorts.value, filtersArr: nestedFilters.value, diff --git a/packages/nc-gui/utils/sortUtils.ts b/packages/nc-gui/utils/sortUtils.ts index b310e17938..6896a367ed 100644 --- a/packages/nc-gui/utils/sortUtils.ts +++ b/packages/nc-gui/utils/sortUtils.ts @@ -1,6 +1,13 @@ import { UITypes } from 'nocodb-sdk' -export const getSortDirectionOptions = (uidt: UITypes | string) => { +export const getSortDirectionOptions = (uidt: UITypes | string, isGroupBy?: boolean) => { + const groupByOptions = isGroupBy + ? [ + { text: 'Count (9 → 1)', value: 'count-desc' }, + { text: 'Count (1 → 9)', value: 'count-asc' }, + ] + : [] + switch (uidt) { case UITypes.Year: case UITypes.Number: @@ -20,16 +27,16 @@ export const getSortDirectionOptions = (uidt: UITypes | string) => { return [ { text: '1 → 9', value: 'asc' }, { text: '9 → 1', value: 'desc' }, - ] + ].concat(groupByOptions) case UITypes.Checkbox: return [ { text: '▢ → ✓', value: 'asc' }, { text: '✓ → ▢', value: 'desc' }, - ] + ].concat(groupByOptions) default: return [ { text: 'A → Z', value: 'asc' }, { text: 'Z → A', value: 'desc' }, - ] + ].concat(groupByOptions) } } diff --git a/packages/nocodb/src/db/BaseModelSqlv2.ts b/packages/nocodb/src/db/BaseModelSqlv2.ts index 2babe1eab5..602d4ff529 100644 --- a/packages/nocodb/src/db/BaseModelSqlv2.ts +++ b/packages/nocodb/src/db/BaseModelSqlv2.ts @@ -811,17 +811,33 @@ class BaseModelSqlv2 { return qb.toQuery(); }, this.dbDriver.raw(`??`, [columnName]).toQuery()); - qb.orderBy( - sanitize(this.dbDriver.raw(finalStatement)), - sort.direction, - sort.direction === 'desc' ? 'LAST' : 'FIRST', - ); + if (!['asc', 'desc'].includes(sort.direction)) { + qb.orderBy( + 'count', + sort.direction === 'count-desc' ? 'desc' : 'asc', + sort.direction === 'count-desc' ? 'LAST' : 'FIRST', + ); + } else { + qb.orderBy( + sanitize(this.dbDriver.raw(finalStatement)), + sort.direction, + sort.direction === 'desc' ? 'LAST' : 'FIRST', + ); + } } else { - qb.orderBy( - column.id, - sort.direction, - sort.direction === 'desc' ? 'LAST' : 'FIRST', - ); + if (!['asc', 'desc'].includes(sort.direction)) { + qb.orderBy( + 'count', + sort.direction === 'count-desc' ? 'desc' : 'asc', + sort.direction === 'count-desc' ? 'LAST' : 'FIRST', + ); + } else { + qb.orderBy( + column.id, + sort.direction, + sort.direction === 'desc' ? 'LAST' : 'FIRST', + ); + } } } @@ -6612,7 +6628,6 @@ export function extractSortsObject( if (!_sorts?.length) return; let sorts = _sorts; - if (!Array.isArray(sorts)) sorts = sorts.split(/\s*,\s*/); return sorts.map((s) => { @@ -6620,13 +6635,20 @@ export function extractSortsObject( if (s.startsWith('-')) { sort.direction = 'desc'; sort.fk_column_id = aliasColObjMap[s.slice(1)]?.id; + } else if (s.startsWith('~-')) { + sort.direction = 'count-desc'; + sort.fk_column_id = aliasColObjMap[s.slice(2)]?.id; + } else if (s.startsWith('~+')) { + sort.direction = 'count-asc'; + sort.fk_column_id = aliasColObjMap[s.slice(2)]?.id; } // replace + at the beginning if present - else sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id; + else { + sort.fk_column_id = aliasColObjMap[s.replace(/^\+/, '')]?.id; + } if (throwErrorIfInvalid && !sort.fk_column_id) NcError.fieldNotFound(s.replace(/^[+-]/, '')); - return new Sort(sort); }); } diff --git a/packages/nocodb/src/models/Sort.ts b/packages/nocodb/src/models/Sort.ts index b82ebdb0c6..737911173d 100644 --- a/packages/nocodb/src/models/Sort.ts +++ b/packages/nocodb/src/models/Sort.ts @@ -17,7 +17,7 @@ export default class Sort { fk_view_id: string; fk_column_id?: string; - direction?: 'asc' | 'desc'; + direction?: 'asc' | 'desc' | 'count-desc' | 'count-asc'; base_id?: string; source_id?: string; diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index c32b685c6f..7203abbf42 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/packages/nocodb/src/schema/swagger.json @@ -23351,7 +23351,9 @@ "description": "Sort direction", "enum": [ "asc", - "desc" + "desc", + "count-desc", + "count-asc" ], "example": "desc" },