Create new searchbar and UsersView requests
gitea/hedera-web/pipeline/pr-beta This commit looks good Details

This commit is contained in:
William Buezas 2025-03-18 14:32:00 -03:00
parent 47c61a7b35
commit c24480099f
3 changed files with 262 additions and 11 deletions

View File

@ -0,0 +1,120 @@
<script setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import VnInput from 'src/components/common/VnInput.vue';
import { fetch } from 'src/composables/serviceUtils';
const props = defineProps({
searchFn: {
type: Function,
default: null
},
placeholder: {
type: String,
default: ''
},
url: {
type: String,
default: null
},
filter: {
type: String,
default: null
},
searchField: {
type: String,
default: 'search'
},
exprBuilder: {
type: Function,
default: null
}
});
const emit = defineEmits(['onSearch', 'onSearchError']);
const { t } = useI18n();
const router = useRouter();
const route = useRoute();
const searchTerm = ref('');
const search = async () => {
try {
router.replace({
query: searchTerm.value ? { search: searchTerm.value } : {}
});
if (!searchTerm.value) {
emit('onSearchError');
return;
}
if (props.url) {
const params = {
filter: props.filter,
[props.searchField]: searchTerm.value
};
const { data } = await fetch({
url: props.url,
params,
exprBuilder: props.exprBuilder
});
emit('onSearch', data);
}
} catch (error) {
console.error('Error searching:', error);
emit('onSearchError');
}
};
onMounted(() => {
if (route.query.search) {
searchTerm.value = route.query.search;
search();
}
});
</script>
<template>
<VnInput
v-model="searchTerm"
@keyup.enter="search()"
:placeholder="props.placeholder || t('search')"
bg-color="white"
is-outlined
:clearable="false"
class="searchbar"
data-cy="searchBar"
>
<template #prepend>
<QIcon name="search" class="cursor-pointer" @click="search()" />
</template>
</VnInput>
</template>
<style lang="scss" scoped>
@import 'src/css/responsive';
.searchbar {
@include mobile {
max-width: 120px;
}
}
</style>
<i18n lang="yaml">
en-US:
search: Search
es-ES:
search: Buscar
ca-ES:
search: Cercar
fr-FR:
search: Rechercher
pt-PT:
search: Pesquisar
</i18n>

View File

@ -0,0 +1,116 @@
import { api } from '@/boot/axios';
function buildFilter(params, builderFunc) {
let and = [];
for (let param in params) {
let value = params[param];
if (value == null) continue;
let expr = builderFunc(param, value);
if (expr) and.push(expr);
}
return simplifyOperation(and, 'and');
}
const simplifyOperation = (operation, operator) => {
switch (operation.length) {
case 0:
return undefined;
case 1:
return operation[0];
default:
return { [operator]: operation };
}
};
async function fetch({
url,
append = false,
params,
exprBuilder,
mapKey,
existingData = [],
existingMap = new Map(),
oneRecord = false
}) {
if (!url) return;
let exprFilter;
if (exprBuilder) {
exprFilter = buildFilter(params, (param, value) => {
if (param === 'filter') return;
const res = exprBuilder(param, value);
if (res) delete params[param];
return res;
});
}
if (params.filter?.where || exprFilter) {
params.filter.where = { ...params.filter.where, ...exprFilter };
}
if (!params?.filter?.order?.length) {
delete params?.filter?.order;
}
params.filter = JSON.stringify(params.filter);
const response = await api.get(url, { params });
const processedData = processData(response.data, {
mapKey,
map: !!mapKey,
append,
oneRecord,
existingData,
existingMap
});
return { response, data: processedData.data, map: processedData.map };
}
function processData(data, options) {
const {
mapKey,
map = true,
append = true,
oneRecord = false,
existingData = [],
existingMap = new Map()
} = options;
let resultData = [...existingData];
let resultMap = new Map(existingMap);
if (oneRecord) {
return Array.isArray(data) ? data[0] : data;
}
if (!append) {
resultData = [];
resultMap = new Map();
}
if (!Array.isArray(data)) {
resultData = data;
} else if (!map && append) {
resultData.push(...data);
} else {
for (const row of data) {
const key = row[mapKey];
const val = { ...row, key };
if (key && resultMap.has(key)) {
const { position } = resultMap.get(key);
val.position = position;
resultMap.set(key, val);
resultData[position] = val;
} else {
val.position = resultMap.size;
resultMap.set(key, val);
resultData.push(val);
}
}
}
return { data: resultData, map: resultMap };
}
export { buildFilter, fetch, processData };

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import CardList from 'src/components/ui/CardList.vue';
import VnSearchBar from 'src/components/ui/VnSearchBar.vue';
import VnSearchBar from 'src/components/ui/NewVnSearchBar.vue';
import VnList from 'src/components/ui/VnList.vue';
import { useAppStore } from 'stores/app';
@ -16,16 +16,11 @@ const router = useRouter();
const userStore = useUserStore();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
const loading = ref(false);
const users = ref([]);
const query = `SELECT u.id, u.name, u.nickname, u.active
FROM account.user u
WHERE u.name LIKE CONCAT('%', #user, '%')
OR u.nickname LIKE CONCAT('%', #user, '%')
OR u.id = #user
ORDER BY u.name LIMIT 200`;
const filter = {
fields: ['id', 'name', 'nickname', 'active']
};
const onSearch = data => (users.value = data || []);
@ -38,15 +33,35 @@ const supplantUser = async user => {
console.error('Error supplanting user:', error);
}
};
const usersExprBuilder = (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? { id: value }
: {
or: [
{ name: { like: `%${value}%` } },
{ nickname: { like: `%${value}%` } }
]
};
case 'name':
case 'nickname':
return { [param]: { like: `%${value}%` } };
case 'roleFk':
return { [param]: value };
}
};
</script>
<template>
<Teleport v-if="isHeaderMounted" to="#actions">
<VnSearchBar
:sql-query="query"
search-field="user"
url="/VnUsers/preview"
@on-search="onSearch"
@on-search-error="users = []"
:expr-builder="usersExprBuilder"
:filter="filter"
data-cy="usersViewSearchBar"
/>
</Teleport>