refactor: use useSearch in views

This commit is contained in:
axolotle 2024-07-09 17:07:39 +02:00
parent 501bce484a
commit 8bb2451e9c
7 changed files with 122 additions and 155 deletions

View file

@ -7,8 +7,10 @@ import CardDeckFeed from '@/components/CardDeckFeed.vue'
import { useForm } from '@/composables/form'
import { useAutoModal } from '@/composables/useAutoModal'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import { randint } from '@/helpers/commons'
import { appRepoUrl, required } from '@/helpers/validators'
import type { Obj } from '@/types/commons'
import type { FieldProps, FormFieldDict } from '@/types/form'
const props = withDefaults(
@ -35,10 +37,42 @@ const { loading } = useInitialQueries(
{ onQueriesResponse },
)
const apps = ref()
const selectedApp = ref()
const antifeatures = ref()
const apps = ref<Obj[] | undefined>()
const [search, filteredApps] = useSearch(
apps,
(s, app) => {
// app doesn't match quality filter
if (props.quality !== 'all' && !app[props.quality]) return false
// app doesn't match category filter
if (props.category !== 'all' && app.category !== props.category)
return false
if (props.subtag !== 'all') {
const appMatchSubtag =
props.subtag === 'others'
? app.subtags.length === 0
: app.subtags.includes(props.subtag)
// app doesn't match subtag filter
if (!appMatchSubtag) return false
}
if (s === '') return true
if (app.searchValues.includes(search)) return true
return false
},
{
externalSearch: () => props.search,
filterIfNoSearch: true,
filterAllFn(s) {
if (props.category === null) return false
if (props.quality === 'all' && props.category === 'all' && s === '') {
return true
}
},
},
)
const form = ref({ url: '' })
const fields = {
url: {
@ -68,34 +102,6 @@ const categories = reactive([
// The rest is filled from api data
])
const filteredApps = computed(() => {
if (!apps.value || props.category === null) return
const search = props.search.toLowerCase()
if (props.quality === 'all' && props.category === 'all' && search === '') {
return apps.value
}
const filtered = apps.value.filter((app) => {
// app doesn't match quality filter
if (props.quality !== 'all' && !app[props.quality]) return false
// app doesn't match category filter
if (props.category !== 'all' && app.category !== props.category)
return false
if (props.subtag !== 'all') {
const appMatchSubtag =
props.subtag === 'others'
? app.subtags.length === 0
: app.subtags.includes(props.subtag)
// app doesn't match subtag filter
if (!appMatchSubtag) return false
}
if (search === '') return true
if (app.searchValues.includes(search)) return true
return false
})
return filtered.length ? filtered : null
})
const subtags = computed(() => {
// build an options array for subtags v-model/options
if (props.category && categories.length > 2) {
@ -196,8 +202,8 @@ const onCustomInstallClick = onSubmit(async () => {
<template>
<ViewSearch
:filtered-items="filteredApps"
:items="apps"
v-model="search"
:items="filteredApps"
items-name="apps"
:loading="loading"
>
@ -211,15 +217,15 @@ const onCustomInstallClick = onSubmit(async () => {
<BFormInput
id="search-input"
:model-value="search"
:placeholder="$t('search.for', { items: $t('items.apps', 2) })"
:modelValue="search"
@update:modelValue="updateQuery('search', $event)"
@update:model-value="updateQuery('search', $event)"
/>
<BFormSelect
:modelValue="quality"
:model-value="quality"
:options="qualityOptions"
@update:modelValue="updateQuery('quality', $event)"
@update:model-value="updateQuery('quality', $event)"
/>
</BInputGroup>
@ -230,9 +236,9 @@ const onCustomInstallClick = onSubmit(async () => {
</BInputGroupText>
<BFormSelect
:modelValue="category"
:model-value="category"
:options="categories"
@update:modelValue="updateQuery('category', $event)"
@update:model-value="updateQuery('category', $event)"
/>
<BButton
@ -260,9 +266,9 @@ const onCustomInstallClick = onSubmit(async () => {
<BFormSelect
id="subtags-select"
:modelValue="subtag"
:model-value="subtag"
:options="subtags"
@update:modelValue="updateQuery('subtag', $event)"
@update:model-value="updateQuery('subtag', $event)"
/>
</BInputGroup>
</div>

View file

@ -1,29 +1,24 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import type { Obj } from '@/types/commons'
const { loading } = useInitialQueries([['GET', 'apps?full']], {
onQueriesResponse,
})
const search = ref('')
const apps = ref()
const filteredApps = computed(() => {
if (!apps.value) return
const search_ = search.value.toLowerCase()
// Check if any value in apps (label, id, name, description) match the search query.
const filtered = apps.value.filter((app) =>
Object.values(app).some(
(item) => item && item.toLowerCase().includes(search_),
),
const apps = ref<Obj[] | undefined>()
const [search, filteredApps] = useSearch(apps, (s, app) => {
return Object.values(app).some(
(value) => value && value.toLowerCase().includes(s),
)
return filtered.length ? filtered : null
})
function onQueriesResponse(data: any) {
if (data.apps.length === 0) {
apps.value = null
apps.value = undefined
return
}
@ -39,10 +34,9 @@ function onQueriesResponse(data: any) {
<template>
<ViewSearch
v-model:search="search"
:filtered-items="filteredApps"
v-model="search"
items-name="installed_apps"
:items="apps"
:items="filteredApps"
:loading="loading"
>
<template #top-bar-buttons>

View file

@ -1,41 +1,29 @@
<script setup lang="ts">
import { useStoreGetters } from '@/store/utils'
import { computed, ref } from 'vue'
import RecursiveListGroup from '@/components/RecursiveListGroup.vue'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import type { TreeRootNode } from '@/helpers/data/tree'
import type { ComputedRef } from 'vue'
const { domains, mainDomain, domainsTree } = useStoreGetters()
const { mainDomain, domainsTree } = useStoreGetters()
const { loading } = useInitialQueries([
['GET', { uri: 'domains', storeKey: 'domains' }],
])
const search = ref('')
const tree = computed(() => {
const [search, filteredTree] = useSearch(
// FIXME rm ts type when moved to pinia or else
const tree = domainsTree.value as TreeRootNode | undefined
if (!tree) return
const search_ = search.value.toLowerCase()
if (search_) {
return tree.filter((node) => node.id.includes(search_))
}
return tree
})
const hasFilteredItems = computed(() => {
if (!tree.value) return null
return tree.value.children.length ? tree.value.children : null
})
domainsTree as ComputedRef<TreeRootNode | undefined>,
(s, node) => node.id.includes(s),
)
</script>
<template>
<ViewSearch
id="domain-list"
v-model:search="search"
:filtered-items="hasFilteredItems"
:items="domains"
v-model="search"
:items="filteredTree ? filteredTree.children : filteredTree"
items-name="domains"
:loading="loading"
>
@ -47,8 +35,8 @@ const hasFilteredItems = computed(() => {
</template>
<RecursiveListGroup
v-if="tree"
:tree="tree"
v-if="filteredTree"
:tree="filteredTree"
:toggle-text="$t('domain.toggle_subdomains')"
class="mb-5"
>
@ -67,9 +55,9 @@ const hasFilteredItems = computed(() => {
<small
v-if="data.name === mainDomain"
v-b-tooltip.hover
:title="$t('domain.types.main_domain')"
class="ms-1"
v-b-tooltip.hover
>
<YIcon iname="star" />
</small>

View file

@ -1,12 +1,14 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import api from '@/api'
import TagsSelectizeItem from '@/components/globals/formItems/TagsSelectizeItem.vue'
import { useAutoModal } from '@/composables/useAutoModal'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import { isEmptyValue } from '@/helpers/commons'
import type { Obj } from '@/types/commons'
// TODO add global search with type (search by: group, user, permission)
// TODO add vuex store update on inputs ?
@ -28,25 +30,15 @@ const { loading } = useInitialQueries(
{ onQueriesResponse },
)
const search = ref('')
const permissions = ref()
const permissionsOptions = ref()
const primaryGroups = ref()
const primaryGroups = ref<Obj[] | undefined>()
const userGroups = ref()
const usersOptions = ref()
const activeUserGroups = ref()
const filteredGroups = computed(() => {
const groups = primaryGroups.value
if (!groups) return
const search_ = search.value.toLowerCase()
const filtered = {}
for (const groupName in groups) {
if (groupName.toLowerCase().includes(search_)) {
filtered[groupName] = groups[groupName]
}
}
return isEmptyValue(filtered) ? null : filtered
const [search, filteredGroups] = useSearch(primaryGroups, (s, group) => {
return group.name.toLowerCase().includes(s)
})
function onQueriesResponse(users: any, allGroups: any, permsDict: any) {
@ -57,12 +49,12 @@ function onQueriesResponse(users: any, allGroups: any, permsDict: any) {
...value,
}))
const userNames = users ? Object.keys(users) : []
const primaryGroups_ = {}
const primaryGroups_ = []
const userGroups_ = {}
for (const groupName in allGroups) {
// copy the group to unlink it from the store
const group_ = { ...allGroups[groupName] }
const group_ = { ...allGroups[groupName], name: groupName }
group_.permissions = group_.permissions.map((perm) => {
return permsDict[perm].label
})
@ -103,7 +95,7 @@ function onQueriesResponse(users: any, allGroups: any, permsDict: any) {
.map(({ id }) => permsDict[id].label)
}
primaryGroups_[groupName] = group_
primaryGroups_.push(group_)
}
const activeUserGroups_ = Object.entries(userGroups_)
@ -179,16 +171,17 @@ async function deleteGroup(groupName) {
{ key: 'groups.delete', name: groupName },
)
.then(() => {
delete primaryGroups.value[groupName]
primaryGroups.value = primaryGroups.value?.filter(
(group) => group.name !== groupName,
)
})
}
</script>
<template>
<ViewSearch
v-model:search="search"
:filtered-items="filteredGroups"
:items="primaryGroups"
v-model="search"
:items="filteredGroups"
items-name="groups"
:loading="loading"
skeleton="CardFormSkeleton"
@ -201,13 +194,13 @@ async function deleteGroup(groupName) {
<!-- PRIMARY GROUPS CARDS -->
<YCard
v-for="(group, groupName) in filteredGroups"
:key="groupName"
v-for="group in filteredGroups"
:key="group.name"
collapsable
:title="
group.isSpecial
? $t('group_' + groupName)
: `${$t('group')} '${groupName}'`
? $t('group_' + group.name)
: `${$t('group')} '${group.name}'`
"
icon="group"
>
@ -215,7 +208,7 @@ async function deleteGroup(groupName) {
<!-- DELETE GROUP -->
<BButton
v-if="!group.isSpecial"
@click="deleteGroup(groupName)"
@click="deleteGroup(group.name)"
size="sm"
variant="danger"
>
@ -231,23 +224,23 @@ async function deleteGroup(groupName) {
<template v-if="group.isSpecial">
<p class="text-primary-emphasis">
<YIcon iname="info-circle" />
{{ $t('group_explain_' + groupName) }}
{{ $t('group_explain_' + group.name) }}
</p>
<p class="text-primary-emphasis" v-if="groupName === 'visitors'">
<p class="text-primary-emphasis" v-if="group.name === 'visitors'">
<em>{{
$t('group_explain_visitors_needed_for_external_client')
}}</em>
</p>
</template>
<template v-if="groupName == 'admins' || !group.isSpecial">
<template v-if="group.name == 'admins' || !group.isSpecial">
<TagsSelectizeItem
v-model="group.members"
:options="usersOptions"
:id="groupName + '-users'"
:id="group.name + '-users'"
:label="$t('group_add_member')"
tag-icon="user"
items-name="users"
@tag-update="onUserChanged({ ...$event, groupName })"
@tag-update="onUserChanged({ ...$event, groupName: group.name })"
/>
</template>
</BCol>
@ -262,11 +255,13 @@ async function deleteGroup(groupName) {
<TagsSelectizeItem
v-model="group.permissions"
:options="permissionsOptions"
:id="groupName + '-perms'"
:id="group.name + '-perms'"
:label="$t('group_add_permission')"
tag-icon="key-modern"
items-name="permissions"
@tag-update="onPermissionChanged({ ...$event, groupName })"
@tag-update="
onPermissionChanged({ ...$event, groupName: group.name })
"
:disabled-items="group.disabledItems"
/>
</BCol>

View file

@ -1,21 +1,18 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import { distanceToNow } from '@/helpers/filters/date'
import type { Obj } from '@/types/commons'
const { loading } = useInitialQueries([['GET', 'services']], {
onQueriesResponse,
})
const search = ref('')
const services = ref()
const filteredServices = computed(() => {
if (!services.value) return
const services_ = services.value.filter(({ name }) => {
return name.toLowerCase().includes(search.value.toLowerCase())
})
return services_.length ? services_ : null
const services = ref<Obj[] | undefined>()
const [search, filteredServices] = useSearch(services, (s, service) => {
return service.name.toLowerCase().includes(s)
})
function onQueriesResponse(services_: any) {
@ -34,9 +31,8 @@ function onQueriesResponse(services_: any) {
<template>
<ViewSearch
id="service-list"
v-model:search="search"
:filtered-items="filteredServices"
:items="services"
v-model="search"
:items="filteredServices"
items-name="services"
:loading="loading"
>

View file

@ -1,24 +1,19 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import { distanceToNow, readableDate } from '@/helpers/filters/date'
import type { Obj } from '@/types/commons'
const { loading } = useInitialQueries(
[['GET', `logs?limit=${25}&with_details`]],
{ onQueriesResponse },
)
const search = ref('')
const operations = ref()
const filteredOperations = computed(() => {
if (!operations.value) return
const search_ = search.value.toLowerCase()
const operations_ = operations.value.filter(({ description }) => {
return description.toLowerCase().includes(search_)
})
return operations_.length ? operations_ : null
const operations = ref<Obj[] | undefined>()
const [search, filteredOperations] = useSearch(operations, (s, op) => {
return op.description.toLowerCase().includes(s)
})
function onQueriesResponse({ operation }: any) {
@ -40,9 +35,8 @@ function onQueriesResponse({ operation }: any) {
<template>
<ViewSearch
v-model:search="search"
:filtered-items="filteredOperations"
:items="operations"
v-model="search"
:items="filteredOperations"
items-name="logs"
:loading="loading"
skeleton="CardListSkeleton"

View file

@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { type ComputedRef } from 'vue'
import { useStore } from 'vuex'
import { useInitialQueries } from '@/composables/useInitialQueries'
import { useSearch } from '@/composables/useSearch'
import { useStoreGetters } from '@/store/utils'
import type { Obj } from '@/types/commons'
const store = useStore()
const { loading } = useInitialQueries([
@ -15,20 +17,13 @@ const { loading } = useInitialQueries([
},
],
])
const { users } = useStoreGetters()
const search = ref('')
const filteredUsers = computed(() => {
if (!users.value) return
const search_ = search.value.toLowerCase()
const filtered = users.value.filter((user) => {
return (
user.username.toLowerCase().includes(search_) ||
user.groups.includes(search_)
)
})
return filtered.length === 0 ? null : filtered
})
const { users } = useStoreGetters()
const [search, filteredUsers] = useSearch(
users as ComputedRef<Obj[] | undefined>,
(s, user) =>
user.username.toLowerCase().includes(s) || user.groups.includes(s),
)
function downloadExport() {
const host = store.getters.host
@ -38,9 +33,8 @@ function downloadExport() {
<template>
<ViewSearch
v-model:search="search"
:filtered-items="filteredUsers"
:items="users"
v-model="search"
:items="filteredUsers"
items-name="users"
:loading="loading"
>