diff --git a/pages/admin/gps/withdraw/index.vue b/pages/admin/gps/withdraw/index.vue index 41f0926..083db15 100644 --- a/pages/admin/gps/withdraw/index.vue +++ b/pages/admin/gps/withdraw/index.vue @@ -180,17 +180,33 @@ export default { try { const page = parseInt(query.page) || 1 const rows = parseInt(query.rows) || 10 + + // Map filter query to status + let status = undefined + if (query.filter === 'dep') status = 2 + else if (query.filter === 'accept') status = 1 + else if (query.filter === 'pend') status = 0 + + const params = { + page, + rows + } + if (status !== undefined) { + params.status = status + } + if (query.search) { + params.search = query.search + } + const { data } = await $axios.get('/api/admin/gps/withdraw', { - params: { - page, - rows - } + params }) return { requestsData: data.data, totalDocuments: data.totalDocuments || 0, - totalPages: data.totalPages || 1 + totalPages: data.totalPages || 1, + filterText: query.search || '' } } catch (error) { console.log(error) @@ -213,31 +229,16 @@ export default { config() { return this.$config }, + statusValue() { + // Map filterType to status integer + if (this.filterType === 'dep') return 2 + if (this.filterType === 'accept') return 1 + if (this.filterType === 'pend') return 0 + return undefined // 'all' returns undefined to show all + }, filteredItems() { - console.log('res data', this.requestsData) - - const filterUsersByType = this.requestsData.filter(item => { - if (this.filterType === 'all') return item - // eslint-disable-next-line eqeqeq - else if (this.filterType === 'dep') return item.status == 2 - // eslint-disable-next-line eqeqeq - else if (this.filterType === 'accept') return item.status == 1 - // eslint-disable-next-line eqeqeq - else if (this.filterType === 'pend') return item.status == 0 - else return null - }) - const filterText = this.filterText - if (!this.filterText.length) return filterUsersByType - else { - return filterUsersByType.filter(item => { - return ( - item.userId.mobile_number.includes(filterText) || - item.userId.national_code.includes(filterText) || - item.userId.last_name.includes(filterText) || - item.userId.first_name.includes(filterText) - ) - }) - } + // No client-side filtering needed - all filtering is done on backend + return this.requestsData }, totalFilteredItems() { return this.filteredItems.length @@ -253,33 +254,112 @@ export default { }, filterType: { get() { - return this.$route.query?.filter + return this.$route.query?.filter || 'all' }, set(val) { - this.$router.push({ name: 'admin-gps-withdraw', query: { filter: val } }) + this.$router.push({ + name: 'admin-gps-withdraw', + query: { + filter: val, + page: 1 // Reset to first page when filter changes + } + }) } } }, + mounted() { + // Initialize filterText from route query + if (this.$route.query.search) { + this.filterText = this.$route.query.search + } + }, + beforeDestroy() { + // Clear search timeout on component destroy + if (this.searchTimeout) { + clearTimeout(this.searchTimeout) + } + }, watch: { - filterText() { - this.fetchData(1) + filterText(newVal, oldVal) { + // Only update route if value actually changed (avoid circular updates) + if (newVal === oldVal) return + + // Clear existing timeout + if (this.searchTimeout) { + clearTimeout(this.searchTimeout) + } + + // If search is cleared (empty), update immediately without debounce + const isCleared = !newVal || !newVal.trim() + const wasNotEmpty = oldVal && oldVal.trim() + + if (isCleared && wasNotEmpty) { + // Immediately update when clearing + this.updateSearchQuery(newVal) + } else { + // Debounce search to avoid too many requests + this.searchTimeout = setTimeout(() => { + this.updateSearchQuery(newVal) + }, 500) // 500ms debounce + } }, filterType() { this.fetchData(1) }, '$route.query.page'() { this.fetchData() + }, + '$route.query.search'(newVal, oldVal) { + // Sync filterText with route query (only if different to avoid loop) + const searchValue = newVal || '' + if (searchValue !== this.filterText) { + this.filterText = searchValue + } + // Always fetch data when search query changes + if (newVal !== oldVal) { + this.fetchData(1) + } } }, methods: { + updateSearchQuery(searchValue) { + // Prevent circular update - check if route query already matches + const currentSearch = this.$route.query.search || '' + const newSearch = searchValue && searchValue.trim() ? searchValue.trim() : undefined + + if (currentSearch === (newSearch || '')) return + + // Update route query - this will trigger $route.query.search watcher + this.$router.push({ + name: 'admin-gps-withdraw', + query: { + ...this.$route.query, + search: newSearch, + page: 1 // Reset to first page when search changes + } + }) + }, async fetchData(page = null) { const currentPage = page || this.currentPage try { + const params = { + page: currentPage, + rows: this.pageSize + } + + // Add status parameter if filterType is set + if (this.statusValue !== undefined) { + params.status = this.statusValue + } + + // Add search parameter from route query (source of truth) + const searchQuery = this.$route.query.search + if (searchQuery && searchQuery.trim()) { + params.search = searchQuery.trim() + } + const { data } = await this.$axios.get('/api/admin/gps/withdraw', { - params: { - page: currentPage, - rows: this.pageSize - } + params }) this.requestsData = data.data this.totalDocuments = data.totalDocuments || 0 diff --git a/server/controllers/GPS.Withdraw.js b/server/controllers/GPS.Withdraw.js index 5b20cd4..b9696e1 100644 --- a/server/controllers/GPS.Withdraw.js +++ b/server/controllers/GPS.Withdraw.js @@ -131,48 +131,118 @@ module.exports.getAllForAdmin = async (req, res) => { const page = parseInt(req.query.page) || 1 const rows = parseInt(req.query.rows) || 10 const search = req.query.search || '' + const status = req.query.status !== undefined ? parseInt(req.query.status) : undefined const sortField = req.query.sortField || 'status' const sortOrder = req.query.sortOrder === 'desc' ? -1 : 1 const skip = (page - 1) * rows - const matchStage = search - ? { - $or: [{ IMEI: new RegExp(search, 'i') }, { trackingNumber: new RegExp(search, 'i') }] - } - : {} + // If search is provided, use aggregation to search in user fields + if (search) { + const matchStage = {} + + if (status !== undefined) { + matchStage.status = status + } - // const aggregationPipeline = [ - // { $match: matchStage }, - // { - // $lookup: { - // from: 'usergps', - // localField: 'userId', - // foreignField: '_id', - // as: 'userId' - // } - // }, - // { $unwind: '$userId' }, - // { $sort: { [sortField]: sortOrder } }, - // { $skip: skip }, - // { $limit: rows } - // ] - // const data = await Withdraw.aggregate(aggregationPipeline) - const data = await Withdraw.find(matchStage) - .sort({ [sortField]: sortOrder }) - .skip(skip) - .limit(rows) - .populate('userId') + // Build search conditions + const searchConditions = [ + { 'userId.mobile_number': new RegExp(search, 'i') }, + { 'userId.national_code': new RegExp(search, 'i') }, + { 'userId.first_name': new RegExp(search, 'i') }, + { 'userId.last_name': new RegExp(search, 'i') }, + { 'userId.shopName': new RegExp(search, 'i') }, + { trackingNumber: new RegExp(search, 'i') } + ] + + // Add amount search if search is a number + if (!isNaN(search) && search.trim() !== '') { + searchConditions.push({ amount: parseInt(search) }) + } - const totalDocuments = await Withdraw.countDocuments(matchStage) + const aggregationPipeline = [ + { $match: matchStage }, + { + $lookup: { + from: 'usergps', + let: { userIdStr: '$userId' }, + pipeline: [ + { + $match: { + $expr: { + $eq: ['$_id', { $toObjectId: '$$userIdStr' }] + } + } + } + ], + as: 'userId' + } + }, + { $unwind: { path: '$userId', preserveNullAndEmptyArrays: false } }, + { + $match: { + $or: searchConditions + } + }, + { $sort: { [sortField]: sortOrder } }, + { $skip: skip }, + { $limit: rows } + ] - // const [data, totalDocuments] = await Promise.all([ - // Withdraw.aggregate(aggregationPipeline), - // Withdraw.countDocuments(matchStage) - // ]) + const countPipeline = [ + { $match: matchStage }, + { + $lookup: { + from: 'usergps', + let: { userIdStr: '$userId' }, + pipeline: [ + { + $match: { + $expr: { + $eq: ['$_id', { $toObjectId: '$$userIdStr' }] + } + } + } + ], + as: 'userId' + } + }, + { $unwind: { path: '$userId', preserveNullAndEmptyArrays: false } }, + { + $match: { + $or: searchConditions + } + }, + { $count: 'total' } + ] - const totalPages = Math.ceil(totalDocuments / rows) + const [data, countResult] = await Promise.all([ + Withdraw.aggregate(aggregationPipeline), + Withdraw.aggregate(countPipeline) + ]) - res.status(200).json({ data, totalDocuments, totalPages }) + const totalDocuments = countResult[0]?.total || 0 + const totalPages = Math.ceil(totalDocuments / rows) + + res.status(200).json({ data, totalDocuments, totalPages }) + } else { + // No search - use simple find with populate + const matchStage = {} + + if (status !== undefined) { + matchStage.status = status + } + + const data = await Withdraw.find(matchStage) + .sort({ [sortField]: sortOrder }) + .skip(skip) + .limit(rows) + .populate('userId') + + const totalDocuments = await Withdraw.countDocuments(matchStage) + const totalPages = Math.ceil(totalDocuments / rows) + + res.status(200).json({ data, totalDocuments, totalPages }) + } } catch (error) { res.status(500).json({ message: 'An error occurred, please try again.', error: error.message }) }