diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index d732b2aabf..82bc1678e3 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -200,6 +200,7 @@ const props = defineProps<{ sourceColumns?: Array; queryEditabilityReason?: string; initialWhereInput?: string; + initialOrderByInput?: string; sortColumn?: string; sortColumnIndex?: number; sortDirection?: DataGridSortDirection; @@ -235,6 +236,7 @@ const emit = defineEmits<{ paginate: [offset: number, limit: number, whereInput?: string, orderBy?: string]; sort: [column: string, columnIndex: number, direction: "asc" | "desc" | null, whereInput?: string]; "update:whereInput": [value: string]; + "update:orderByInput": [value: string]; }>(); console.info("[DBX][DataGrid:setup]", { @@ -431,7 +433,7 @@ const orderByMeasureRef = ref(); const orderBySuggestionLeft = ref(0); const orderBySuggestionPosition = ref({ left: 0, top: 0 }); -const orderByInput = ref(""); +const orderByInput = ref(props.initialOrderByInput ?? ""); const hasOrderByInput = computed(() => orderByInput.value.trim().length > 0); const whereFilterInput = ref(props.initialWhereInput ?? ""); const hasWhereFilterInput = computed(() => whereFilterInput.value.trim().length > 0); @@ -1361,6 +1363,7 @@ function navigateOrderBySuggestion(delta: number) { } watch(orderByInput, (val) => { + emit("update:orderByInput", val); orderBySuggestions.value = []; if (!props.tableMeta?.columns?.length) return; const trimmed = val.trim(); @@ -1756,16 +1759,24 @@ function currentOrderBy(): string | undefined { } function syncOrderByInputWithSort(column: string | null, direction: "asc" | "desc" | null) { - orderByInput.value = column && direction ? `${queryColumnRef(column)} ${direction.toUpperCase()}` : ""; + const nextOrderByInput = column && direction ? `${queryColumnRef(column)} ${direction.toUpperCase()}` : ""; + orderByInput.value = nextOrderByInput; + emit("update:orderByInput", nextOrderByInput); } watch( () => [props.sortColumn, props.sortColumnIndex, props.sortDirection] as const, - ([column, columnIndex, direction]) => { + ([column, columnIndex, direction], previous) => { + const wasControlledSort = !!previous?.[0] && !!previous?.[2]; + const isControlledSort = !!column && !!direction; sortCol.value = column && direction ? column : null; sortColIndex.value = typeof columnIndex === "number" && direction ? columnIndex : null; sortDir.value = direction ?? "asc"; - syncOrderByInputWithSort(sortCol.value, sortCol.value ? sortDir.value : null); + if (isControlledSort) { + syncOrderByInputWithSort(sortCol.value, sortDir.value); + } else if (wasControlledSort) { + syncOrderByInputWithSort(null, null); + } }, { immediate: true }, ); @@ -2752,6 +2763,7 @@ async function clearContextFilter() { async function applyOrderBySearch() { if (!props.tableMeta || !props.onExecuteSql) return; const orderByClause = orderByInput.value.trim() || undefined; + emit("update:orderByInput", orderByInput.value); isApplyingWhere.value = true; saveError.value = ""; currentPage.value = 1; diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index 44bea6a460..63cef987e1 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -463,6 +463,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :sort-column="activeTab.resultSortColumn" :sort-column-index="activeTab.resultSortColumnIndex" :sort-direction="activeTab.resultSortDirection" + :initial-order-by-input="activeTab.orderByInput" :sql="activeTab.lastExecutedSql || activeTab.sql" :loading="activeTab.isExecuting" :editable="!!activeTab.queryAnalysis" @@ -480,6 +481,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :total-row-count="activeTab.resultTotalRowCount" :on-execute-sql="async (sql: string) => emit('executeSql', sql)" :full-export-result="() => queryStore.fetchTabResultForExport(activeTab.id)" + @update:order-by-input="(v: string) => (activeTab.orderByInput = v)" @reload=" ( sql?: string, @@ -670,6 +672,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :sort-column="activeTab.resultSortColumn" :sort-column-index="activeTab.resultSortColumnIndex" :sort-direction="activeTab.resultSortDirection" + :initial-order-by-input="activeTab.orderByInput" :sql="activeTab.sql" :loading="activeTab.isExecuting" :editable="isTableDataEditable(activeConnection?.db_type, activeTab.tableMeta?.primaryKeys ?? [])" @@ -684,6 +687,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget }); :on-execute-sql="async (sql: string) => emit('executeSql', sql)" :full-export-result="() => queryStore.fetchTabResultForExport(activeTab.id)" @update:where-input="(v: string) => (activeTab.whereInput = v)" + @update:order-by-input="(v: string) => (activeTab.orderByInput = v)" @reload=" ( sql?: string, diff --git a/apps/desktop/src/lib/openTabsPersistence.ts b/apps/desktop/src/lib/openTabsPersistence.ts index 21b0b313b5..071b9bbaa8 100644 --- a/apps/desktop/src/lib/openTabsPersistence.ts +++ b/apps/desktop/src/lib/openTabsPersistence.ts @@ -15,6 +15,7 @@ export interface SavedOpenTab { resultSortColumn?: string; resultSortColumnIndex?: number; resultSortDirection?: QueryTab["resultSortDirection"]; + orderByInput?: string; resultPageLimit?: number; resultPageOffset?: number; whereInput?: string; @@ -47,6 +48,7 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] { ...(tab.resultSortColumn !== undefined ? { resultSortColumn: tab.resultSortColumn } : {}), ...(tab.resultSortColumnIndex !== undefined ? { resultSortColumnIndex: tab.resultSortColumnIndex } : {}), ...(tab.resultSortDirection !== undefined ? { resultSortDirection: tab.resultSortDirection } : {}), + ...(tab.orderByInput !== undefined ? { orderByInput: tab.orderByInput } : {}), ...(tab.resultPageLimit !== undefined ? { resultPageLimit: tab.resultPageLimit } : {}), ...(tab.resultPageOffset !== undefined ? { resultPageOffset: tab.resultPageOffset } : {}), ...(tab.whereInput !== undefined ? { whereInput: tab.whereInput } : {}), diff --git a/apps/desktop/src/stores/queryStore.ts b/apps/desktop/src/stores/queryStore.ts index beb41f3276..e892432b46 100644 --- a/apps/desktop/src/stores/queryStore.ts +++ b/apps/desktop/src/stores/queryStore.ts @@ -120,6 +120,7 @@ export const useQueryStore = defineStore("query", () => { resultSortColumn: t.resultSortColumn, resultSortColumnIndex: t.resultSortColumnIndex, resultSortDirection: t.resultSortDirection, + orderByInput: t.orderByInput, resultPageLimit: t.resultPageLimit, resultPageOffset: t.resultPageOffset, whereInput: t.whereInput, diff --git a/apps/desktop/src/types/database.ts b/apps/desktop/src/types/database.ts index 729e6eadfc..a2e3da3da0 100644 --- a/apps/desktop/src/types/database.ts +++ b/apps/desktop/src/types/database.ts @@ -351,6 +351,7 @@ export interface QueryTab { resultSortColumn?: string; resultSortColumnIndex?: number; resultSortDirection?: "asc" | "desc"; + orderByInput?: string; resultPageSql?: string; resultPageLimit?: number; resultPageOffset?: number; diff --git a/packages/app-tests/openTabsPersistence.test.ts b/packages/app-tests/openTabsPersistence.test.ts index 904889767e..cfb586f1b0 100644 --- a/packages/app-tests/openTabsPersistence.test.ts +++ b/packages/app-tests/openTabsPersistence.test.ts @@ -70,6 +70,7 @@ test("serializes table tabs with reload context", () => { resultPageLimit: 100, resultPageOffset: 100, whereInput: "active = true", + orderByInput: "created_at DESC", tableMeta: { schema: "public", tableName: "users", columns: [], primaryKeys: [] }, }), ]); @@ -81,6 +82,7 @@ test("serializes table tabs with reload context", () => { resultPageLimit: saved[0]?.resultPageLimit, resultPageOffset: saved[0]?.resultPageOffset, whereInput: saved[0]?.whereInput, + orderByInput: saved[0]?.orderByInput, tableMeta: saved[0]?.tableMeta, }, { @@ -89,6 +91,7 @@ test("serializes table tabs with reload context", () => { resultPageLimit: 100, resultPageOffset: 100, whereInput: "active = true", + orderByInput: "created_at DESC", tableMeta: { schema: "public", tableName: "users", columns: [], primaryKeys: [] }, }, ); @@ -145,6 +148,7 @@ test("restores data and structure tabs with table state", () => { resultPageLimit: 50, resultPageOffset: 50, whereInput: "id > 10", + orderByInput: "id DESC", tableMeta: { schema: "public", tableName: "users", @@ -184,6 +188,7 @@ test("restores data and structure tabs with table state", () => { assert.equal(restored.tabs[0]?.resultPageLimit, 50); assert.equal(restored.tabs[0]?.resultPageOffset, 50); assert.equal(restored.tabs[0]?.whereInput, "id > 10"); + assert.equal(restored.tabs[0]?.orderByInput, "id DESC"); assert.equal(restored.tabs[1]?.structureTableName, "users"); });