From 49e57f8c6175c8fa30651c16daff2b644a92182b Mon Sep 17 00:00:00 2001
From: William Newman <3382274+newmanw@users.noreply.github.com>
Date: Mon, 27 Jul 2026 14:39:40 -0600
Subject: [PATCH] fix(web-app): show default map icon for forms without select
fields
The Default symbology card was only rendered when a form had select
fields and a primary field was chosen, making the default icon
unreachable otherwise. It now always renders and displays the actual
uploaded icon via getIconUrl instead of a static placeholder.
---
.../form-details/form-details.component.html | 64 ++++++------
.../form-details/form-details.component.scss | 5 +-
.../form-details.component.spec.ts | 98 +++++++++++++++++++
3 files changed, 131 insertions(+), 36 deletions(-)
diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html
index 8fc4dfcc3..b7dad3988 100644
--- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html
+++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.html
@@ -162,51 +162,45 @@
-
-
place
-
- Default
- No symbology configured
+
+
-
+
+
+
+
![icon]()
+
+
+ place
+
+
+
+
+
info
No Select fields available
-
Add a Select field to the form to configure map symbology options.
+
Add a Select field to the form to configure additional map symbology options.
0">
-
-
-
-
-
Primary Field
diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss
index a3b086f48..e83b235c1 100644
--- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss
+++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.scss
@@ -414,6 +414,7 @@
.default-symbology-card {
margin-bottom: 16px;
+ padding-bottom: 8px;
display: inline-flex;
flex-direction: column;
min-width: 220px;
@@ -441,11 +442,13 @@
.symbology-variant-item {
min-width: 220px;
flex-shrink: 0;
+ padding-bottom: 8px;
}
.variant-card-header {
display: flex;
align-items: center;
+ gap: 8px;
padding: 4px 4px 0 12px;
}
@@ -472,7 +475,7 @@
}
.icon-preview {
- height: 48px;
+ height: 36px;
display: flex;
align-items: center;
justify-content: center;
diff --git a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts
index 6deb2f700..94a9a785d 100644
--- a/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts
+++ b/web-app/src/app/admin/admin-event/admin-event-form/form-details/form-details.component.spec.ts
@@ -451,6 +451,104 @@ describe('FormDetailsComponent', () => {
});
});
+ describe('getDropdownFields', () => {
+ it('returns empty array when form has no fields', () => {
+ component.form = { name: 'Test Form', color: '#ff0000', fields: [] };
+
+ expect(component.getDropdownFields()).toEqual([]);
+ });
+
+ it('excludes non-dropdown fields', () => {
+ component.form = {
+ name: 'Test Form',
+ color: '#ff0000',
+ fields: [
+ { name: 'notes', type: 'textfield' } as any,
+ { name: 'count', type: 'numberfield' } as any
+ ]
+ };
+
+ expect(component.getDropdownFields()).toEqual([]);
+ });
+
+ it('includes dropdown and userDropdown fields, excluding multiselect and archived', () => {
+ const dropdown = { name: 'status', type: 'dropdown' } as any;
+ const userDropdown = { name: 'assignee', type: 'userDropdown' } as any;
+ const multiselect = {
+ name: 'tags',
+ type: 'dropdown',
+ multiselect: true
+ } as any;
+ const archived = {
+ name: 'old',
+ type: 'dropdown',
+ archived: true
+ } as any;
+
+ component.form = {
+ name: 'Test Form',
+ color: '#ff0000',
+ fields: [dropdown, userDropdown, multiselect, archived]
+ };
+
+ expect(component.getDropdownFields()).toEqual([dropdown, userDropdown]);
+ });
+
+ it('excludes the field named by excludeField', () => {
+ const primary = { name: 'status', type: 'dropdown' } as any;
+ const secondary = { name: 'severity', type: 'dropdown' } as any;
+
+ component.form = {
+ name: 'Test Form',
+ color: '#ff0000',
+ fields: [primary, secondary]
+ };
+
+ expect(component.getDropdownFields('status')).toEqual([secondary]);
+ });
+ });
+
+ describe('getIconUrl', () => {
+ it('returns null when no icons have been cached', () => {
+ expect(component.getIconUrl('')).toBeNull();
+ });
+
+ it('returns the form-wide default icon for an empty primary', () => {
+ (component as any).iconCache = { icon: 'default-icon-url' };
+
+ expect(component.getIconUrl('')).toBe('default-icon-url');
+ });
+
+ it('returns the icon for a primary choice', () => {
+ (component as any).iconCache = {
+ Active: { icon: 'active-icon-url' },
+ icon: 'default-icon-url'
+ };
+
+ expect(component.getIconUrl('Active')).toBe('active-icon-url');
+ });
+
+ it('returns the icon for a primary/variant combination', () => {
+ (component as any).iconCache = {
+ Active: { High: 'active-high-icon-url', icon: 'active-icon-url' }
+ };
+
+ expect(component.getIconUrl('Active', 'High')).toBe(
+ 'active-high-icon-url'
+ );
+ });
+
+ it('falls back to the primary icon when the variant has no icon', () => {
+ (component as any).iconCache = {
+ Active: { icon: 'active-icon-url' }
+ };
+
+ expect(component.getIconUrl('Active', 'High')).toBe(
+ 'active-icon-url'
+ );
+ });
+ });
+
describe('exportForm', () => {
let mockAnchor: any;