Redesign automations UI with card-based flow and improved UX#1655
Redesign automations UI with card-based flow and improved UX#1655karinakharchenko wants to merge 8 commits intorocket-admin:mainfrom
Conversation
Replace dropdown-based rule creation with visual WHEN/THEN card layout. Add colored trigger pills with glow states, action method pills, and unified styling for both creation and edit views. Save button shows "Saved" state after successful save. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gger/action bugs - Match footer pattern with widgets (routerLink Back, proper shadow/padding per theme) - Improve dark theme: lighter action pill borders/text, lighter hover states, muted trigger colors - Improve light theme: muted trigger colors when inactive, darker affects-tab text - Fix Single/Multiple row buttons not clickable (use setCustomEventType method) - Fix action card staying visible when all triggers deselected - Unify icon-picker: show "Add icon"/"Change icon" with dashed border - Align icon-picker and checkbox vertically with input fields - Style WHEN/THEN badges: larger text, thinner/lighter background Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Save button always active (disabled only when submitting or already saved) - On click, validates top-to-bottom: name → triggers → action fields - Focuses empty input or opens empty select on validation failure - Highlights trigger card with pulse animation when no triggers selected - Remove required attributes to avoid red error styling on focus Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rule in list - Remove icon-button from sidebar header - Add dashed "Add automation" button below saved rules list - Show "New automation" placeholder in sidebar when creating - Sync name input with sidebar title in real-time - Clean up unsaved rule from list on Back/undo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
There was a problem hiding this comment.
Pull request overview
This PR redesigns the database table “Automations” page to a card-based WHEN/THEN flow with trigger/action pills, improved empty/creation states, and a refreshed icon picker button UX.
Changes:
- Reworked automations editor UI into a two-step (Triggers → Action) card layout with pill/tile interactions and a fixed bottom action bar.
- Updated save UX/state tracking (e.g.,
isSaved, creation mode) and added inline focus/scroll guidance for missing required inputs. - Updated the icon picker trigger button to display “Add icon” / “Change icon” with a unified button style.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| frontend/src/app/components/ui-components/icon-picker/icon-picker.component.html | Simplifies icon-picker trigger into a single button that changes label based on whether an icon is selected. |
| frontend/src/app/components/dashboard/db-table-view/db-table-actions/db-table-actions.component.ts | Adds creation/edit state, trigger/action selection helpers, and inline validation/focus logic for the new UI flow. |
| frontend/src/app/components/dashboard/db-table-view/db-table-actions/db-table-actions.component.html | Replaces the old dropdown-based form with a card-based WHEN/THEN flow and sidebar “Add automation” behavior. |
| frontend/src/app/components/dashboard/db-table-view/db-table-actions/db-table-actions.component.css | Introduces new styling for the card flow, pills, and fixed action bar (dark/light scheme support). |
Comments suppressed due to low confidence (1)
frontend/src/app/components/dashboard/db-table-view/db-table-actions/db-table-actions.component.ts:548
- After switching to the new trigger-pill UI, several legacy methods/properties appear unused by the template (
handleAddNewRule(),onEventChange(),removeEvent(), and related “trailing null” handling). Leaving both the new flow and the old form logic in place increases complexity and the risk of state bugs. Consider removing the unused legacy paths or explicitly isolating them behind a feature flag to keep the component’s state model single-purpose.
onEventChange(event: any) {
console.log(this.selectedRule.events);
this.selectedEvents.push(event.value);
let customEvent = this.selectedRule.events.find((event) => event.event === EventType.Custom);
if (event.value === EventType.Custom) {
customEvent = {
...customEvent,
title: '',
type: CustomActionType.Single,
icon: '',
require_confirmation: false,
};
// this.selectedRule.events.push(customEvent);
this.selectedRuleCustomEvent = customEvent;
}
if (this.selectedRule.events.length < 4) {
this.selectedRule.events.push({ event: null });
}
}
toggleTriggerTile(eventType: EventType) {
this.isSaved = false;
const idx = this.selectedEvents.indexOf(eventType);
if (idx > -1) {
// Remove trigger
this.selectedRule.events = this.selectedRule.events.filter((e) => e.event !== eventType);
this.selectedEvents = this.selectedEvents.filter((e) => e !== eventType);
if (eventType === EventType.Custom) {
this.selectedRuleCustomEvent = null;
}
} else {
// Add trigger
this.selectedRule.events = this.selectedRule.events.filter((e) => e.event !== null);
this.selectedRule.events.push({ event: eventType });
this.selectedEvents.push(eventType);
if (eventType === EventType.Custom) {
this.selectedRuleCustomEvent = {
event: EventType.Custom,
title: '',
type: CustomActionType.Single,
icon: '',
require_confirmation: false,
};
}
}
// Keep trailing null for the old form
if (this.selectedRule.events[this.selectedRule.events.length - 1]?.event !== null) {
this.selectedRule.events.push({ event: null });
}
}
selectActionMethod(method: string) {
this.isSaved = false;
if (this.selectedRule.table_actions.length) {
this.selectedRule.table_actions[0].method = method as CustomActionMethod;
}
}
removeEvent(event: any) {
this.selectedRule.events = this.selectedRule.events.filter((e) => e.event !== event);
this.selectedEvents = this.selectedRule.events.map((event) => event.event);
if (event === EventType.Custom) {
this.selectedRuleCustomEvent = null;
}
if (this.selectedRule.events.length === 3) {
this.selectedRule.events.push({ event: null });
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; | ||
| import { FormsModule } from '@angular/forms'; | ||
| import { MatButtonModule } from '@angular/material/button'; | ||
| import { MatCheckboxModule } from '@angular/material/checkbox'; | ||
| import { MatDialog, MatDialogModule } from '@angular/material/dialog'; | ||
| import { MatIconModule } from '@angular/material/icon'; | ||
| import { MatInputModule } from '@angular/material/input'; | ||
| import { MatButtonToggleModule } from '@angular/material/button-toggle'; | ||
| import { MatListModule } from '@angular/material/list'; | ||
| import { MatMenuModule } from '@angular/material/menu'; | ||
| import { MatRadioModule } from '@angular/material/radio'; | ||
| import { MatSelectModule } from '@angular/material/select'; | ||
| import { MatSidenavModule } from '@angular/material/sidenav'; |
There was a problem hiding this comment.
There are several newly introduced members/imports that appear unused in this component after the UI refactor (e.g. @ViewChild('newActionInput'), MatButtonToggleModule, MatMenuModule, and MatRadioModule). Keeping unused ViewChild queries and module imports makes the component harder to maintain and can confuse future refactors. Remove unused imports/fields or wire them up if they’re intended for upcoming work.
| <div class="action-methods"> | ||
| <button type="button" class="action-method-pill" | ||
| [class.action-method-pill--active]="$any(selectedRule.table_actions[0]).method === 'URL'" | ||
| (click)="selectActionMethod('URL')"> | ||
| <mat-icon class="action-method-pill__icon">link</mat-icon> URL webhook | ||
| </button> | ||
| <button type="button" class="action-method-pill" | ||
| [class.action-method-pill--active]="$any(selectedRule.table_actions[0]).method === 'EMAIL'" | ||
| (click)="selectActionMethod('EMAIL')"> | ||
| <mat-icon class="action-method-pill__icon">email</mat-icon> Email | ||
| </button> | ||
| <button type="button" class="action-method-pill" | ||
| [class.action-method-pill--active]="$any(selectedRule.table_actions[0]).method === 'SLACK'" | ||
| (click)="selectActionMethod('SLACK')"> | ||
| <mat-icon class="action-method-pill__icon">tag</mat-icon> Slack | ||
| </button> | ||
| </div> | ||
|
|
||
| <div class="custom-event__row"> | ||
| <mat-radio-group | ||
| name="action-type" | ||
| [(ngModel)]="selectedRuleCustomEvent.type"> | ||
| <mat-label>Affects</mat-label> | ||
| <mat-radio-button value='single' class="radio-button_first" checked> | ||
| Single row | ||
| </mat-radio-button> | ||
| <mat-radio-button value='multiple' class="radio-button_second"> | ||
| Multiple rows | ||
| </mat-radio-button> | ||
| </mat-radio-group> | ||
| <div class="action-fields"> | ||
| @if ($any(selectedRule.table_actions[0]).method === 'URL') { | ||
| <mat-form-field appearance="outline" class="action-fields__input"> | ||
| <mat-label>Webhook URL</mat-label> | ||
| <input matInput [(ngModel)]="selectedRule.table_actions[0].url" name="action-url"> | ||
| </mat-form-field> |
There was a problem hiding this comment.
This template assumes selectedRule.table_actions[0] always exists (e.g. $any(selectedRule.table_actions[0]).method and bindings to [0].url/emails/slack_url). If a rule is loaded with an empty table_actions array, change detection will throw when reading .method from undefined and the page will break. Consider guarding this block with an *ngIf/@if on selectedRule.table_actions?.length, or initializing a default action when selecting/creating a rule so [0] is always defined.
| setSelectedRule(rule: Rule) { | ||
| this.selectedRule = rule; | ||
| this.selectedRuleTitle = rule.title; | ||
| this.isCreationMode = !rule.id; | ||
| if (this.selectedRule.events[this.selectedRule.events.length - 1].event !== null) | ||
| this.selectedRule.events.push({ event: null }); | ||
| this.selectedEvents = this.selectedRule.events.map((event) => event.event); |
There was a problem hiding this comment.
isSaved isn't reset when switching rules via setSelectedRule(). After saving one rule (isSaved = true), selecting a different rule will still show a disabled “Saved” button until the user edits something, which is misleading and can block an immediate save. Reset isSaved (and potentially other creation-state flags) inside setSelectedRule() and/or addNewRule() so each rule starts in a consistent state.
| addRule() { | ||
| this.submitting = true; | ||
| if (this.selectedRuleTitle) this.selectedRule.title = this.selectedRuleTitle; | ||
| this.newRule = null; | ||
| this.selectedRule.events = this.selectedRule.events.filter((event) => event.event !== null); | ||
| this.selectedRule.events = this.selectedRule.events.map((event) => { |
There was a problem hiding this comment.
In addRule(), this.newRule is cleared before the save request completes. If the request fails, the unsaved placeholder rule remains in rules but can no longer be undone via undoRule() (which relies on newRule), leaving the UI in a stuck creation state. Consider only clearing newRule (and toggling isCreationMode) after a successful save, or keeping a separate reference so cancel/undo still works on failure.
…WHEN/THEN badges inside cards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Test plan
🤖 Generated with Claude Code