Skip to content

Commit 56e867c

Browse files
authored
Merge pull request #642 from MerginMaps/develop
2026.5.0 release
2 parents ead4fbf + e0968d5 commit 56e867c

15 files changed

Lines changed: 423 additions & 302 deletions

File tree

web-app/packages/admin-lib/src/modules/admin/adminApi.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ export const AdminApi = {
2828
},
2929

3030
async fetchUsers(
31-
params: PaginatedUsersParams
31+
params: PaginatedUsersParams,
32+
signal?: AbortSignal
3233
): Promise<AxiosResponse<UsersResponse>> {
33-
return AdminModule.httpService.get(`/app/admin/users`, { params })
34+
return AdminModule.httpService.get(`/app/admin/users`, { params, signal })
3435
},
3536

3637
async fetchUserByName(
@@ -73,9 +74,10 @@ export const AdminApi = {
7374
},
7475

7576
async getProjects(
76-
params: PaginatedAdminProjectsParams
77+
params: PaginatedAdminProjectsParams,
78+
signal?: AbortSignal
7779
): Promise<AxiosResponse<PaginatedAdminProjectsResponse>> {
78-
return AdminModule.httpService.get('/app/admin/projects', { params })
80+
return AdminModule.httpService.get('/app/admin/projects', { params, signal })
7981
},
8082

8183
/**

web-app/packages/admin-lib/src/modules/admin/components/AccountsTable.vue

Lines changed: 63 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<PInputText
2525
placeholder="Search accounts"
2626
data-cy="search-members-field"
27-
v-model="searchByName"
27+
v-model="search"
2828
class="w-full"
2929
@input="onSearch"
3030
/>
@@ -44,48 +44,34 @@
4444
:first="(options.page - 1) * options.itemsPerPage"
4545
:sort-field="options.sortBy[0]"
4646
:sort-order="options.sortDesc[0] ? -1 : 1"
47+
:rowHover="true"
4748
removableSort
4849
reorderable-columns
4950
@page="onPage"
50-
@row-click="rowClick"
5151
@sort="onSort"
5252
data-cy="accounts-table"
5353
>
5454
<template v-for="header in headers" :key="header.field">
5555
<PColumn
56-
v-if="header.field === 'username'"
5756
:field="header.field"
5857
:header="header.header"
5958
:sortable="header.sortable"
6059
>
61-
<template #body="slotProps">
60+
<template #body="{ data }">
6261
<router-link
63-
class="title-t4"
64-
:to="{
65-
name: 'account',
66-
params: { username: slotProps.data.username }
67-
}"
62+
:to="accountRoute(data)"
63+
class="dt-row-link"
64+
:class="header.class"
6865
>
69-
{{ slotProps.data.username }}
66+
<template v-if="header.field === 'active'">{{
67+
fieldValue(data, header.field) ? 'Active' : 'Inactive'
68+
}}</template>
69+
<template v-else>{{
70+
fieldValue(data, header.field)
71+
}}</template>
7072
</router-link>
7173
</template>
7274
</PColumn>
73-
<PColumn
74-
v-else-if="header.field === 'active'"
75-
:header="header.header"
76-
:field="header.field"
77-
>
78-
<template #body="slotProps">
79-
<i v-if="slotProps.data.active" class="ti ti-check" />
80-
<i v-else class="ti ti-x" />
81-
</template>
82-
</PColumn>
83-
<PColumn
84-
v-else
85-
:field="header.field"
86-
:header="header.header"
87-
:sortable="header.sortable"
88-
></PColumn>
8975
</template>
9076
<template #paginatorstart>
9177
<PButton
@@ -106,112 +92,90 @@
10692
<script lang="ts">
10793
import {
10894
PaginatedUsersParams,
109-
useDialogStore,
11095
TableDataHeader,
96+
useDataTableSearch,
97+
useDialogStore,
11198
AppContainer,
11299
AppSection
113100
} from '@mergin/lib'
114-
import debounce from 'lodash/debounce'
115-
import { mapActions, mapState } from 'pinia'
116-
import {
117-
DataTablePageEvent,
118-
DataTableRowClickEvent,
119-
DataTableSortEvent
120-
} from 'primevue/datatable'
101+
import get from 'lodash/get'
102+
import { mapState } from 'pinia'
121103
import { defineComponent } from 'vue'
122104
123105
import { AdminRoutes } from '@/modules'
124106
import CreateUserForm from '@/modules/admin/components/CreateUserForm.vue'
125107
import { useAdminStore } from '@/modules/admin/store'
126108
109+
const headers: TableDataHeader[] = [
110+
{
111+
field: 'username',
112+
header: 'Username',
113+
sortable: true,
114+
linked: true,
115+
class: 'title-t4'
116+
},
117+
{ field: 'email', header: 'Email', sortable: true, linked: true },
118+
{ field: 'profile.name', header: 'Full name', linked: true },
119+
{ field: 'active', header: 'Active', linked: true }
120+
]
121+
127122
export default defineComponent({
128123
name: 'AccountsTable',
129124
components: {
130125
AppContainer,
131126
AppSection
132127
},
133-
data() {
128+
setup() {
129+
const adminStore = useAdminStore()
130+
const dialogStore = useDialogStore()
131+
132+
const tableSearch = useDataTableSearch({
133+
defaultSortBy: 'username',
134+
defaultSortDesc: false
135+
})
136+
137+
tableSearch.setFetchFn((signal) => {
138+
const { options, search } = tableSearch
139+
const params: PaginatedUsersParams = {
140+
page: options.page,
141+
per_page: options.itemsPerPage
142+
}
143+
if (options.sortBy[0]) {
144+
params.descending = options.sortDesc[0]
145+
params.order_by = options.sortBy[0]
146+
}
147+
if (search.value) params.like = search.value.trim()
148+
adminStore.fetchUsers({ params, signal })
149+
})
150+
134151
return {
135-
options: {
136-
sortBy: ['username'],
137-
sortDesc: [false],
138-
itemsPerPage: 20,
139-
page: 1,
140-
perPageOptions: [20, 50, 100]
141-
},
142-
searchByName: '',
143-
headers: [
144-
{ field: 'username', header: 'Username', sortable: true },
145-
{ field: 'email', header: 'Email', sortable: true },
146-
{ field: 'profile.name', header: 'Full name' },
147-
{ field: 'active', header: 'Active' }
148-
] as TableDataHeader[]
152+
...tableSearch,
153+
show: dialogStore.show.bind(dialogStore),
154+
headers
149155
}
150156
},
151157
computed: {
152158
...mapState(useAdminStore, ['users', 'loading'])
153159
},
154160
created() {
155-
this.resetPaging = debounce(this.resetPaging, 1000)
156-
this.fetchUsers({ params: this.getParams() })
161+
this.initFromQuery()
162+
this.doFetch()
157163
},
158164
methods: {
159-
...mapActions(useAdminStore, ['fetchUsers']),
160-
...mapActions(useDialogStore, ['show']),
161-
162-
onSearch() {
163-
this.resetPaging()
164-
this.fetchUsers({ params: this.getParams() })
165+
fieldValue(data: unknown, field: string) {
166+
return get(data, field)
165167
},
166168
167-
async resetPaging() {
168-
this.options.page = 1
169-
},
170-
171-
getParams(): PaginatedUsersParams {
172-
const params = {
173-
page: this.options.page,
174-
per_page: this.options.itemsPerPage
175-
} as PaginatedUsersParams
176-
if (this.options.sortBy[0]) {
177-
params.descending = this.options.sortDesc[0]
178-
params.order_by = this.options.sortBy[0]
179-
}
180-
if (this.searchByName) {
181-
params.like = this.searchByName.trim()
182-
}
183-
return params
184-
},
185-
186-
onRefresh() {
187-
this.fetchUsers({ params: this.getParams() })
188-
},
189-
190-
onPage(event: DataTablePageEvent) {
191-
this.options.page = event.page + 1
192-
this.options.itemsPerPage = event.rows
193-
this.fetchUsers({ params: this.getParams() })
194-
},
195-
196-
onSort(event: DataTableSortEvent) {
197-
this.options.sortBy[0] = event.sortField?.toString()
198-
this.options.sortDesc[0] = event.sortOrder < 1
199-
this.fetchUsers({ params: this.getParams() })
200-
},
201-
202-
rowClick(event: DataTableRowClickEvent) {
203-
this.$router.push({
204-
name: AdminRoutes.ACCOUNT,
205-
params: { username: event.data.username }
206-
})
169+
accountRoute(data) {
170+
return { name: AdminRoutes.ACCOUNT, params: { username: data.username } }
207171
},
208172
209173
createUserDialog() {
210174
const dialog = { maxWidth: 500, header: 'Create user' }
211175
const listeners = {
212176
success: () => {
213-
this.resetPaging()
214-
this.fetchUsers({ params: this.getParams() })
177+
this.options.page = 1
178+
this.doFetch()
215179
}
216180
}
217181
this.show({

0 commit comments

Comments
 (0)