diff --git a/assets/script.js b/assets/script.js index bdc498376c06a66018a05f92d9fcdcb16632c3b6..4168011dcafc3e831d5af22d46cf25a38729dfa9 100644 --- a/assets/script.js +++ b/assets/script.js @@ -73,6 +73,7 @@ createApp({ data () { return { + needle: '', issues, qmLabels, filters @@ -81,6 +82,13 @@ methods: { getStateForIssueAndQmLabel(issue, qm) { return issue.qm_states[qm]; + }, + valueMatchesNeedle(what) { + if (this.needle.length === 0) { + return false; + } + + return what.toLowerCase().includes(this.needle.toLowerCase()); } }, computed: { @@ -88,7 +96,7 @@ return 8 + Object.values(qmLabels).length; }, filteredIssues() { - return this.issues.filter(issue => { + let filtered = this.issues.filter(issue => { for (const [key, value] of Object.entries(this.filters)) { if (value === null) { continue; @@ -99,6 +107,19 @@ } return true; }); + + if (this.needle.length > 0) { + filtered = filtered.filter(issue => { + const ciNeedle = this.needle.toLowerCase(); + return issue.iid.toString().includes(this.needle) + || issue.title.toLowerCase().includes(ciNeedle) + || (issue.assignee ?? '').toLowerCase().includes(ciNeedle) + || (issue.author ?? '').toLowerCase().includes(ciNeedle) + || issue.reviewers.some(reviewer => reviewer.toLowerCase().includes(ciNeedle)); + }); + } + + return filtered; } }, watch: { diff --git a/assets/style.scss b/assets/style.scss index 965afa51078f2c97c9cd36a5df90a9ad86a0b055..98e4050b72993d4a71bc61f14de1d005e32b5232 100644 --- a/assets/style.scss +++ b/assets/style.scss @@ -23,3 +23,7 @@ padding: 0 0.5em; } } + +td.filter-match { + background-color: var(--yellow-20); +} diff --git a/views/dashboard/index.php b/views/dashboard/index.php index 2359f1d2a7770d9e95f1e4cdbfc1b24f1ea55d86..1e98d18f9738ba59d0fa66155bda80b1dad19717 100644 --- a/views/dashboard/index.php +++ b/views/dashboard/index.php @@ -48,7 +48,7 @@ $attributes = [ </td> </tr> <tr v-for="issue in filteredIssues"> - <td> + <td :class="{'filter-match': valueMatchesNeedle(issue.iid.toString())}"> <a :href="issue.web_url" target="_blank"> #{{ issue.iid }} </a> @@ -70,14 +70,18 @@ $attributes = [ <?= Icon::create('date', Icon::ROLE_STATUS_YELLOW) ?> </abbr> </td> - <td> + <td :class="{'filter-match': valueMatchesNeedle(issue.title)}"> <a :href="issue.web_url" target="_blank"> {{ issue.title }} </a> </td> - <td>{{ issue.author }}</td> - <td>{{ issue.assignee }}</td> - <td> + <td :class="{'filter-match': valueMatchesNeedle(issue.author)}"> + {{ issue.author }} + </td> + <td :class="{'filter-match': valueMatchesNeedle(issue.assignee)}"> + {{ issue.assignee }} + </td> + <td :class="{'filter-match': valueMatchesNeedle(issue.reviewers.join('||'))}"> <ul v-if="issue.reviewers.length > 0" class="list-csv"> <li v-for="username in issue.reviewers" :key="`reviewer-${issue.iid}-${username}`">{{ username }}</li> </ul> @@ -92,8 +96,26 @@ $attributes = [ </table> <mounting-portal mount-to="#layout-sidebar .sidebar" name="sidebar-filter" append> + <div class="sidebar-widget sidebar-search"> + <div class="sidebar-widget-header"><?= _('Suche') ?></div> + <div class="sidebar-widget-content"> + <form action="#" @submit="event => event.preventDefault()"> + <ul class="needles"> + <li> + <div class="input-group files-search"> + <input type="text" v-model.trim="needle" placeholder="<?= _('Suchwort') ?>"> + <button class="submit-search" type="submit" title="<?= _('Suche auführen') ?>"> + <?= Icon::create('search')->asImg(20) ?> + </button> + </div> + </li> + </ul> + </form> + </div> + </div> + <div class="sidebar-widget"> - <div class="sidebar-widget-header">Filter</div> + <div class="sidebar-widget-header"><?= _('Filter') ?></div> <div class="sidebar-widget-content"> <label v-for="(abbr, label) in qmLabels" style="display: block;"> {{ label }}