feat(ui): sort search bar icon [EE-3663] (#7205)
parent
88c4a43a19
commit
ce840997bf
|
@ -193,7 +193,7 @@ input:checked + .slider:before {
|
|||
flex-wrap: nowarp;
|
||||
}
|
||||
|
||||
.toolBar > .searchBar {
|
||||
.toolBar .searchBar {
|
||||
flex: right;
|
||||
margin-right: 10px;
|
||||
width: 500px;
|
||||
|
|
|
@ -40,10 +40,16 @@ angular.module('portainer.app').controller('GenericDatatableController', [
|
|||
_.map(this.state.filteredDataSet, (item) => (item.Checked = false));
|
||||
};
|
||||
|
||||
this.onTextFilterChange = function () {
|
||||
DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
|
||||
this.onTextFilterChangeGeneric = onTextFilterChangeGeneric;
|
||||
|
||||
this.onTextFilterChange = function onTextFilterChange() {
|
||||
return this.onTextFilterChangeGeneric();
|
||||
};
|
||||
|
||||
function onTextFilterChangeGeneric() {
|
||||
DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
|
||||
}
|
||||
|
||||
this.changeOrderBy = function changeOrderBy(orderField) {
|
||||
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
|
||||
this.state.orderBy = orderField;
|
||||
|
|
|
@ -2,7 +2,6 @@ import angular from 'angular';
|
|||
import 'angular-utils-pagination';
|
||||
|
||||
import { datatableTitlebar } from './titlebar';
|
||||
import { datatableSearchbar } from './searchbar';
|
||||
import { datatableSortIcon } from './sort-icon';
|
||||
import { datatablePagination } from './pagination';
|
||||
import { datatableFilter } from './filter';
|
||||
|
@ -10,7 +9,6 @@ import { datatableFilter } from './filter';
|
|||
export default angular
|
||||
.module('portainer.shared.datatable', ['angularUtils.directives.dirPagination'])
|
||||
.component('datatableTitlebar', datatableTitlebar)
|
||||
.component('datatableSearchbar', datatableSearchbar)
|
||||
.component('datatableSortIcon', datatableSortIcon)
|
||||
.component('datatablePagination', datatablePagination)
|
||||
.component('datatableFilter', datatableFilter).name;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input type="text" class="searchInput" ng-model="$ctrl.filter" ng-change="$ctrl.onChange($ctrl.filter)" placeholder="Search..." ng-model-options="{ debounce: 300 }" />
|
||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||
export const datatableSearchbar = {
|
||||
bindings: {
|
||||
onChange: '<',
|
||||
ngModel: '<',
|
||||
},
|
||||
templateUrl: './datatable-searchbar.html',
|
||||
};
|
|
@ -3,19 +3,9 @@
|
|||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar">
|
||||
<div class="toolBarTitle"><i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px"></i> {{ $ctrl.titleText }} </div>
|
||||
<div class="searchBar">
|
||||
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
|
||||
<input
|
||||
type="text"
|
||||
class="searchInput"
|
||||
ng-model="$ctrl.state.textFilter"
|
||||
ng-change="$ctrl.onTextFilterChange()"
|
||||
placeholder="Search..."
|
||||
auto-focus
|
||||
ng-model-options="{ debounce: 300 }"
|
||||
data-cy="stack-searchInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<datatable-searchbar value="$ctrl.state.textFilter" placeholder="'Search...'" on-change="($ctrl.onTextFilterChange)" data-cy="stack-searchInput"></datatable-searchbar>
|
||||
|
||||
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -33,6 +33,13 @@ angular.module('portainer.app').controller('StacksDatatableController', [
|
|||
DatatableService.setColumnVisibilitySettings(this.tableKey, this.columnVisibility);
|
||||
}
|
||||
|
||||
this.onTextFilterChange = onTextFilterChange.bind(this);
|
||||
|
||||
function onTextFilterChange(value) {
|
||||
this.state.textFilter = value;
|
||||
this.onTextFilterChangeGeneric();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow external items
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,7 @@ import { PasswordCheckHint } from '@@/PasswordCheckHint';
|
|||
import { ViewLoading } from '@@/ViewLoading';
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
import { DashboardItem } from '@@/DashboardItem';
|
||||
import { SearchBar } from '@@/datatables/SearchBar';
|
||||
|
||||
import { fileUploadField } from './file-upload-field';
|
||||
import { switchField } from './switch-field';
|
||||
|
@ -43,4 +44,8 @@ export const componentsModule = angular
|
|||
.component(
|
||||
'dashboardItem',
|
||||
r2a(DashboardItem, ['featherIcon', 'icon', 'type', 'value', 'children'])
|
||||
)
|
||||
.component(
|
||||
'datatableSearchbar',
|
||||
r2a(SearchBar, ['data-cy', 'onChange', 'value', 'placeholder'])
|
||||
).name;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<datatable-titlebar title="Activity Logs" icon="fa-history" feature="{{::$ctrl.feature}}"></datatable-titlebar>
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)" ng-model="$ctrl.keyword"></datatable-searchbar>
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)" value="$ctrl.keyword"></datatable-searchbar>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
|
|
|
@ -3,8 +3,9 @@ import moment from 'moment';
|
|||
import { FeatureId } from '@/portainer/feature-flags/enums';
|
||||
export default class ActivityLogsViewController {
|
||||
/* @ngInject */
|
||||
constructor($async, Notifications) {
|
||||
constructor($async, $scope, Notifications) {
|
||||
this.$async = $async;
|
||||
this.$scope = $scope;
|
||||
this.Notifications = Notifications;
|
||||
|
||||
this.limitedFeature = FeatureId.ACTIVITY_AUDIT;
|
||||
|
@ -54,9 +55,11 @@ export default class ActivityLogsViewController {
|
|||
}
|
||||
|
||||
onChangeKeyword(keyword) {
|
||||
this.state.page = 1;
|
||||
this.state.keyword = keyword;
|
||||
this.loadLogs();
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.state.page = 1;
|
||||
this.state.keyword = keyword;
|
||||
this.loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
onChangeDate({ startDate, endDate }) {
|
||||
|
@ -65,16 +68,6 @@ export default class ActivityLogsViewController {
|
|||
this.loadLogs();
|
||||
}
|
||||
|
||||
async export() {
|
||||
return this.$async(async () => {
|
||||
try {
|
||||
await this.UserActivityService.saveLogsAsCSV(this.state.sort, this.state.keyword, this.state.date, this.state.contextFilter);
|
||||
} catch (err) {
|
||||
this.Notifications.error('Failure', err, 'Failed loading user activity logs csv');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadLogs() {
|
||||
return this.$async(async () => {
|
||||
this.state.logs = null;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<datatable-titlebar title="Authentication Events" icon="fa-history" feature="{{::$ctrl.feature}}"></datatable-titlebar>
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)" ng-model="$ctrl.keyword"></datatable-searchbar>
|
||||
<datatable-searchbar on-change="($ctrl.onChangeKeyword)" value="$ctrl.keyword"></datatable-searchbar>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap-cells">
|
||||
<thead>
|
||||
|
|
|
@ -68,9 +68,11 @@ export default class AuthLogsViewController {
|
|||
}
|
||||
|
||||
onChangeKeyword(keyword) {
|
||||
this.state.page = 1;
|
||||
this.state.keyword = keyword;
|
||||
this.loadLogs();
|
||||
return this.$scope.$evalAsync(() => {
|
||||
this.state.page = 1;
|
||||
this.state.keyword = keyword;
|
||||
this.loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
onChangeDate({ startDate, endDate }) {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { useLocalStorage } from '@/portainer/hooks/useLocalStorage';
|
||||
import { Search } from 'react-feather';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface Props {
|
||||
import { useLocalStorage } from '@/portainer/hooks/useLocalStorage';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
interface Props extends AutomationTestingProps {
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
onChange(value: string): void;
|
||||
|
@ -10,21 +15,45 @@ export function SearchBar({
|
|||
value,
|
||||
placeholder = 'Search...',
|
||||
onChange,
|
||||
'data-cy': dataCy,
|
||||
}: Props) {
|
||||
const [searchValue, setSearchValue] = useDebounce(value, onChange);
|
||||
|
||||
return (
|
||||
<div className="searchBar">
|
||||
<i className="fa fa-search searchIcon" aria-hidden="true" />
|
||||
<div className="searchBar items-center flex">
|
||||
<Search className="searchIcon feather" />
|
||||
<input
|
||||
type="text"
|
||||
className="searchInput"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
data-cy={dataCy}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useDebounce(defaultValue: string, onChange: (value: string) => void) {
|
||||
const [searchValue, setSearchValue] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchValue(defaultValue);
|
||||
}, [defaultValue]);
|
||||
|
||||
const onChangeDebounces = useMemo(
|
||||
() => _.debounce(onChange, 300),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return [searchValue, handleChange] as const;
|
||||
|
||||
function handleChange(value: string) {
|
||||
setSearchValue(value);
|
||||
onChangeDebounces(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function useSearchBarState(
|
||||
key: string
|
||||
): [string, (value: string) => void] {
|
||||
|
|
Loading…
Reference in New Issue