Skip to content

Commit e7cb390

Browse files
committed
Frontend: feature, settings, hub and portal updates
Refactor notification settings with recipient picker UI. Improve accessibility across feature components, settings pages, hub and portal sections. Fix various a11y issues in testing, time tracking, actions, and collection components.
1 parent cd35420 commit e7cb390

56 files changed

Lines changed: 372 additions & 261 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/src/lib/features/actions/ActionFlowEditor.svelte

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -470,8 +470,9 @@
470470
<div class="space-y-4">
471471
{#if selectedNode.type === 'trigger'}
472472
<div>
473-
<label class="block text-xs font-medium mb-1">{t('actions.config.triggerType')}</label>
473+
<label for="config-trigger-type" class="block text-xs font-medium mb-1">{t('actions.config.triggerType')}</label>
474474
<select
475+
id="config-trigger-type"
475476
class="w-full px-3 py-2 border rounded-md text-sm config-input"
476477
value={selectedNode.data?.triggerType || action?.trigger_type || 'status_transition'}
477478
onchange={handleTriggerTypeChange}
@@ -483,8 +484,9 @@
483484
</div>
484485
{#if (selectedNode.data?.triggerType || action?.trigger_type) === 'status_transition'}
485486
<div>
486-
<label class="block text-xs font-medium mb-1">{t('actions.config.fromStatus')}</label>
487+
<label for="config-from-status" class="block text-xs font-medium mb-1">{t('actions.config.fromStatus')}</label>
487488
<select
489+
id="config-from-status"
488490
class="w-full px-3 py-2 border rounded-md text-sm config-input"
489491
value={selectedNode.data?.config?.from_status_id || ''}
490492
onchange={handleFromStatusChange}
@@ -496,8 +498,9 @@
496498
</select>
497499
</div>
498500
<div>
499-
<label class="block text-xs font-medium mb-1">{t('actions.config.toStatus')}</label>
501+
<label for="config-to-status" class="block text-xs font-medium mb-1">{t('actions.config.toStatus')}</label>
500502
<select
503+
id="config-to-status"
501504
class="w-full px-3 py-2 border rounded-md text-sm config-input"
502505
value={selectedNode.data?.config?.to_status_id || ''}
503506
onchange={handleToStatusChange}
@@ -520,8 +523,9 @@
520523
</div>
521524
{:else if selectedNode.type === 'set_status'}
522525
<div>
523-
<label class="block text-xs font-medium mb-1">{t('actions.config.targetStatus')}</label>
526+
<label for="config-target-status" class="block text-xs font-medium mb-1">{t('actions.config.targetStatus')}</label>
524527
<select
528+
id="config-target-status"
525529
class="w-full px-3 py-2 border rounded-md text-sm config-input"
526530
value={selectedNode.data?.config?.status_id || ''}
527531
onchange={handleTargetStatusChange}
@@ -534,7 +538,7 @@
534538
</div>
535539
{:else if selectedNode.type === 'set_field'}
536540
<div>
537-
<label class="block text-xs font-medium mb-1">{t('actions.config.fieldName')}</label>
541+
<label for="config-set-field-name" class="block text-xs font-medium mb-1">{t('actions.config.fieldName')}</label>
538542
<FieldSelector
539543
selectedField={selectedNode.data?.config?.field_name ? { id: selectedNode.data.config.field_name, name: selectedNode.data.config.field_name } : null}
540544
onselect={handleSetFieldSelect}
@@ -543,7 +547,7 @@
543547
</div>
544548
<div>
545549
<div class="flex items-center gap-1 mb-1">
546-
<label class="block text-xs font-medium">{t('actions.config.value')}</label>
550+
<label for="config-set-field-value" class="block text-xs font-medium">{t('actions.config.value')}</label>
547551
<button
548552
onclick={() => showPlaceholderModal = true}
549553
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"
@@ -553,6 +557,7 @@
553557
</button>
554558
</div>
555559
<input
560+
id="config-set-field-value"
556561
type="text"
557562
class="w-full px-3 py-2 border rounded-md text-sm config-input"
558563
value={selectedNode.data?.config?.value || ''}
@@ -563,7 +568,7 @@
563568
{:else if selectedNode.type === 'add_comment'}
564569
<div>
565570
<div class="flex items-center gap-1 mb-1">
566-
<label class="block text-xs font-medium">{t('actions.config.commentContent')}</label>
571+
<label for="config-comment-content" class="block text-xs font-medium">{t('actions.config.commentContent')}</label>
567572
<button
568573
onclick={() => showPlaceholderModal = true}
569574
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"
@@ -573,6 +578,7 @@
573578
</button>
574579
</div>
575580
<textarea
581+
id="config-comment-content"
576582
class="w-full px-3 py-2 border rounded-md text-sm config-input"
577583
rows="4"
578584
value={selectedNode.data?.config?.content || ''}
@@ -588,16 +594,17 @@
588594
/>
589595
{:else if selectedNode.type === 'condition'}
590596
<div>
591-
<label class="block text-xs font-medium mb-1">{t('actions.config.fieldToCheck')}</label>
597+
<label for="config-condition-field" class="block text-xs font-medium mb-1">{t('actions.config.fieldToCheck')}</label>
592598
<FieldSelector
593599
selectedField={selectedNode.data?.config?.field_name ? { id: selectedNode.data.config.field_name, name: selectedNode.data.config.field_name } : null}
594600
onselect={handleConditionFieldSelect}
595601
onclear={handleConditionFieldClear}
596602
/>
597603
</div>
598604
<div>
599-
<label class="block text-xs font-medium mb-1">{t('actions.config.operator')}</label>
605+
<label for="config-condition-operator" class="block text-xs font-medium mb-1">{t('actions.config.operator')}</label>
600606
<select
607+
id="config-condition-operator"
601608
class="w-full px-3 py-2 border rounded-md text-sm config-input"
602609
value={selectedNode.data?.config?.operator || 'eq'}
603610
onchange={handleOperatorChange}
@@ -612,8 +619,9 @@
612619
</select>
613620
</div>
614621
<div>
615-
<label class="block text-xs font-medium mb-1">{t('actions.config.compareValue')}</label>
622+
<label for="config-condition-value" class="block text-xs font-medium mb-1">{t('actions.config.compareValue')}</label>
616623
<input
624+
id="config-condition-value"
617625
type="text"
618626
class="w-full px-3 py-2 border rounded-md text-sm config-input"
619627
value={selectedNode.data?.config?.value || ''}
@@ -622,8 +630,9 @@
622630
</div>
623631
{:else if selectedNode.type === 'notify_user'}
624632
<div>
625-
<label class="block text-xs font-medium mb-1">{t('actions.config.recipientType')}</label>
633+
<label for="config-recipient-type" class="block text-xs font-medium mb-1">{t('actions.config.recipientType')}</label>
626634
<select
635+
id="config-recipient-type"
627636
class="w-full px-3 py-2 border rounded-md text-sm config-input"
628637
value={selectedNode.data?.config?.recipient_type || 'assignee'}
629638
onchange={handleRecipientTypeChange}
@@ -635,7 +644,7 @@
635644
</div>
636645
<div>
637646
<div class="flex items-center gap-1 mb-1">
638-
<label class="block text-xs font-medium">{t('actions.config.notifyMessage')}</label>
647+
<label for="config-notify-message" class="block text-xs font-medium">{t('actions.config.notifyMessage')}</label>
639648
<button
640649
onclick={() => showPlaceholderModal = true}
641650
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"
@@ -645,6 +654,7 @@
645654
</button>
646655
</div>
647656
<textarea
657+
id="config-notify-message"
648658
class="w-full px-3 py-2 border rounded-md text-sm config-input"
649659
rows="4"
650660
value={selectedNode.data?.config?.message || ''}

frontend/src/lib/features/actions/ActionsSettings.svelte

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@
200200

201201
<div class="space-y-4">
202202
<div>
203-
<label class="block text-sm font-medium mb-1 modal-label">{t('common.name')}</label>
203+
<label for="action-name" class="block text-sm font-medium mb-1 modal-label">{t('common.name')}</label>
204204
<input
205+
id="action-name"
205206
type="text"
206207
class="w-full px-3 py-2 border rounded-md text-sm modal-input"
207208
bind:value={newActionName}
@@ -210,8 +211,9 @@
210211
</div>
211212

212213
<div>
213-
<label class="block text-sm font-medium mb-1 modal-label">{t('common.description')}</label>
214+
<label for="action-description" class="block text-sm font-medium mb-1 modal-label">{t('common.description')}</label>
214215
<textarea
216+
id="action-description"
215217
class="w-full px-3 py-2 border rounded-md text-sm modal-input"
216218
rows="2"
217219
bind:value={newActionDescription}

frontend/src/lib/features/actions/CreateAssetConfigPanel.svelte

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,9 @@
165165
<div class="space-y-4">
166166
<!-- Step 1: Select asset set -->
167167
<div>
168-
<label class="block text-xs font-medium mb-1">{t('actions.config.assetSet')}</label>
168+
<label for="asset-set" class="block text-xs font-medium mb-1">{t('actions.config.assetSet')}</label>
169169
<select
170+
id="asset-set"
170171
class="w-full px-3 py-2 border rounded-md text-sm config-input"
171172
value={selectedNode.data?.config?.asset_set_id || ''}
172173
onchange={handleAssetSetChange}
@@ -182,8 +183,9 @@
182183
<!-- Step 2: Select asset type -->
183184
{#if selectedNode.data?.config?.asset_set_id}
184185
<div>
185-
<label class="block text-xs font-medium mb-1">{t('actions.config.targetAssetType')}</label>
186+
<label for="asset-type" class="block text-xs font-medium mb-1">{t('actions.config.targetAssetType')}</label>
186187
<select
188+
id="asset-type"
187189
class="w-full px-3 py-2 border rounded-md text-sm config-input"
188190
value={selectedNode.data?.config?.asset_type_id || ''}
189191
onchange={handleAssetTypeChange}
@@ -202,7 +204,7 @@
202204
<!-- Title -->
203205
<div class="mb-3">
204206
<div class="flex items-center gap-1 mb-1">
205-
<label class="block text-xs font-medium">{t('actions.config.assetTitle')}</label>
207+
<label for="asset-title" class="block text-xs font-medium">{t('actions.config.assetTitle')}</label>
206208
<span class="text-red-500 text-xs">*</span>
207209
<button
208210
onclick={() => showPlaceholderModal = true}
@@ -213,6 +215,7 @@
213215
</button>
214216
</div>
215217
<input
218+
id="asset-title"
216219
type="text"
217220
class="w-full px-3 py-2 border rounded-md text-sm config-input"
218221
value={selectedNode.data?.config?.title || ''}
@@ -225,7 +228,7 @@
225228
<!-- Description -->
226229
<div class="mb-3">
227230
<div class="flex items-center gap-1 mb-1">
228-
<label class="block text-xs font-medium">{t('actions.config.assetDescription')}</label>
231+
<label for="asset-description" class="block text-xs font-medium">{t('actions.config.assetDescription')}</label>
229232
<button
230233
onclick={() => showPlaceholderModal = true}
231234
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"
@@ -235,6 +238,7 @@
235238
</button>
236239
</div>
237240
<textarea
241+
id="asset-description"
238242
class="w-full px-3 py-2 border rounded-md text-sm config-input"
239243
rows="2"
240244
value={selectedNode.data?.config?.description || ''}
@@ -246,7 +250,7 @@
246250
<!-- Asset Tag -->
247251
<div class="mb-3">
248252
<div class="flex items-center gap-1 mb-1">
249-
<label class="block text-xs font-medium">{t('actions.config.assetTagLabel')}</label>
253+
<label for="asset-tag" class="block text-xs font-medium">{t('actions.config.assetTagLabel')}</label>
250254
<button
251255
onclick={() => showPlaceholderModal = true}
252256
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"
@@ -256,6 +260,7 @@
256260
</button>
257261
</div>
258262
<input
263+
id="asset-tag"
259264
type="text"
260265
class="w-full px-3 py-2 border rounded-md text-sm config-input"
261266
value={selectedNode.data?.config?.asset_tag || ''}
@@ -266,8 +271,9 @@
266271
267272
<!-- Category -->
268273
<div class="mb-3">
269-
<label class="block text-xs font-medium mb-1">{t('actions.config.assetCategory')}</label>
274+
<label for="asset-category" class="block text-xs font-medium mb-1">{t('actions.config.assetCategory')}</label>
270275
<select
276+
id="asset-category"
271277
class="w-full px-3 py-2 border rounded-md text-sm config-input"
272278
value={selectedNode.data?.config?.category_id || ''}
273279
onchange={handleCategoryChange}
@@ -281,8 +287,9 @@
281287
282288
<!-- Status -->
283289
<div class="mb-3">
284-
<label class="block text-xs font-medium mb-1">{t('actions.config.assetStatus')}</label>
290+
<label for="asset-status" class="block text-xs font-medium mb-1">{t('actions.config.assetStatus')}</label>
285291
<select
292+
id="asset-status"
286293
class="w-full px-3 py-2 border rounded-md text-sm config-input"
287294
value={selectedNode.data?.config?.status_id || ''}
288295
onchange={handleStatusChange}
@@ -298,7 +305,7 @@
298305
<!-- Step 4: Field mappings -->
299306
<div class="pt-2 border-t" style="border-color: var(--ds-border);">
300307
<div class="flex items-center justify-between mb-2">
301-
<label class="block text-xs font-medium">{t('actions.config.fieldMappingsLabel')}</label>
308+
<span class="block text-xs font-medium">{t('actions.config.fieldMappingsLabel')}</span>
302309
<button
303310
onclick={() => showPlaceholderModal = true}
304311
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"

frontend/src/lib/features/actions/TestActionModal.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@
8080
</p>
8181
8282
<div class="mb-4">
83-
<label class="block text-sm font-medium mb-2" style="color: var(--ds-text);">
83+
<span class="block text-sm font-medium mb-2" style="color: var(--ds-text);">
8484
{t('actions.test.selectItem')}
85-
</label>
85+
</span>
8686
<ItemPicker
8787
bind:value={selectedItemId}
8888
{items}

frontend/src/lib/features/actions/UpdateAssetConfigPanel.svelte

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,9 @@
132132
<div class="space-y-4">
133133
<!-- Step 1: Select source asset field -->
134134
<div>
135-
<label class="block text-xs font-medium mb-1">{t('actions.config.sourceAssetField')}</label>
135+
<label for="source-asset-field" class="block text-xs font-medium mb-1">{t('actions.config.sourceAssetField')}</label>
136136
<select
137+
id="source-asset-field"
137138
class="w-full px-3 py-2 border rounded-md text-sm config-input"
138139
value={selectedNode.data?.config?.source_field_id || ''}
139140
onchange={handleSourceFieldChange}
@@ -150,8 +151,9 @@
150151
<!-- Step 2: Select target asset type -->
151152
{#if selectedNode.data?.config?.source_field_id}
152153
<div>
153-
<label class="block text-xs font-medium mb-1">{t('actions.config.targetAssetType')}</label>
154+
<label for="target-asset-type" class="block text-xs font-medium mb-1">{t('actions.config.targetAssetType')}</label>
154155
<select
156+
id="target-asset-type"
155157
class="w-full px-3 py-2 border rounded-md text-sm config-input"
156158
value={selectedNode.data?.config?.asset_type_id || ''}
157159
onchange={handleAssetTypeChange}
@@ -168,7 +170,7 @@
168170
{#if selectedNode.data?.config?.asset_type_id}
169171
<div class="pt-2 border-t" style="border-color: var(--ds-border);">
170172
<div class="flex items-center justify-between mb-2">
171-
<label class="block text-xs font-medium">{t('actions.config.fieldMappingsLabel')}</label>
173+
<span class="block text-xs font-medium">{t('actions.config.fieldMappingsLabel')}</span>
172174
<button
173175
onclick={() => showPlaceholderModal = true}
174176
class="text-[var(--ds-text-subtlest)] hover:text-[var(--ds-interactive)] transition-colors"

frontend/src/lib/features/assets/AssetBrowser.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@
597597
{#if selectedAsset}
598598
<div class="flex-shrink-0 flex flex-col relative" style="width: {assetPanelWidth}px; min-width: 280px; max-width: 600px; border-left: 1px solid var(--ds-border);">
599599
<!-- Resize handle -->
600+
<!-- svelte-ignore a11y_no_static_element_interactions -->
600601
<div
601602
class="absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize transition-colors z-10"
602603
style="background-color: transparent;"

frontend/src/lib/features/assets/ImagePreview.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
const dispatch = createEventDispatcher();
77
8+
/** @type {{ id: string, original_filename: string, file_size: number, mime_type: string, uploader_name: string, [key: string]: any } | null} */
89
export let attachment = null;
910
export let show = false;
1011
@@ -194,13 +195,19 @@
194195

195196
{#if show && attachment}
196197
<!-- Modal backdrop -->
198+
<!-- svelte-ignore a11y_click_events_have_key_events -->
199+
<!-- svelte-ignore a11y_no_static_element_interactions -->
197200
<div
198201
class="fixed inset-0 bg-black bg-opacity-90 z-50 flex items-center justify-center"
202+
role="presentation"
199203
onclick={close}
200204
>
201205
<!-- Modal content -->
206+
<!-- svelte-ignore a11y_click_events_have_key_events -->
207+
<!-- svelte-ignore a11y_no_static_element_interactions -->
202208
<div
203209
class="relative w-full h-full flex flex-col"
210+
role="presentation"
204211
onclick={(e) => e.stopPropagation()}
205212
>
206213
<!-- Header -->
@@ -277,6 +284,7 @@
277284
class:cursor-grabbing={isDragging}
278285
onwheel={handleWheel}
279286
>
287+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
280288
<img
281289
bind:this={imageElement}
282290
src={getImageUrl(attachment)}

frontend/src/lib/features/collections/CollectionMap.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ async function loadStatuses() {
787787
<div class="space-y-2">
788788
{#each childItemsByParent[backboneItem.id] || [] as childItem}
789789
{@const childItemType = getItemTypeInfo(childItem.item_type_id)}
790+
<!-- svelte-ignore a11y_no_static_element_interactions -->
790791
<div
791792
class="item-card rounded border p-3 cursor-move"
792793
style="box-shadow: var(--ds-shadow-raised); {styles.cardStyle(4)}"

frontend/src/lib/features/collections/CollectionsList.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
<!-- Workspace filter (only shown in workspace view) -->
232232
{#if isWorkspaceView}
233233
<div class="flex flex-wrap items-center gap-3 mb-6">
234-
<label class="text-sm font-medium" style="color: var(--ds-text-subtle);">{t('collections.workspaceFilter')}</label>
234+
<span class="text-sm font-medium" style="color: var(--ds-text-subtle);">{t('collections.workspaceFilter')}</span>
235235
<div class="min-w-[260px]">
236236
<WorkspaceSelector
237237
value={selectedWorkspaceFilter}

0 commit comments

Comments
 (0)