diff --git a/src/frontend/src/routes/awards/[id]/+page.svelte b/src/frontend/src/routes/awards/[id]/+page.svelte index e6ffb01..a8b4edc 100644 --- a/src/frontend/src/routes/awards/[id]/+page.svelte +++ b/src/frontend/src/routes/awards/[id]/+page.svelte @@ -9,6 +9,8 @@ let error = null; let filter = 'all'; // all, worked, confirmed, unworked let sort = 'name'; // name, status + let groupedData = []; + let bands = []; onMount(async () => { await loadAwardData(); @@ -40,6 +42,9 @@ award = data.award; entities = data.entities || []; + + // Group data for table display + groupDataForTable(); } catch (e) { error = e.message; } finally { @@ -47,6 +52,93 @@ } } + function groupDataForTable() { + // Group by entity name, then create band columns + const entityMap = new Map(); + const bandsSet = new Set(); + + entities.forEach((entity) => { + const entityName = entity.entityName || entity.entity || 'Unknown'; + + if (!entityMap.has(entityName)) { + entityMap.set(entityName, { + entityName, + bands: new Map(), + worked: entity.worked, + confirmed: entity.confirmed, + }); + } + + const entityData = entityMap.get(entityName); + + if (entity.band) { + bandsSet.add(entity.band); + + if (!entityData.bands.has(entity.band)) { + entityData.bands.set(entity.band, []); + } + + // Add QSO info to this band + entityData.bands.get(entity.band).push({ + callsign: entity.callsign, + mode: entity.mode, + confirmed: entity.confirmed, + qsoDate: entity.qsoDate, + }); + } + }); + + // Convert bands Set to sorted array + bands = Array.from(bandsSet).sort(); + + // Convert Map to array + groupedData = Array.from(entityMap.values()); + + // Apply filtering + applyFilter(); + } + + function applyFilter() { + // Re-group from original entities with filter applied + const filteredEntities = getFilteredEntities(); + + const entityMap = new Map(); + const bandsSet = new Set(); + + filteredEntities.forEach((entity) => { + const entityName = entity.entityName || entity.entity || 'Unknown'; + + if (!entityMap.has(entityName)) { + entityMap.set(entityName, { + entityName, + bands: new Map(), + worked: entity.worked, + confirmed: entity.confirmed, + }); + } + + const entityData = entityMap.get(entityName); + + if (entity.band) { + bandsSet.add(entity.band); + + if (!entityData.bands.has(entity.band)) { + entityData.bands.set(entity.band, []); + } + + entityData.bands.get(entity.band).push({ + callsign: entity.callsign, + mode: entity.mode, + confirmed: entity.confirmed, + qsoDate: entity.qsoDate, + }); + } + }); + + bands = Array.from(bandsSet).sort(); + groupedData = Array.from(entityMap.values()); + } + function getFilteredEntities() { let filtered = [...entities]; @@ -88,16 +180,9 @@ return filtered; } - function getStatusClass(entity) { - if (entity.confirmed) return 'confirmed'; - if (entity.worked) return 'worked'; - return 'unworked'; - } - - function getStatusText(entity) { - if (entity.confirmed) return 'Confirmed'; - if (entity.worked) return 'Worked'; - return 'Not Worked'; + // Re-apply filter when filter/sort changes + $: if (entities.length > 0) { + applyFilter(); } @@ -187,44 +272,46 @@ {/if} -
- {#if getFilteredEntities().length === 0} +
+ {#if groupedData.length === 0}
No entities match the current filter.
{:else} - {#each getFilteredEntities() as entity (entity.entity)} -
-
-
- {entity.entityName || entity.entity || 'Unknown'} - {#if entity.points} - {entity.points} pts - {/if} - {#if entity.entityId && entity.entityId !== entity.entityName} - ({entity.entityId}) - {/if} -
-
- {#if entity.callsign} - {entity.callsign} - {/if} - {#if entity.band} - {entity.band} - {/if} - {#if entity.mode} - {entity.mode} - {/if} - {#if entity.qsoDate} - {entity.qsoDate} - {/if} -
-
-
- - {getStatusText(entity)} - -
-
- {/each} + + + + + {#each bands as band} + + {/each} + + + + {#each groupedData as row} + + + {#each bands as band} + {@const qsos = row.bands.get(band) || []} + + {/each} + + {/each} + +
Entity{band}
+
{row.entityName}
+
+ {#if qsos.length > 0} +
+ {#each qsos as qso} +
+ {qso.callsign} + {qso.mode} +
+ {/each} +
+ {:else} +
-
+ {/if} +
{/if}
{/if} @@ -250,6 +337,16 @@ color: #d32f2f; } + .award-table { + border: 1px solid #000; + border-collapse: collapse; + } + + .award-table td { + border: 1px solid #000; + padding: 0px 3px 0px 3px; +} + .award-header { margin-bottom: 2rem; }