Add award detail page and fix award progress calculation
## Frontend - Create award detail page at /awards/[id] - Show all entities with worked/confirmed status - Add filtering (all, worked, confirmed, unworked) - Add sorting (name, status) - Display summary cards (total, confirmed, worked, needed) - Show entity details (callsign, band, mode, date) ## Backend Fixes - Fix award progress calculation for filtered awards - Add normalizeAwardRules to handle "filtered" type awards - Fix satellite filter to check satName field instead of satellite - Add case-insensitive contains matching - Apply normalization to both progress and entity breakdown functions This fixes the 0/0 issue for DXCC CW, WAS, VUCC, and satellite awards. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,13 +60,33 @@ export async function getAllAwards() {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize award rules to a consistent format
|
||||
*/
|
||||
function normalizeAwardRules(rules) {
|
||||
// Handle "filtered" type awards (like DXCC CW)
|
||||
if (rules.type === 'filtered' && rules.baseRule) {
|
||||
return {
|
||||
type: 'entity',
|
||||
entityType: rules.baseRule.entityType,
|
||||
target: rules.baseRule.target,
|
||||
filters: rules.filters,
|
||||
};
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate award progress for a user
|
||||
* @param {number} userId - User ID
|
||||
* @param {Object} award - Award definition
|
||||
*/
|
||||
export async function calculateAwardProgress(userId, award) {
|
||||
const { rules } = award;
|
||||
let { rules } = award;
|
||||
|
||||
// Normalize rules to handle different formats
|
||||
rules = normalizeAwardRules(rules);
|
||||
|
||||
// Get all QSOs for user
|
||||
const allQSOs = await db
|
||||
@@ -149,7 +169,15 @@ function applyFilters(qsos, filters) {
|
||||
* Check if a QSO matches a filter
|
||||
*/
|
||||
function matchesFilter(qso, filter) {
|
||||
const value = qso[filter.field];
|
||||
let value;
|
||||
|
||||
// Special handling for satellite field
|
||||
if (filter.field === 'satellite') {
|
||||
// Check if it's a satellite QSO (has satName)
|
||||
value = qso.satName && qso.satName.length > 0;
|
||||
} else {
|
||||
value = qso[filter.field];
|
||||
}
|
||||
|
||||
switch (filter.operator) {
|
||||
case 'eq':
|
||||
@@ -161,7 +189,7 @@ function matchesFilter(qso, filter) {
|
||||
case 'nin':
|
||||
return Array.isArray(filter.value) && !filter.value.includes(value);
|
||||
case 'contains':
|
||||
return value && typeof value === 'string' && value.includes(filter.value);
|
||||
return value && typeof value === 'string' && value.toLowerCase().includes(filter.value.toLowerCase());
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
@@ -204,7 +232,10 @@ export async function getAwardEntityBreakdown(userId, awardId) {
|
||||
throw new Error('Award not found');
|
||||
}
|
||||
|
||||
const { rules } = award;
|
||||
let { rules } = award;
|
||||
|
||||
// Normalize rules to handle different formats
|
||||
rules = normalizeAwardRules(rules);
|
||||
|
||||
// Get all QSOs for user
|
||||
const allQSOs = await db
|
||||
|
||||
Reference in New Issue
Block a user