fix: use qsoId for fetching QSO details in award page modal
The award page was filtering QSOs by callsign/date/band/mode, which could return the wrong QSO when multiple QSOs with the same callsign exist on the same band/mode combination. Changes: - Backend: Add qsoId field to award entity breakdown responses - Backend: Add GET /api/qsos/:id endpoint to fetch QSO by ID - Backend: Implement getQSOById() function in lotw.service.js - Frontend: Update openQSODetailModal() to fetch by qsoId instead of filtering - Frontend: Include qsoId in QSO entry objects for modal click handler Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
getUserQSOs,
|
getUserQSOs,
|
||||||
getQSOStats,
|
getQSOStats,
|
||||||
deleteQSOs,
|
deleteQSOs,
|
||||||
|
getQSOById,
|
||||||
} from './services/lotw.service.js';
|
} from './services/lotw.service.js';
|
||||||
import {
|
import {
|
||||||
enqueueJob,
|
enqueueJob,
|
||||||
@@ -489,6 +490,44 @@ const app = new Elysia()
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/qsos/:id
|
||||||
|
* Get a single QSO by ID (requires authentication)
|
||||||
|
*/
|
||||||
|
.get('/api/qsos/:id', async ({ user, params, set }) => {
|
||||||
|
if (!user) {
|
||||||
|
set.status = 401;
|
||||||
|
return { success: false, error: 'Unauthorized' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const qsoId = parseInt(params.id);
|
||||||
|
if (isNaN(qsoId)) {
|
||||||
|
set.status = 400;
|
||||||
|
return { success: false, error: 'Invalid QSO ID' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const qso = await getQSOById(user.id, qsoId);
|
||||||
|
|
||||||
|
if (!qso) {
|
||||||
|
set.status = 404;
|
||||||
|
return { success: false, error: 'QSO not found' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
qso,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to fetch QSO by ID', { error: error.message, userId: user?.id, qsoId: params.id });
|
||||||
|
set.status = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to fetch QSO',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/qsos/stats
|
* GET /api/qsos/stats
|
||||||
* Get QSO statistics (requires authentication)
|
* Get QSO statistics (requires authentication)
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ async function calculateDOKAwardProgress(userId, award, options = {}) {
|
|||||||
// Initialize combination if not exists
|
// Initialize combination if not exists
|
||||||
if (!dokCombinations.has(combinationKey)) {
|
if (!dokCombinations.has(combinationKey)) {
|
||||||
dokCombinations.set(combinationKey, {
|
dokCombinations.set(combinationKey, {
|
||||||
|
qsoId: qso.id,
|
||||||
entity: dok,
|
entity: dok,
|
||||||
entityId: null,
|
entityId: null,
|
||||||
entityName: dok,
|
entityName: dok,
|
||||||
@@ -335,6 +336,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
|
|
||||||
if (!combinationMap.has(combinationKey)) {
|
if (!combinationMap.has(combinationKey)) {
|
||||||
combinationMap.set(combinationKey, {
|
combinationMap.set(combinationKey, {
|
||||||
|
qsoId: qso.id,
|
||||||
callsign,
|
callsign,
|
||||||
band,
|
band,
|
||||||
mode,
|
mode,
|
||||||
@@ -373,6 +375,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
|
|
||||||
if (!stationMap.has(callsign)) {
|
if (!stationMap.has(callsign)) {
|
||||||
stationMap.set(callsign, {
|
stationMap.set(callsign, {
|
||||||
|
qsoId: qso.id,
|
||||||
callsign,
|
callsign,
|
||||||
points,
|
points,
|
||||||
worked: true,
|
worked: true,
|
||||||
@@ -410,6 +413,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
if (qso.lotwQslRstatus === 'Y') {
|
if (qso.lotwQslRstatus === 'Y') {
|
||||||
totalPoints += points;
|
totalPoints += points;
|
||||||
stationDetails.push({
|
stationDetails.push({
|
||||||
|
qsoId: qso.id,
|
||||||
callsign,
|
callsign,
|
||||||
points,
|
points,
|
||||||
worked: true,
|
worked: true,
|
||||||
@@ -446,6 +450,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
const entities = stationDetails.map((detail) => {
|
const entities = stationDetails.map((detail) => {
|
||||||
if (countMode === 'perBandMode') {
|
if (countMode === 'perBandMode') {
|
||||||
return {
|
return {
|
||||||
|
qsoId: detail.qsoId,
|
||||||
entity: `${detail.callsign}/${detail.band}/${detail.mode}`,
|
entity: `${detail.callsign}/${detail.band}/${detail.mode}`,
|
||||||
entityId: null,
|
entityId: null,
|
||||||
entityName: `${detail.callsign} (${detail.band}/${detail.mode})`,
|
entityName: `${detail.callsign} (${detail.band}/${detail.mode})`,
|
||||||
@@ -460,6 +465,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
};
|
};
|
||||||
} else if (countMode === 'perStation') {
|
} else if (countMode === 'perStation') {
|
||||||
return {
|
return {
|
||||||
|
qsoId: detail.qsoId,
|
||||||
entity: detail.callsign,
|
entity: detail.callsign,
|
||||||
entityId: null,
|
entityId: null,
|
||||||
entityName: detail.callsign,
|
entityName: detail.callsign,
|
||||||
@@ -474,6 +480,7 @@ async function calculatePointsAwardProgress(userId, award, options = {}) {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
qsoId: detail.qsoId,
|
||||||
entity: `${detail.callsign}-${detail.qsoDate}`,
|
entity: `${detail.callsign}-${detail.qsoDate}`,
|
||||||
entityId: null,
|
entityId: null,
|
||||||
entityName: `${detail.callsign} on ${detail.qsoDate}`,
|
entityName: `${detail.callsign} on ${detail.qsoDate}`,
|
||||||
@@ -673,6 +680,7 @@ export async function getAwardEntityBreakdown(userId, awardId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entityMap.set(entity, {
|
entityMap.set(entity, {
|
||||||
|
qsoId: qso.id,
|
||||||
entity,
|
entity,
|
||||||
entityId: qso.entityId,
|
entityId: qso.entityId,
|
||||||
entityName: displayName,
|
entityName: displayName,
|
||||||
|
|||||||
@@ -451,3 +451,18 @@ export async function deleteQSOs(userId) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single QSO by ID for a specific user
|
||||||
|
* @param {number} userId - User ID
|
||||||
|
* @param {number} qsoId - QSO ID
|
||||||
|
* @returns {Object|null} QSO object or null if not found
|
||||||
|
*/
|
||||||
|
export async function getQSOById(userId, qsoId) {
|
||||||
|
const result = await db
|
||||||
|
.select()
|
||||||
|
.from(qsos)
|
||||||
|
.where(and(eq(qsos.userId, userId), eq(qsos.id, qsoId)));
|
||||||
|
|
||||||
|
return result.length > 0 ? result[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
|
|
||||||
// Add QSO info to this band
|
// Add QSO info to this band
|
||||||
entityData.bands.get(entity.band).push({
|
entityData.bands.get(entity.band).push({
|
||||||
|
qsoId: entity.qsoId,
|
||||||
callsign: entity.callsign,
|
callsign: entity.callsign,
|
||||||
mode: entity.mode,
|
mode: entity.mode,
|
||||||
band: entity.band,
|
band: entity.band,
|
||||||
@@ -131,6 +132,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
entityData.bands.get(entity.band).push({
|
entityData.bands.get(entity.band).push({
|
||||||
|
qsoId: entity.qsoId,
|
||||||
callsign: entity.callsign,
|
callsign: entity.callsign,
|
||||||
mode: entity.mode,
|
mode: entity.mode,
|
||||||
band: entity.band,
|
band: entity.band,
|
||||||
@@ -169,16 +171,8 @@
|
|||||||
selectedQSO = null;
|
selectedQSO = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch full QSO details using filters
|
// Fetch full QSO details by ID
|
||||||
const params = new URLSearchParams({
|
const response = await fetch(`/api/qsos/${qso.qsoId}`, {
|
||||||
callsign: qso.callsign,
|
|
||||||
qsoDate: qso.qsoDate,
|
|
||||||
band: qso.band,
|
|
||||||
mode: qso.mode,
|
|
||||||
limit: '1'
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`/api/qsos?${params}`, {
|
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${$auth.token}`,
|
'Authorization': `Bearer ${$auth.token}`,
|
||||||
},
|
},
|
||||||
@@ -194,8 +188,8 @@
|
|||||||
throw new Error(data.error || 'Failed to fetch QSO details');
|
throw new Error(data.error || 'Failed to fetch QSO details');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.qsos && data.qsos.length > 0) {
|
if (data.qso) {
|
||||||
selectedQSO = data.qsos[0];
|
selectedQSO = data.qso;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('QSO not found');
|
throw new Error('QSO not found');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user