Skip to content

Commit 33b450a

Browse files
authored
DT-73: Implement Query History Drawer Section (#74)
* feat(query/history):Add QueryHistory component and integrate into DeploymentPage * feat(QueryHistory): Enhance query deletion and integrate history management in DeploymentPage * feat(QueryHistory, Filtre, Sidebar, DeploymentPage): Enhance filter and query history functionalities, improve UI components, and fix local storage key consistency
1 parent 5c26217 commit 33b450a

File tree

4 files changed

+231
-55
lines changed

4 files changed

+231
-55
lines changed

web/components/Filtre.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { navigate, url } from '../lib/router.tsx'
33

44
type FilterRow = { idx: number; key: string; op: string; value: string }
55

6-
const filterOperators = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'like'] as const
6+
const filterOperators = [
7+
'eq',
8+
'neq',
9+
'gt',
10+
'gte',
11+
'lt',
12+
'lte',
13+
'like',
14+
'ilike',
15+
] as const
716

817
export function parseFilters(prefix: string): FilterRow[] {
918
const data = url.getAll(`f${prefix}`)
@@ -28,7 +37,10 @@ function setFilters(prefix: string, rows: FilterRow[]) {
2837
const v = rows[i]
2938
next.push([v.key, v.op, v.value].join(','))
3039
}
31-
navigate({ params: { [`f${prefix}`]: next }, replace: true })
40+
navigate({
41+
params: { [`f${prefix}`]: next, [`${prefix}page`]: null },
42+
replace: true,
43+
})
3244
}
3345

3446
function addFilter(prefix: string) {
@@ -115,12 +127,17 @@ export const FilterMenu = (
115127
const rows = parseFilters(prefix)
116128

117129
return (
118-
<details class='dropdown dropdown-end'>
119-
<summary class='btn btn-outline btn-sm'>
130+
<div class='dropdown dropdown-end'>
131+
<button
132+
type='button'
133+
class='btn btn-outline btn-sm'
134+
popovertarget='popover-1'
135+
style='anchor-name:--anchor-1'
136+
>
120137
<Filter class='h-4 w-4' />
121138
Filters
122-
</summary>
123-
<div class='dropdown-content z-10 w-110 mt-2'>
139+
</button>
140+
<div class='dropdown-content w-110 mt-2'>
124141
<div class='bg-base-100 rounded-box shadow border border-base-300 p-3 space-y-3'>
125142
<div class='space-y-2 max-h-72 overflow-y-auto pr-1'>
126143
{rows.map((r) => (
@@ -185,7 +202,7 @@ export const FilterMenu = (
185202
</button>
186203
</div>
187204
</div>
188-
</details>
205+
</div>
189206
)
190207
}
191208

@@ -196,12 +213,17 @@ export const SortMenu = ({ tag, sortKeyOptions }: {
196213
const prefix = tag === 'tables' ? 't' : 'l'
197214
const rows = parseSort(prefix)
198215
return (
199-
<details class='dropdown dropdown-end'>
200-
<summary class='btn btn-outline btn-sm'>
216+
<div class='dropdown dropdown-end'>
217+
<button
218+
type='button'
219+
class='btn btn-outline btn-sm'
220+
popovertarget='popover-1'
221+
style='anchor-name:--anchor-1'
222+
>
201223
<ArrowUpDown class='h-4 w-4' />
202224
Sort
203-
</summary>
204-
<div class='dropdown-content z-10 w-80 mt-2'>
225+
</button>
226+
<div class='dropdown-content w-80 mt-2'>
205227
<div class='bg-base-100 rounded-box shadow border border-base-300 p-3 space-y-3'>
206228
<div class='space-y-2 max-h-60 overflow-y-auto pr-1'>
207229
{rows.map((r) => (
@@ -253,6 +275,6 @@ export const SortMenu = ({ tag, sortKeyOptions }: {
253275
</button>
254276
</div>
255277
</div>
256-
</details>
278+
</div>
257279
)
258280
}

web/components/QueryHistory.tsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { ChevronRight, Clock, Play, Search, Trash2 } from 'lucide-preact'
2+
import { A, navigate, url } from '../lib/router.tsx'
3+
import { onRun, queriesHistory } from '../pages/DeploymentPage.tsx'
4+
5+
export type QueryHistoryItem = {
6+
query: string
7+
timestamp: string
8+
columns?: number
9+
rows?: number
10+
}
11+
12+
const deleteQuery = (hash: string) => {
13+
const updatedHistory = { ...queriesHistory.value }
14+
delete updatedHistory[hash]
15+
queriesHistory.value = updatedHistory
16+
}
17+
18+
export const QueryHistory = () => {
19+
const filteredHistory = Object.entries(queriesHistory.value).sort((a, b) =>
20+
new Date(b[1].timestamp).getTime() - new Date(a[1].timestamp).getTime()
21+
).filter(([_, item]) => {
22+
const qh = url.params.qh?.toLowerCase() || ''
23+
return item.query.toLowerCase().includes(qh)
24+
})
25+
26+
return (
27+
<div class='flex flex-col h-full'>
28+
<div class='p-4 border-b border-base-300'>
29+
<h2 class='text-lg font-semibold'>Query History</h2>
30+
<div class='flex items-center justify-between mt-4 gap-2'>
31+
<label class='input input-sm min-w-0 w-full sm:w-64'>
32+
<Search class='opacity-50' />
33+
<input
34+
type='search'
35+
class='grow'
36+
placeholder='Search'
37+
value={url.params.qh || ''}
38+
onInput={(e) => {
39+
const value = (e.target as HTMLInputElement).value
40+
navigate({ params: { qh: value || null }, replace: true })
41+
}}
42+
/>
43+
</label>
44+
<button
45+
type='button'
46+
class='btn btn-xs btn-ghost text-error'
47+
title='Delete All from history'
48+
disabled={Object.keys(queriesHistory.value).length === 0}
49+
onClick={() => queriesHistory.value = {}}
50+
>
51+
<Trash2 class='w-4 h-4' />
52+
Clear All
53+
</button>
54+
</div>
55+
</div>
56+
<div class='flex-1 overflow-auto'>
57+
{filteredHistory.map(([hash, item]) => (
58+
<div
59+
key={hash}
60+
class='p-4 border-b border-base-300 hover:bg-base-200'
61+
>
62+
<div class='flex items-center justify-between'>
63+
<div class='flex-1 min-w-0'>
64+
<div class='text-xs text-base-content/60 flex items-center gap-2'>
65+
<Clock class='w-3 h-3' />
66+
{new Date(item.timestamp).toLocaleString()}
67+
</div>
68+
<p class='font-mono text-sm truncate mt-1' title={item.query}>
69+
{item.query}
70+
</p>
71+
<div class='text-xs text-base-content/60 mt-1'>
72+
{item.columns} columns, {item.rows} rows
73+
</div>
74+
</div>
75+
<div class='flex items-center gap-2 shrink-0 ml-4'>
76+
<A
77+
params={{ q: item.query, tab: 'queries' }}
78+
class='btn btn-xs btn-ghost'
79+
title='Run query'
80+
onClick={() => onRun(item.query)}
81+
>
82+
<Play class='w-4 h-4' />
83+
</A>
84+
<A
85+
class='btn btn-xs btn-ghost'
86+
title='Insert into editor'
87+
params={{ q: item.query, tab: 'queries' }}
88+
>
89+
<ChevronRight class='w-4 h-4' />
90+
</A>
91+
<button
92+
type='button'
93+
class='btn btn-xs btn-ghost text-error'
94+
title='Delete from history'
95+
disabled={!deleteQuery}
96+
onClick={() => deleteQuery(hash)}
97+
>
98+
<Trash2 class='w-4 h-4' />
99+
</button>
100+
</div>
101+
</div>
102+
</div>
103+
))}
104+
{filteredHistory.length === 0 && (
105+
<div class='p-4 text-center text-base-content/60'>
106+
No queries found.
107+
</div>
108+
)}
109+
</div>
110+
</div>
111+
)
112+
}

web/components/SideBar.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,17 @@ export function Sidebar(
2121
title?: string
2222
},
2323
) {
24-
const { sb } = url.params
24+
const sb = url.params.sb
2525
return (
2626
<div class='drawer-side'>
27-
<label htmlFor='drawer-toggle' class='drawer-overlay'></label>
2827
<div
2928
class={`${
30-
sb ? 'w-16' : 'w-64'
29+
sb ? 'w-64' : 'w-16'
3130
} min-h-94/100 bg-base-100 border-r border-base-300 flex flex-col transition-all duration-300`}
3231
>
3332
<div class='p-4 border-b border-base-300'>
3433
<div class='flex items-center justify-between'>
35-
{!sb && (
34+
{sb && (
3635
<span class='text-sm font-medium rounded bg-base-200 w-7/10 py-1 px-2 text-center text-base-content/60'>
3736
{title || 'Project Name'}
3837
</span>
@@ -42,8 +41,8 @@ export function Sidebar(
4241
class='p-2 hover:bg-base-200 rounded'
4342
>
4443
{sb
45-
? <ChevronsRight class='h-4 w-4 text-base-content/60' />
46-
: <ChevronsLeft class='h-4 w-4 text-base-content/60' />}
44+
? <ChevronsLeft class='h-4 w-4 text-base-content/60' />
45+
: <ChevronsRight class='h-4 w-4 text-base-content/60' />}
4746
</A>
4847
</div>
4948
</div>
@@ -54,12 +53,12 @@ export function Sidebar(
5453
<li key={slug}>
5554
<A
5655
class={`${sbi === slug ? 'bg-base-200' : ''}`}
57-
data-tip={sb ? item.label : undefined}
56+
data-tip={sb ? undefined : item.label}
5857
params={{ sbi: slug }}
5958
replace
6059
>
6160
<item.icon class='h-4 w-4' />
62-
{!sb && item.label}
61+
{sb && item.label}
6362
</A>
6463
</li>
6564
))}
@@ -77,7 +76,7 @@ export function Sidebar(
7776
}`}
7877
>
7978
<Settings class='h-4 w-4' />
80-
{!sb && 'Settings'}
79+
{sb && 'Settings'}
8180
</A>
8281
</div>
8382
</div>

0 commit comments

Comments
 (0)