Skip to content

Commit fc4dee5

Browse files
committed
Better ticket listing summary/description search and columns selector
1 parent 21460f0 commit fc4dee5

6 files changed

Lines changed: 205 additions & 107 deletions

File tree

src/assets/css/dropdown.css

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111
&.open {
1212
@apply bg-white;
1313
}
14+
15+
&.dropdown-toggle-white {
16+
@apply bg-white;
17+
@apply text-gray-800;
18+
@apply border-gray-400;
19+
@apply hover:border-gray-400;
20+
}
1421
}
1522

1623
.dropdown {
@@ -23,6 +30,13 @@
2330
@apply mt-0;
2431
@apply border border-gray-200;
2532

33+
& > ul {
34+
@apply list-none;
35+
@apply p-0;
36+
@apply m-0;
37+
}
38+
39+
& > ul > li > label,
2640
& > .dropdown-item {
2741
@apply p-2;
2842
@apply text-gray-800;
@@ -34,9 +48,5 @@
3448
&:checked {
3549
@apply bg-gray-100;
3650
}
37-
38-
/* & > input {
39-
@apply hidden;
40-
} */
4151
}
4252
}

src/assets/css/ticket-listing.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,17 @@ table.ticket-listing {
317317
}
318318
}
319319
}
320+
321+
.ticket-listing-filter-bar {
322+
@apply flex flex-row items-center justify-between;
323+
@apply p-2 mb-2;
324+
@apply bg-gray-100;
325+
@apply rounded;
326+
327+
& input.search-filter {
328+
@apply w-full min-w-96;
329+
@apply p-2;
330+
@apply bg-white;
331+
@apply border border-solid border-gray-400 rounded;
332+
}
333+
}

src/assets/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ window.EasyMDE = EasyMDE
2929
// Alpinejs for simpler UI tasks
3030
import Alpine from "alpinejs"
3131

32+
import "./main/Dropdown"
3233
import "./main/PopoverConfirm"
3334
import "./main/TicketTasks"
3435
import "./main/TicketTemplates"

src/assets/main/Dropdown.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*!
2+
* Traq
3+
* Copyright (C) 2009-2025 Jack Polgar
4+
* Copyright (C) 2012-2025 Traq.io
5+
* https://github.com/nirix
6+
* http://traq.io
7+
*
8+
* This file is part of Traq.
9+
*
10+
* Traq is free software: you can redistribute it and/or modify
11+
* it under the terms of the GNU General Public License as published by
12+
* the Free Software Foundation; version 3 only.
13+
*
14+
* Traq is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU General Public License
20+
* along with Traq. If not, see <http://www.gnu.org/licenses/>.
21+
*/
22+
23+
import Alpine from 'alpinejs'
24+
25+
Alpine.data('dropdown', () => ({
26+
open: false,
27+
28+
init() {
29+
this.open = false
30+
31+
const button = this.$refs.dropdownToggle as HTMLButtonElement
32+
const dropdown = this.$refs.dropdownMenu as HTMLDivElement
33+
34+
// if the dropdown menu is going off the right side of the screen, move it to the left
35+
if (dropdown.offsetLeft + dropdown.offsetWidth > window.innerWidth) {
36+
// set the dropdown top right corner to the bottom right corner of the button
37+
// dropdown.style.top = `${button.offsetTop + button.offsetHeight}px`
38+
const rect = button.getBoundingClientRect()
39+
dropdown.style.left = `${rect.right - dropdown.offsetWidth}px`
40+
// dropdown.style.top = `${rect.top}px`
41+
}
42+
},
43+
44+
toggle() {
45+
this.open = !this.open
46+
},
47+
}))

src/assets/main/TicketList.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Alpine.data('ticketList', () => ({
6262
sortOrder: 'asc',
6363

6464
tickets: [],
65+
search: '',
6566
filters: [] as FilterInterface[],
6667
filterData: {
6768
milestones: [] as Array<{ label: string; value: string }>,
@@ -84,14 +85,14 @@ Alpine.data('ticketList', () => ({
8485
},
8586

8687
availableFilters: [
87-
{
88-
field: "summary",
89-
type: "contains",
90-
},
91-
{
92-
field: "description",
93-
type: "contains",
94-
},
88+
// {
89+
// field: "summary",
90+
// type: "contains",
91+
// },
92+
// {
93+
// field: "description",
94+
// type: "contains",
95+
// },
9596
{
9697
field: "type",
9798
type: "is",
@@ -232,6 +233,11 @@ Alpine.data('ticketList', () => ({
232233
this.updateUrl()
233234
this.fetchTickets()
234235
})
236+
237+
this.$watch('search', () => {
238+
this.updateUrl()
239+
this.fetchTickets()
240+
})
235241
},
236242

237243
convertQueryString() {
@@ -291,6 +297,10 @@ Alpine.data('ticketList', () => ({
291297
})
292298
}
293299

300+
if (this.search.length > 0) {
301+
params.set('q', this.search)
302+
}
303+
294304
if (this.page > 1) {
295305
params.set('page', this.page.toString())
296306
} else {

src/views/default/tickets/index.phtml

Lines changed: 111 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -119,107 +119,123 @@
119119
<template x-if="!filters.find(f => f.field === filter.field)">
120120
<option :value="filter.field" x-text="filter.label ?? language[filter.field]"></option>
121121
</template>
122-
</template>r
122+
</template>
123123
</select>
124124
<button class="btn-primary" @click="applyFilters()" type="button"><?= l('apply') ?></button>
125125
</div>
126126
</div>
127127
</div>
128128
</fieldset>
129129

130-
<fieldset class="ticket-columns-container" :class=" { 'open' : showColumnSettings }">
131-
<legend @click="showColumnSettings = !showColumnSettings"><?= l('columns') ?></legend>
130+
<div class="ticket-listing-filter-bar">
132131
<div>
133-
<ul>
134-
<li>
135-
<label>
136-
<input type="checkbox" x-model="columns.id">
137-
<?= l('id') ?>
138-
</label>
139-
</li>
140-
<li>
141-
<label>
142-
<input type="checkbox" x-model="columns.summary">
143-
<?= l('summary') ?>
144-
</label>
145-
</li>
146-
<li>
147-
<label>
148-
<input type="checkbox" x-model="columns.status">
149-
<?= l('status') ?>
150-
</label>
151-
</li>
152-
<li>
153-
<label>
154-
<input type="checkbox" x-model="columns.owner">
155-
<?= l('owner') ?>
156-
</label>
157-
</li>
158-
<li>
159-
<label>
160-
<input type="checkbox" x-model="columns.type">
161-
<?= l('type') ?>
162-
</label>
163-
</li>
164-
<li>
165-
<label>
166-
<input type="checkbox" x-model="columns.component">
167-
<?= l('component') ?>
168-
</label>
169-
</li>
170-
<li>
171-
<label>
172-
<input type="checkbox" x-model="columns.milestone">
173-
<?= l('milestone') ?>
174-
</label>
175-
</li>
176-
<li>
177-
<label>
178-
<input type="checkbox" x-model="columns.assignee">
179-
<?= l('assignee') ?>
180-
</label>
181-
</li>
182-
<li>
183-
<label>
184-
<input type="checkbox" x-model="columns.priority">
185-
<?= l('priority') ?>
186-
</label>
187-
</li>
188-
<li>
189-
<label>
190-
<input type="checkbox" x-model="columns.severity">
191-
<?= l('severity') ?>
192-
</label>
193-
</li>
194-
<li>
195-
<label>
196-
<input type="checkbox" x-model="columns.reported">
197-
<?= l('reported') ?>
198-
</label>
199-
</li>
200-
<li>
201-
<label>
202-
<input type="checkbox" x-model="columns.updated">
203-
<?= l('updated') ?>
204-
</label>
205-
</li>
206-
<li>
207-
<label>
208-
<input type="checkbox" x-model="columns.votes">
209-
<?= l('votes') ?>
210-
</label>
211-
</li>
212-
<?php foreach ($customFields as $field): ?>
213-
<li>
214-
<label>
215-
<input type="checkbox" x-model="columns.<?= $field->slug ?>">
216-
<?= $field->name ?>
217-
</label>
218-
</li>
219-
<?php endforeach; ?>
220-
</ul>
132+
<label for="search-filter" class="sr-only"><?= l('search') ?></label>
133+
<input type="text" placeholder="<?= l('search') ?>" x-model.trim.debounce.300ms="search" class="search-filter" />
221134
</div>
222-
</fieldset>
135+
136+
<div>
137+
<div x-data="dropdown()">
138+
<button @click="toggle()" class="dropdown-toggle dropdown-toggle-white" :class="{ open: open }" x-ref="dropdownToggle">
139+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
140+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 4.5v15m6-15v15m-10.875 0h15.75c.621 0 1.125-.504 1.125-1.125V5.625c0-.621-.504-1.125-1.125-1.125H4.125C3.504 4.5 3 5.004 3 5.625v12.75c0 .621.504 1.125 1.125 1.125Z" />
141+
</svg>
142+
<span><?= l('columns'); ?></span>
143+
<i class="fa-solid fa-chevron-down text-gray-400" x-show="!open"></i>
144+
<i class="fa-solid fa-chevron-up text-gray-400" x-show="open"></i>
145+
</button>
146+
<div x-show="open" class="dropdown" @click.outside="open = false" x-ref="dropdownMenu">
147+
<ul>
148+
<li>
149+
<label>
150+
<input type="checkbox" x-model="columns.id">
151+
<?= l('id') ?>
152+
</label>
153+
</li>
154+
<li>
155+
<label>
156+
<input type="checkbox" x-model="columns.summary">
157+
<?= l('summary') ?>
158+
</label>
159+
</li>
160+
<li>
161+
<label>
162+
<input type="checkbox" x-model="columns.status">
163+
<?= l('status') ?>
164+
</label>
165+
</li>
166+
<li>
167+
<label>
168+
<input type="checkbox" x-model="columns.owner">
169+
<?= l('owner') ?>
170+
</label>
171+
</li>
172+
<li>
173+
<label>
174+
<input type="checkbox" x-model="columns.type">
175+
<?= l('type') ?>
176+
</label>
177+
</li>
178+
<li>
179+
<label>
180+
<input type="checkbox" x-model="columns.component">
181+
<?= l('component') ?>
182+
</label>
183+
</li>
184+
<li>
185+
<label>
186+
<input type="checkbox" x-model="columns.milestone">
187+
<?= l('milestone') ?>
188+
</label>
189+
</li>
190+
<li>
191+
<label>
192+
<input type="checkbox" x-model="columns.assignee">
193+
<?= l('assignee') ?>
194+
</label>
195+
</li>
196+
<li>
197+
<label>
198+
<input type="checkbox" x-model="columns.priority">
199+
<?= l('priority') ?>
200+
</label>
201+
</li>
202+
<li>
203+
<label>
204+
<input type="checkbox" x-model="columns.severity">
205+
<?= l('severity') ?>
206+
</label>
207+
</li>
208+
<li>
209+
<label>
210+
<input type="checkbox" x-model="columns.reported">
211+
<?= l('reported') ?>
212+
</label>
213+
</li>
214+
<li>
215+
<label>
216+
<input type="checkbox" x-model="columns.updated">
217+
<?= l('updated') ?>
218+
</label>
219+
</li>
220+
<li>
221+
<label>
222+
<input type="checkbox" x-model="columns.votes">
223+
<?= l('votes') ?>
224+
</label>
225+
</li>
226+
<?php foreach ($customFields as $field): ?>
227+
<li>
228+
<label>
229+
<input type="checkbox" x-model="columns.<?= $field->slug ?>">
230+
<?= $field->name ?>
231+
</label>
232+
</li>
233+
<?php endforeach; ?>
234+
</ul>
235+
</div>
236+
</div>
237+
</div>
238+
</div>
223239

224240
<table class="ticket-listing" id="ticket-listing">
225241
<thead>
@@ -275,9 +291,9 @@
275291
<td x-text="ticket.status.name" x-show="columns.status"></td>
276292
<td x-text="ticket.owner.name" x-show="columns.owner"></td>
277293
<td x-text="ticket.type.name" x-show="columns.type"></td>
278-
<td x-text="ticket.component.name" x-show="columns.component"></td>
279-
<td x-text="ticket.milestone.name" x-show="columns.milestone"></td>
280-
<td x-text="ticket.assignee.name" x-show="columns.assignee"></td>
294+
<td x-text="ticket.component ? ticket.component.name : ''" x-show="columns.component"></td>
295+
<td x-text="ticket.milestone ? ticket.milestone.name : ''" x-show="columns.milestone"></td>
296+
<td x-text="ticket.assignee ? ticket.assignee.name : ''" x-show="columns.assignee"></td>
281297
<td x-text="ticket.priority.name" x-show="columns.priority"></td>
282298
<td x-text="ticket.severity.name" x-show="columns.severity"></td>
283299
<td x-text="ticket.created_at" x-show="columns.reported"></td>

0 commit comments

Comments
 (0)