Add displayField configuration to award definitions

Add configurable displayField to award definitions to control what
is shown in award details instead of always using the entity value.

## Award Definitions Updated
- DXCC: displayField = 'entity' (shows country name)
- DXCC CW: displayField = 'entity' (shows country name)
- WAS: displayField = 'state' (shows state name)
- VUCC: displayField = 'grid' (shows grid square)
- RS-44: displayField = 'callsign' (shows callsign)

## Backend Changes
- Preserve displayField when normalizing award rules
- Use displayField to determine entity name in details
- Fallback logic for awards without displayField
- Add satName to entity data for better display

This fixes VUCC showing grid squares instead of country names.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-16 09:12:07 +01:00
parent 3b7dfd74fe
commit 052f0d13bd
11 changed files with 28 additions and 5 deletions

View File

@@ -8,7 +8,8 @@
"baseRule": { "baseRule": {
"type": "entity", "type": "entity",
"entityType": "dxcc", "entityType": "dxcc",
"target": 100 "target": 100,
"displayField": "entity"
}, },
"filters": { "filters": {
"operator": "AND", "operator": "AND",

View File

@@ -6,6 +6,7 @@
"rules": { "rules": {
"type": "entity", "type": "entity",
"entityType": "dxcc", "entityType": "dxcc",
"target": 100 "target": 100,
"displayField": "entity"
} }
} }

View File

@@ -7,6 +7,7 @@
"type": "counter", "type": "counter",
"target": 44, "target": 44,
"countBy": "qso", "countBy": "qso",
"displayField": "callsign",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [

View File

@@ -7,6 +7,7 @@
"type": "entity", "type": "entity",
"entityType": "grid", "entityType": "grid",
"target": 100, "target": 100,
"displayField": "grid",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [

View File

@@ -7,6 +7,7 @@
"type": "entity", "type": "entity",
"entityType": "state", "entityType": "state",
"target": 50, "target": 50,
"displayField": "state",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [

View File

@@ -70,6 +70,7 @@ function normalizeAwardRules(rules) {
type: 'entity', type: 'entity',
entityType: rules.baseRule.entityType, entityType: rules.baseRule.entityType,
target: rules.baseRule.target, target: rules.baseRule.target,
displayField: rules.baseRule.displayField,
filters: rules.filters, filters: rules.filters,
}; };
} }
@@ -81,6 +82,7 @@ function normalizeAwardRules(rules) {
type: 'entity', type: 'entity',
entityType: rules.countBy === 'qso' ? 'callsign' : 'callsign', entityType: rules.countBy === 'qso' ? 'callsign' : 'callsign',
target: rules.target, target: rules.target,
displayField: rules.displayField,
filters: rules.filters, filters: rules.filters,
}; };
} }
@@ -277,16 +279,27 @@ export async function getAwardEntityBreakdown(userId, awardId) {
if (!entity) continue; if (!entity) continue;
if (!entityMap.has(entity)) { if (!entityMap.has(entity)) {
// Determine what to display as the entity name
// Use displayField from award rules, or fallback to entity/type
let displayName = String(entity);
if (rules.displayField) {
displayName = String(qso[rules.displayField] || entity);
} else {
// Fallback: try entity, state, grid, callsign in order
displayName = qso.entity || qso.state || qso.grid || qso.callsign || String(entity);
}
entityMap.set(entity, { entityMap.set(entity, {
entity, entity,
entityId: qso.entityId, entityId: qso.entityId,
entityName: qso.entity || qso.state || qso.grid || qso.callsign || String(entity), entityName: displayName,
worked: false, worked: false,
confirmed: false, confirmed: false,
qsoDate: qso.qsoDate, qsoDate: qso.qsoDate,
band: qso.band, band: qso.band,
mode: qso.mode, mode: qso.mode,
callsign: qso.callsign, callsign: qso.callsign,
satName: qso.satName,
}); });
} }

View File

@@ -8,7 +8,8 @@
"baseRule": { "baseRule": {
"type": "entity", "type": "entity",
"entityType": "dxcc", "entityType": "dxcc",
"target": 100 "target": 100,
"displayField": "entity"
}, },
"filters": { "filters": {
"operator": "AND", "operator": "AND",

View File

@@ -6,6 +6,7 @@
"rules": { "rules": {
"type": "entity", "type": "entity",
"entityType": "dxcc", "entityType": "dxcc",
"target": 100 "target": 100,
"displayField": "entity"
} }
} }

View File

@@ -7,6 +7,7 @@
"type": "counter", "type": "counter",
"target": 44, "target": 44,
"countBy": "qso", "countBy": "qso",
"displayField": "callsign",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [

View File

@@ -7,6 +7,7 @@
"type": "entity", "type": "entity",
"entityType": "grid", "entityType": "grid",
"target": 100, "target": 100,
"displayField": "grid",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [

View File

@@ -7,6 +7,7 @@
"type": "entity", "type": "entity",
"entityType": "state", "entityType": "state",
"target": 50, "target": 50,
"displayField": "state",
"filters": { "filters": {
"operator": "AND", "operator": "AND",
"filters": [ "filters": [