1
1
<script setup lang="ts">
2
2
import { Download , Search , Table2Icon } from ' lucide-vue-next'
3
3
import { computed , ref } from ' vue'
4
- import { formatNumber } from ' ../helpers'
4
+ import { createHeaders , formatNumber } from ' ../helpers'
5
5
import { FIELDTYPES } from ' ../helpers/constants'
6
6
import { QueryResultColumn , QueryResultRow } from ' ../types/query.types'
7
+ import DataTableColumn from ' ./DataTableColumn.vue'
7
8
8
9
const emit = defineEmits ({
10
+ sort : (sort_order : Record <string , ' asc' | ' desc' >) => true ,
9
11
' cell-dbl-click' : (row : QueryResultRow , column : QueryResultColumn ) => true ,
10
12
})
11
13
const props = defineProps <{
@@ -16,10 +18,17 @@ const props = defineProps<{
16
18
showFilterRow? : boolean
17
19
loading? : boolean
18
20
onExport? : Function
21
+ sortOrder? : Record <string , ' asc' | ' desc' >
19
22
}>()
20
23
21
- const isNumberColumn = (col : QueryResultColumn ) => FIELDTYPES .NUMBER .includes (col .type )
24
+ const headers = computed (() => {
25
+ if (! props .columns ?.length ) return []
26
+ return createHeaders (props .columns )
27
+ })
28
+
29
+ const isNumberColumn = (col : QueryResultColumn ): boolean => FIELDTYPES .NUMBER .includes (col .type )
22
30
31
+ const filterPerColumn = ref <Record <string , string >>({})
23
32
const visibleRows = computed (() => {
24
33
const columns = props .columns
25
34
const rows = props .rows
@@ -96,33 +105,61 @@ const totalColumnTotal = computed(() => {
96
105
return Object .values (totalPerColumn .value ).reduce ((acc , val ) => acc + val , 0 )
97
106
})
98
107
99
- const filterPerColumn = ref <Record <string , string >>({})
108
+ const sortOrder = ref <Record <string , ' asc' | ' desc' >>({ ... props .sortOrder })
109
+ function getSortOrder(column : QueryResultColumn ) {
110
+ return sortOrder .value [column .name ]
111
+ }
112
+ function sortBy(column : QueryResultColumn , direction : ' asc' | ' desc' | ' ' ) {
113
+ if (! direction ) {
114
+ delete sortOrder .value [column .name ]
115
+ } else {
116
+ sortOrder .value [column .name ] = direction
117
+ }
118
+ emit (' sort' , sortOrder .value )
119
+ }
100
120
</script >
101
121
102
122
<template >
103
123
<div
104
124
v-if =" columns?.length || rows?.length"
105
- class =" flex h-full w-full flex-col overflow-hidden font-mono text-sm"
125
+ class =" flex h-full w-full flex-col overflow-hidden text-sm"
106
126
>
107
127
<div class =" w-full flex-1 overflow-y-auto" >
108
128
<table class =" relative h-full w-full border-separate border-spacing-0" >
109
129
<thead class =" sticky top-0 z-10 bg-white" >
110
- <tr >
130
+ <tr v-for = " headerRow in headers " >
111
131
<td
112
132
class =" sticky left-0 z-10 whitespace-nowrap border-b border-r bg-white"
113
133
width =" 1%"
114
134
></td >
115
135
<td
116
- v-for =" (column , idx) in props.columns "
136
+ v-for =" (header , idx) in headerRow "
117
137
:key =" idx"
118
138
class =" border-b border-r"
119
- :class =" isNumberColumn(column) ? 'text-right' : 'text-left'"
139
+ :class =" [
140
+ header.isLast && isNumberColumn(header.column)
141
+ ? 'text-right'
142
+ : 'text-left',
143
+ ]"
144
+ :colspan =" header.colspan"
120
145
>
121
- <slot name =" column-header" :column =" column" >
122
- <div class =" truncate py-2 px-3" >
123
- {{ column.name }}
124
- </div >
146
+ <slot
147
+ v-if =" header.isLast"
148
+ name =" column-header"
149
+ :column =" header.column"
150
+ :label =" header.label"
151
+ >
152
+ <DataTableColumn
153
+ :column =" header.column"
154
+ :label =" header.label"
155
+ :sort-order =" getSortOrder(header.column)"
156
+ @sort-change =" sortBy(header.column, $event)"
157
+ />
125
158
</slot >
159
+
160
+ <div v-else class =" flex h-7 items-center truncate px-3" >
161
+ {{ header.label }}
162
+ </div >
126
163
</td >
127
164
128
165
<td
@@ -133,6 +170,7 @@ const filterPerColumn = ref<Record<string, string>>({})
133
170
<div class =" truncate pl-3 pr-20" ></div >
134
171
</td >
135
172
</tr >
173
+
136
174
<tr v-if =" props.showFilterRow" >
137
175
<td
138
176
class =" sticky left-0 z-10 whitespace-nowrap border-b border-r bg-white"
@@ -141,13 +179,13 @@ const filterPerColumn = ref<Record<string, string>>({})
141
179
<td
142
180
v-for =" (column, idx) in props.columns"
143
181
:key =" idx"
144
- class =" border-b border-r p-0.5 "
182
+ class =" border-b border-r p-1 "
145
183
>
146
184
<FormControl
147
185
type =" text"
148
186
v-model =" filterPerColumn[column.name]"
149
187
autocomplete =" off"
150
- class =" [& _input]:bg-gray-200/80"
188
+ class =" [& _input]:h-6 [ & _input]: bg-gray-200/80"
151
189
>
152
190
<template #prefix >
153
191
<Search class =" h-4 w-4 text-gray-500" stroke-width =" 1.5" />
@@ -235,17 +273,17 @@ const filterPerColumn = ref<Record<string, string>>({})
235
273
</slot >
236
274
</div >
237
275
238
- <div
239
- v-else-if =" props.loading"
240
- class =" absolute top-10 z-10 flex h-[calc(100%-2rem)] w-full items-center justify-center rounded bg-white/30 backdrop-blur-sm"
241
- >
242
- <LoadingIndicator class =" h-8 w-8 text-gray-700" />
243
- </div >
244
-
245
276
<div v-else class =" flex h-full w-full items-center justify-center" >
246
277
<div class =" flex flex-col items-center gap-2" >
247
278
<Table2Icon class =" h-16 w-16 text-gray-300" stroke-width =" 1.5" />
248
279
<p class =" text-center text-gray-500" >No data to display.</p >
249
280
</div >
250
281
</div >
282
+
283
+ <div
284
+ v-if =" props.loading"
285
+ class =" absolute top-10 z-10 flex h-[calc(100%-2rem)] w-full items-center justify-center rounded bg-white/30 backdrop-blur-sm"
286
+ >
287
+ <LoadingIndicator class =" h-8 w-8 text-gray-700" />
288
+ </div >
251
289
</template >
0 commit comments