fix withdraw page search bug

This commit is contained in:
2025-12-23 16:38:09 +03:30
parent 0d6ac60459
commit 7963af93cd
2 changed files with 220 additions and 70 deletions
+117 -37
View File
@@ -180,17 +180,33 @@ export default {
try { try {
const page = parseInt(query.page) || 1 const page = parseInt(query.page) || 1
const rows = parseInt(query.rows) || 10 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', { const { data } = await $axios.get('/api/admin/gps/withdraw', {
params: { params
page,
rows
}
}) })
return { return {
requestsData: data.data, requestsData: data.data,
totalDocuments: data.totalDocuments || 0, totalDocuments: data.totalDocuments || 0,
totalPages: data.totalPages || 1 totalPages: data.totalPages || 1,
filterText: query.search || ''
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
@@ -213,31 +229,16 @@ export default {
config() { config() {
return this.$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() { filteredItems() {
console.log('res data', this.requestsData) // No client-side filtering needed - all filtering is done on backend
return 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)
)
})
}
}, },
totalFilteredItems() { totalFilteredItems() {
return this.filteredItems.length return this.filteredItems.length
@@ -253,33 +254,112 @@ export default {
}, },
filterType: { filterType: {
get() { get() {
return this.$route.query?.filter return this.$route.query?.filter || 'all'
}, },
set(val) { 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: { watch: {
filterText() { filterText(newVal, oldVal) {
this.fetchData(1) // 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() { filterType() {
this.fetchData(1) this.fetchData(1)
}, },
'$route.query.page'() { '$route.query.page'() {
this.fetchData() 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: { 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) { async fetchData(page = null) {
const currentPage = page || this.currentPage const currentPage = page || this.currentPage
try { 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', { const { data } = await this.$axios.get('/api/admin/gps/withdraw', {
params: { params
page: currentPage,
rows: this.pageSize
}
}) })
this.requestsData = data.data this.requestsData = data.data
this.totalDocuments = data.totalDocuments || 0 this.totalDocuments = data.totalDocuments || 0
+103 -33
View File
@@ -131,48 +131,118 @@ module.exports.getAllForAdmin = async (req, res) => {
const page = parseInt(req.query.page) || 1 const page = parseInt(req.query.page) || 1
const rows = parseInt(req.query.rows) || 10 const rows = parseInt(req.query.rows) || 10
const search = req.query.search || '' const search = req.query.search || ''
const status = req.query.status !== undefined ? parseInt(req.query.status) : undefined
const sortField = req.query.sortField || 'status' const sortField = req.query.sortField || 'status'
const sortOrder = req.query.sortOrder === 'desc' ? -1 : 1 const sortOrder = req.query.sortOrder === 'desc' ? -1 : 1
const skip = (page - 1) * rows const skip = (page - 1) * rows
const matchStage = search // If search is provided, use aggregation to search in user fields
? { if (search) {
$or: [{ IMEI: new RegExp(search, 'i') }, { trackingNumber: new RegExp(search, 'i') }] const matchStage = {}
}
: {} if (status !== undefined) {
matchStage.status = status
}
// const aggregationPipeline = [ // Build search conditions
// { $match: matchStage }, const searchConditions = [
// { { 'userId.mobile_number': new RegExp(search, 'i') },
// $lookup: { { 'userId.national_code': new RegExp(search, 'i') },
// from: 'usergps', { 'userId.first_name': new RegExp(search, 'i') },
// localField: 'userId', { 'userId.last_name': new RegExp(search, 'i') },
// foreignField: '_id', { 'userId.shopName': new RegExp(search, 'i') },
// as: 'userId' { trackingNumber: new RegExp(search, 'i') }
// } ]
// },
// { $unwind: '$userId' }, // Add amount search if search is a number
// { $sort: { [sortField]: sortOrder } }, if (!isNaN(search) && search.trim() !== '') {
// { $skip: skip }, searchConditions.push({ amount: parseInt(search) })
// { $limit: rows } }
// ]
// const data = await Withdraw.aggregate(aggregationPipeline)
const data = await Withdraw.find(matchStage)
.sort({ [sortField]: sortOrder })
.skip(skip)
.limit(rows)
.populate('userId')
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([ const countPipeline = [
// Withdraw.aggregate(aggregationPipeline), { $match: matchStage },
// Withdraw.countDocuments(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) { } catch (error) {
res.status(500).json({ message: 'An error occurred, please try again.', error: error.message }) res.status(500).json({ message: 'An error occurred, please try again.', error: error.message })
} }