11<script setup lang="ts">
2- import { ref , onMounted , computed } from ' vue'
2+ import { ref , onMounted } from ' vue'
33import { useI18n } from ' vue-i18n'
44import { useRouter } from ' vue-router'
55import { useBreadcrumbs } from ' @/composables/useBreadcrumbs'
6- import { Input } from ' @/components/ui/input '
6+ import { Search } from ' lucide-vue-next '
77import { DsPageHeading } from ' @/components/ui/ds-page-heading'
88import NavbarLayout from ' @/components/NavbarLayout.vue'
9- import { getEnv } from ' @/utils/env '
9+ import { TeamService } from ' @/services/teamService '
1010import TeamTableColumns from ' ./TeamTableColumns.vue'
11- import type { Team , TeamsApiResponse } from ' ./types'
11+ import PaginationControls from ' @/components/ui/pagination/PaginationControls.vue'
12+ import { Skeleton } from ' @/components/ui/skeleton'
13+ import {
14+ Table ,
15+ TableBody ,
16+ TableCell ,
17+ TableHead ,
18+ TableHeader ,
19+ TableRow ,
20+ } from ' @/components/ui/table'
21+ import type { Team , PaginationMeta } from ' ./types'
1222
1323const { t } = useI18n ()
1424const router = useRouter ()
@@ -19,63 +29,110 @@ const isLoading = ref(true)
1929const error = ref <string | null >(null )
2030const searchQuery = ref (' ' )
2131
22- const apiUrl = getEnv (' VITE_DEPLOYSTACK_BACKEND_URL' ) || ' '
23-
24- // Filter teams based on search query
25- const filteredTeams = computed (() => {
26- if (! searchQuery .value ) {
27- return teams .value
28- }
29- const query = searchQuery .value .toLowerCase ()
30- return teams .value .filter (team => {
31- return team .name .toLowerCase ().includes (query ) ||
32- team .slug .toLowerCase ().includes (query ) ||
33- (team .description && team .description .toLowerCase ().includes (query ))
34- })
32+ // Pagination state
33+ const currentPage = ref (1 )
34+ const pageSize = ref (20 )
35+ const totalItems = ref (0 )
36+ const pagination = ref <PaginationMeta >({
37+ total: 0 ,
38+ limit: 20 ,
39+ offset: 0 ,
40+ has_more: false
3541})
3642
3743// Navigation function for viewing team details
3844const handleViewTeam = (teamId : string ) => {
3945 router .push (` /admin/teams/${teamId } ` )
4046}
4147
42- // Fetch teams from API
43- async function fetchTeams(): Promise <Team []> {
44- if (! apiUrl ) {
45- throw new Error (' VITE_DEPLOYSTACK_BACKEND_URL is not configured.' )
46- }
48+ // Check if text search is active
49+ const hasTextSearch = () => {
50+ return !! searchQuery .value && searchQuery .value .trim ().length > 0
51+ }
4752
48- const response = await fetch (` ${apiUrl }/api/admin/teams ` , {
49- credentials: ' include'
50- })
53+ // Search via backend API
54+ const searchTeams = async (): Promise <void > => {
55+ try {
56+ isLoading .value = true
57+ error .value = null
58+ const offset = (currentPage .value - 1 ) * pageSize .value
5159
52- if (! response .ok ) {
53- const errorData = await response .json ().catch (() => ({}))
54- throw new Error (errorData .error || ` Failed to fetch teams: ${response .statusText } (status: ${response .status }) ` )
55- }
60+ const response = await TeamService .searchTeamsAdmin ({
61+ name: searchQuery .value .trim (),
62+ limit: pageSize .value ,
63+ offset
64+ })
5665
57- const result: TeamsApiResponse = await response .json ()
58- if (! result .success || ! Array .isArray (result .data )) {
59- throw new Error (' API response for teams was not successful or data format is incorrect.' )
66+ teams .value = response .teams
67+ pagination .value = response .pagination
68+ totalItems .value = response .pagination .total
69+ } catch (err ) {
70+ error .value = err instanceof Error ? err .message : ' An unknown error occurred'
71+ teams .value = []
72+ totalItems .value = 0
73+ } finally {
74+ isLoading .value = false
6075 }
61-
62- return result .data
6376}
6477
65- // Load teams on component mount
66- onMounted (async () => {
67- setBreadcrumbs ([{ label: t (' adminTeams.title' ) }])
68-
78+ // Fetch all teams with pagination
79+ const fetchTeams = async (): Promise <void > => {
6980 try {
7081 isLoading .value = true
71- teams .value = await fetchTeams ()
7282 error .value = null
83+ const offset = (currentPage .value - 1 ) * pageSize .value
84+
85+ const response = await TeamService .getTeamsAdminPaginated ({
86+ limit: pageSize .value ,
87+ offset
88+ })
89+
90+ teams .value = response .teams
91+ pagination .value = response .pagination
92+ totalItems .value = response .pagination .total
7393 } catch (err ) {
7494 error .value = err instanceof Error ? err .message : ' An unknown error occurred'
7595 teams .value = []
96+ totalItems .value = 0
7697 } finally {
7798 isLoading .value = false
7899 }
100+ }
101+
102+ // Execute search or fetch
103+ const executeSearch = async () => {
104+ currentPage .value = 1
105+ if (hasTextSearch ()) {
106+ await searchTeams ()
107+ } else {
108+ await fetchTeams ()
109+ }
110+ }
111+
112+ // Pagination handlers
113+ const handlePageChange = async (page : number ) => {
114+ currentPage .value = page
115+ if (hasTextSearch ()) {
116+ await searchTeams ()
117+ } else {
118+ await fetchTeams ()
119+ }
120+ }
121+
122+ const handlePageSizeChange = async (newPageSize : number ) => {
123+ pageSize .value = newPageSize
124+ currentPage .value = 1
125+ if (hasTextSearch ()) {
126+ await searchTeams ()
127+ } else {
128+ await fetchTeams ()
129+ }
130+ }
131+
132+ // Load teams on component mount
133+ onMounted (async () => {
134+ setBreadcrumbs ([{ label: t (' adminTeams.title' ) }])
135+ await fetchTeams ()
79136})
80137 </script >
81138
@@ -84,32 +141,70 @@ onMounted(async () => {
84141 <DsPageHeading :title =" t('adminTeams.title')" />
85142
86143 <div class =" space-y-6" >
87- <!-- Loading State -->
88- <div v-if =" isLoading" class =" text-muted-foreground" >
89- {{ t('adminTeams.table.loading') }}
90- </div >
91-
92144 <!-- Error State -->
93- <div v-else- if =" error" class =" text-red-500" >
145+ <div v-if =" error" class =" text-red-500" >
94146 {{ t('adminTeams.table.error', { error }) }}
95147 </div >
96148
97- <!-- Data Table -->
149+ <!-- Data Table with Search -->
98150 <div v-else class =" space-y-4" >
99151 <!-- Search Input -->
100152 <div class =" flex items-center py-4" >
101- <Input
102- :placeholder =" t('adminTeams.table.search.placeholder')"
103- v-model =" searchQuery"
104- class =" max-w-sm"
105- />
153+ <div class =" flex items-center rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px] has-[input:disabled]:opacity-50 has-[input:disabled]:cursor-not-allowed max-w-sm" >
154+ <input
155+ type =" text"
156+ :placeholder =" t('adminTeams.table.search.placeholder')"
157+ v-model =" searchQuery"
158+ @keyup.enter =" executeSearch"
159+ class =" flex-1 h-9 min-w-0 bg-transparent px-3 py-1 text-base outline-none placeholder:text-muted-foreground md:text-sm disabled:pointer-events-none disabled:cursor-not-allowed"
160+ />
161+ <div class =" flex items-center justify-center text-muted-foreground order-last pr-3" >
162+ <Search class =" h-4 w-4" />
163+ </div >
164+ </div >
165+ </div >
166+
167+ <!-- Loading State with Skeleton -->
168+ <div v-if =" isLoading" class =" rounded-md border" >
169+ <Table >
170+ <TableHeader >
171+ <TableRow >
172+ <TableHead >{{ t('adminTeams.table.columns.name') }}</TableHead >
173+ <TableHead >{{ t('adminTeams.table.columns.slug') }}</TableHead >
174+ <TableHead >{{ t('adminTeams.table.columns.type') }}</TableHead >
175+ <TableHead >{{ t('adminTeams.table.columns.createdAt') }}</TableHead >
176+ <TableHead class =" w-[100px]" >{{ t('adminTeams.table.columns.actions') }}</TableHead >
177+ </TableRow >
178+ </TableHeader >
179+ <TableBody >
180+ <TableRow v-for =" i in 5" :key =" i" >
181+ <TableCell ><Skeleton class =" h-4 w-32" /></TableCell >
182+ <TableCell ><Skeleton class =" h-4 w-24" /></TableCell >
183+ <TableCell ><Skeleton class =" h-5 w-16" /></TableCell >
184+ <TableCell ><Skeleton class =" h-4 w-20" /></TableCell >
185+ <TableCell ><Skeleton class =" h-8 w-16" /></TableCell >
186+ </TableRow >
187+ </TableBody >
188+ </Table >
106189 </div >
107190
108191 <!-- Teams Table Component -->
109192 <TeamTableColumns
110- :teams =" filteredTeams"
193+ v-else
194+ :teams =" teams"
111195 :on-view-team =" handleViewTeam"
112196 />
197+
198+ <!-- Pagination Controls -->
199+ <PaginationControls
200+ v-if =" totalItems > 0"
201+ :current-page =" currentPage"
202+ :page-size =" pageSize"
203+ :total-items =" totalItems"
204+ :is-loading =" isLoading"
205+ @page-change =" handlePageChange"
206+ @page-size-change =" handlePageSizeChange"
207+ />
113208 </div >
114209 </div >
115210 </NavbarLayout >
0 commit comments