+
Award Definitions
+
Manage award definitions. Create, edit, and delete awards.
+
+
+
+
+
Award Management
+
From the Awards management page, you can:
+
+ - Create new award definitions
+ - Edit existing award definitions
+ - Delete awards
+ - Test award calculations with sample user data
+
+
All award definitions are stored as JSON files in the award-definitions/ directory.
+
+
+ {/if}
+
{#if selectedTab === 'actions'}
@@ -919,6 +949,50 @@
cursor: pointer;
}
+ .help-text {
+ color: #666;
+ font-style: italic;
+ margin-bottom: 1rem;
+ }
+
+ .awards-quick-actions {
+ margin-bottom: 2rem;
+ }
+
+ .awards-info {
+ background-color: #f9f9f9;
+ border-left: 4px solid #667eea;
+ padding: 1.5rem;
+ border-radius: 4px;
+ }
+
+ .awards-info h3 {
+ margin-top: 0;
+ margin-bottom: 1rem;
+ color: #333;
+ }
+
+ .awards-info p {
+ margin-bottom: 1rem;
+ }
+
+ .awards-info ul {
+ margin-bottom: 1rem;
+ padding-left: 1.5rem;
+ }
+
+ .awards-info li {
+ margin-bottom: 0.5rem;
+ }
+
+ .awards-info code {
+ background-color: #e0e0e0;
+ padding: 0.2rem 0.4rem;
+ border-radius: 3px;
+ font-family: monospace;
+ font-size: 0.9rem;
+ }
+
@media (max-width: 768px) {
.users-header {
flex-direction: column;
diff --git a/src/frontend/src/routes/admin/awards/+page.svelte b/src/frontend/src/routes/admin/awards/+page.svelte
new file mode 100644
index 0000000..0a8063d
--- /dev/null
+++ b/src/frontend/src/routes/admin/awards/+page.svelte
@@ -0,0 +1,379 @@
+
+
+{#if loading && awards.length === 0}
+
Loading award definitions...
+{:else if error}
+
{error}
+{:else}
+
+
+
+
+
+
+
+
+
+
+
+
+ | ID |
+ Name |
+ Category |
+ Rule Type |
+ Target |
+ Actions |
+
+
+
+ {#each filteredAwards as award}
+
+ | {award.id} |
+
+
+ {award.name}
+ {award.description}
+
+ |
+
+
+ {award.category}
+
+ |
+ {getRuleTypeDisplayName(award.rules.type)} |
+ {award.rules.target || '-'} |
+
+ Edit
+ View
+
+ |
+
+ {/each}
+
+
+
+
+
Showing {filteredAwards.length} award(s)
+
+{/if}
+
+
diff --git a/src/frontend/src/routes/admin/awards/[id]/+page.svelte b/src/frontend/src/routes/admin/awards/[id]/+page.svelte
new file mode 100644
index 0000000..ea9b8a5
--- /dev/null
+++ b/src/frontend/src/routes/admin/awards/[id]/+page.svelte
@@ -0,0 +1,1162 @@
+
+
+{#if loading}
+
Loading award definition...
+{:else}
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
+ {#if validationErrors.length > 0}
+
+
Please fix the following errors:
+
+ {#each validationErrors as err}
+ - {err}
+ {/each}
+
+
+ {/if}
+
+ {#if validationWarnings.length > 0}
+
+
Warnings:
+
+ {#each validationWarnings as warn}
+ - {warn}
+ {/each}
+
+
+ {/if}
+
+
+
+
+
+
+
+
+ {#if activeTab === 'basic'}
+
+ {/if}
+
+ {#if activeTab === 'modeGroups'}
+
+ {/if}
+
+ {#if activeTab === 'rules'}
+
+ {/if}
+
+
+
+ {#if showTestModal && awardId}
+
showTestModal = false}
+ />
+ {/if}
+{/if}
+
+
diff --git a/src/frontend/src/routes/admin/awards/components/FilterBuilder.svelte b/src/frontend/src/routes/admin/awards/components/FilterBuilder.svelte
new file mode 100644
index 0000000..8c5d15d
--- /dev/null
+++ b/src/frontend/src/routes/admin/awards/components/FilterBuilder.svelte
@@ -0,0 +1,483 @@
+
+
+
+ {#if !filters || !filters.filters || filters.filters.length === 0}
+
+
No filters defined. All QSOs will be evaluated.
+
+
+ {:else}
+
+
+
+
+ {#each filters.filters as filter, index}
+
+ {#if isFilterGroup(filter)}
+
+
+
+ updateFilter(index, nested)} />
+
+ {:else}
+
+
+
+
+
+
+
+ {#if getInputType(filter.field, filter.operator) === 'band'}
+
+ {:else if getInputType(filter.field, filter.operator) === 'band-multi'}
+
+ {#each BAND_OPTIONS as band}
+
+ {/each}
+
+ {:else if getInputType(filter.field, filter.operator) === 'mode'}
+
+ {:else if getInputType(filter.field, filter.operator) === 'mode-multi'}
+
+ {#each MODE_OPTIONS as mode}
+
+ {/each}
+
+ {:else if getInputType(filter.field, filter.operator) === 'boolean'}
+
+ {:else if getInputType(filter.field, filter.operator) === 'text-array'}
+
{
+ const values = e.target.value.split(',').map(v => v.trim()).filter(v => v);
+ updateFilter(index, 'value', values);
+ }}
+ />
+ {:else}
+
updateFilter(index, 'value', filter.value)}
+ />
+ {/if}
+
+
+
+
+ {/if}
+
+ {/each}
+
+
+
+
+
+
+
+
+ {/if}
+
+
+
diff --git a/src/frontend/src/routes/admin/awards/components/TestAwardModal.svelte b/src/frontend/src/routes/admin/awards/components/TestAwardModal.svelte
new file mode 100644
index 0000000..76a02a0
--- /dev/null
+++ b/src/frontend/src/routes/admin/awards/components/TestAwardModal.svelte
@@ -0,0 +1,836 @@
+
+
+{#if logicValidation || testResult || testError}
+
+
+
+
+
+
+ {#if logicValidation && (logicValidation.errors.length > 0 || logicValidation.warnings.length > 0 || logicValidation.info.length > 0)}
+
+
Logic Validation
+
+ {#if logicValidation.errors.length > 0}
+
+
Errors (must fix)
+
+ {#each logicValidation.errors as err}
+ - {err}
+ {/each}
+
+
+ {/if}
+
+ {#if logicValidation.warnings.length > 0}
+
+
Warnings
+
+ {#each logicValidation.warnings as warn}
+ - {warn}
+ {/each}
+
+
+ {/if}
+
+ {#if logicValidation.info.length > 0}
+
+
Suggestions
+
+ {#each logicValidation.info as info}
+ - {info}
+ {/each}
+
+
+ {/if}
+
+ {#if logicValidation.errors.length === 0 && logicValidation.warnings.length === 0}
+
+
No issues found. The award definition looks good!
+
+ {/if}
+
+ {/if}
+
+
+
+
Test Calculation
+
Select a user to test the award calculation with their QSO data.
+
+
+
+
+
+
+
+
+
+
+ {#if testError}
+
+
Test Failed
+
{testError}
+
+ {:else if testResult}
+
+
Test Results
+
+
+
+ Award:
+ {testResult.award?.name || awardId}
+
+
+ Worked:
+ {testResult.worked || 0}
+
+
+ Confirmed:
+ {testResult.confirmed || 0}
+
+
+ Target:
+ {testResult.target || 0}
+
+
+ Progress:
+ {testResult.percentage || 0}%
+
+
+
+ {#if testResult.warnings && testResult.warnings.length > 0}
+
+
Warnings:
+
+ {#each testResult.warnings as warning}
+ - {warning}
+ {/each}
+
+
+ {/if}
+
+ {#if testResult.sampleEntities && testResult.sampleEntities.length > 0}
+
+
Sample Matched Entities (first {testResult.sampleEntities.length}):
+
+ {#each testResult.sampleEntities as entity}
+ {entity}
+ {/each}
+
+
+ {:else}
+
+
No entities matched. Check filters and band/mode restrictions.
+
+ {/if}
+
+ {/if}
+
+
+
+
+
+{/if}
+
+
diff --git a/src/frontend/src/routes/admin/awards/create/+page.svelte b/src/frontend/src/routes/admin/awards/create/+page.svelte
new file mode 100644
index 0000000..1ba3d11
--- /dev/null
+++ b/src/frontend/src/routes/admin/awards/create/+page.svelte
@@ -0,0 +1,1187 @@
+
+
+{#if loading}
+ Loading award definition...
+{:else}
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
+ {#if validationErrors.length > 0}
+
+
Please fix the following errors:
+
+ {#each validationErrors as err}
+ - {err}
+ {/each}
+
+
+ {/if}
+
+ {#if validationWarnings.length > 0}
+
+
Warnings:
+
+ {#each validationWarnings as warn}
+ - {warn}
+ {/each}
+
+
+ {/if}
+
+
+
+
+
+
+
+
+ {#if activeTab === 'basic'}
+
+ {/if}
+
+ {#if activeTab === 'modeGroups'}
+
+ {/if}
+
+ {#if activeTab === 'rules'}
+
+ {/if}
+
+
+
+ {#if showTestModal && (formData.id || awardId)}
+ showTestModal = false}
+ />
+ {/if}
+{/if}
+
+