From 8ec2776a5f079e48bf9c4e3ab0f9b37d0d402098 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 27 Jun 2026 13:28:22 +0300 Subject: [PATCH] Record menu icons --- .../Resources/metadata/clientDefs/Case.json | 3 +- .../Resources/metadata/clientDefs/Task.json | 3 +- .../Resources/metadata/clientDefs/Global.json | 6 ++- .../crm/src/views/call/record/detail.js | 1 + .../crm/src/views/meeting/modals/detail.js | 3 ++ .../crm/src/views/meeting/record/detail.js | 1 + .../crm/src/views/task/record/detail.js | 1 + client/src/components/controls.ts | 2 +- client/src/ui/dialog.ts | 3 ++ client/src/views/email/record/detail.js | 8 ++++ client/src/views/modal.ts | 2 + client/src/views/modals/detail.js | 1 + client/src/views/record/detail.ts | 9 ++++ client/src/views/record/detail/buttons.ts | 1 + frontend/less/espo-rtl/custom.less | 9 ++-- frontend/less/espo/elements/dropdown.less | 44 +++++++++++++------ 16 files changed, 75 insertions(+), 22 deletions(-) diff --git a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json index a39ab5d335..c51781f4b3 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json +++ b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Case.json @@ -9,7 +9,8 @@ "label": "Close", "handler": "crm:handlers/case/detail-actions", "actionFunction": "close", - "checkVisibilityFunction": "isCloseAvailable" + "checkVisibilityFunction": "isCloseAvailable", + "iconClass": "fas fa-check" }, { "name": "reject", diff --git a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Task.json b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Task.json index 50ff0a4a54..42d7f3f814 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Task.json +++ b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Task.json @@ -40,7 +40,8 @@ "acl": "edit", "handler": "modules/crm/handlers/task/detail-actions", "actionFunction": "complete", - "checkVisibilityFunction": "isCompleteAvailable" + "checkVisibilityFunction": "isCompleteAvailable", + "iconClass": "fas fa-check" } ], "filterList": [ diff --git a/application/Espo/Resources/metadata/clientDefs/Global.json b/application/Espo/Resources/metadata/clientDefs/Global.json index 3b569149d5..2d595ba4c7 100644 --- a/application/Espo/Resources/metadata/clientDefs/Global.json +++ b/application/Espo/Resources/metadata/clientDefs/Global.json @@ -23,7 +23,8 @@ "handler": "handlers/record/lock-action", "checkVisibilityFunction": "canBeLocked", "actionFunction": "actionLock", - "groupIndex": 9 + "groupIndex": 9, + "iconClass": "fas fa-lock" }, { "name": "unlock", @@ -32,7 +33,8 @@ "handler": "handlers/record/lock-action", "checkVisibilityFunction": "canBeUnlocked", "actionFunction": "actionUnlock", - "groupIndex": 9 + "groupIndex": 9, + "iconClass": "fas fa-unlock" } ], "massActionList": [ diff --git a/client/modules/crm/src/views/call/record/detail.js b/client/modules/crm/src/views/call/record/detail.js index 1a2796bcbc..681da26899 100644 --- a/client/modules/crm/src/views/call/record/detail.js +++ b/client/modules/crm/src/views/call/record/detail.js @@ -52,6 +52,7 @@ export default class extends DetailRecordView { 'label': 'Set Held', 'name': 'setHeld', onClick: () => this.actionSetHeld(), + iconClass: 'fas fa-check', }); this.dropdownItemList.push({ diff --git a/client/modules/crm/src/views/meeting/modals/detail.js b/client/modules/crm/src/views/meeting/modals/detail.js index 9534441ccb..cd14194bf6 100644 --- a/client/modules/crm/src/views/meeting/modals/detail.js +++ b/client/modules/crm/src/views/meeting/modals/detail.js @@ -72,6 +72,7 @@ class MeetingModalDetailView extends DetailModalView { name: 'setHeld', text: this.translate('Set Held', 'labels', this.model.entityType), hidden: true, + iconClass: 'fas fa-check', }); this.addDropdownItem({ @@ -86,6 +87,8 @@ class MeetingModalDetailView extends DetailModalView { text: this.translate('Send Invitations', 'labels', 'Meeting'), hidden: !this.isSendInvitationsToBeDisplayed(), onClick: () => this.actionSendInvitations(), + groupIndex: 10, + iconClass: 'far fa-paper-plane', }); this.initAcceptanceStatus(); diff --git a/client/modules/crm/src/views/meeting/record/detail.js b/client/modules/crm/src/views/meeting/record/detail.js index 5aa05bf7ae..d5d1158d1f 100644 --- a/client/modules/crm/src/views/meeting/record/detail.js +++ b/client/modules/crm/src/views/meeting/record/detail.js @@ -59,6 +59,7 @@ class MeetingDetailRecordView extends DetailRecordView { 'label': 'Set Held', 'name': 'setHeld', onClick: () => this.actionSetHeld(), + iconClass: 'fas fa-check', }); this.dropdownItemList.push({ diff --git a/client/modules/crm/src/views/task/record/detail.js b/client/modules/crm/src/views/task/record/detail.js index 686d5d0e68..2a993ddce2 100644 --- a/client/modules/crm/src/views/task/record/detail.js +++ b/client/modules/crm/src/views/task/record/detail.js @@ -38,6 +38,7 @@ export default class TaskDetailRecordView extends DetailRecordView { label: 'Complete', name: 'setCompleted', onClick: () => this.actionSetCompleted(), + iconClass: 'fas fa-check', }); const historyStatusList = [ diff --git a/client/src/components/controls.ts b/client/src/components/controls.ts index ed76bd33e9..f1ecb29809 100644 --- a/client/src/components/controls.ts +++ b/client/src/components/controls.ts @@ -253,7 +253,7 @@ function prepareItemContent( icon = div.firstElementChild ? toVNode(div.firstElementChild) : null; } else if (options.iconClass) { - icon = h('span', {props: {className: options.iconClass}}); + icon = h('span', {props: {className: options.iconClass + ' item-icon'}}); } if (isDropdownItem) { diff --git a/client/src/ui/dialog.ts b/client/src/ui/dialog.ts index c71713ab8a..794341414d 100644 --- a/client/src/ui/dialog.ts +++ b/client/src/ui/dialog.ts @@ -82,6 +82,7 @@ export interface DialogButton { className?: string; title?: string; groupIndex?: number; + iconClass?: string; } /** @@ -797,6 +798,7 @@ class FooterComponent { disabled: it.disabled, text: it.text, html: it.html, + iconClass: it.iconClass, onClick: event => { if (it.onClick) { it.onClick(this.dialog, event); @@ -831,6 +833,7 @@ class FooterComponent { disabled: it.disabled, text: it.text, html: it.html, + iconClass: it.iconClass, onClick: event => { if (it.onClick) { it.onClick(this.dialog, event); diff --git a/client/src/views/email/record/detail.js b/client/src/views/email/record/detail.js index c78ef7f014..3393e41f85 100644 --- a/client/src/views/email/record/detail.js +++ b/client/src/views/email/record/detail.js @@ -124,6 +124,7 @@ class EmailDetailRecordView extends DetailRecordView { name: 'markAsImportant', hidden: this.model.get('isImportant'), groupIndex: 1, + iconClass: 'far fa-star', }); this.addDropdownItem({ @@ -138,6 +139,7 @@ class EmailDetailRecordView extends DetailRecordView { name: 'moveToTrash', hidden: this.isInTrash(), groupIndex: 2, + iconClass: 'far fa-trash-can', }); this.addDropdownItem({ @@ -153,12 +155,14 @@ class EmailDetailRecordView extends DetailRecordView { groupIndex: 2, hidden: this.isInArchive(), onClick: () => this.actionMoveToArchive(), + iconClass: 'far fa-caret-square-down', }); this.addDropdownItem({ label: 'Move to Folder', name: 'moveToFolder', groupIndex: 2, + iconClass: 'far fa-folder', }); } else if (this.model.attributes.groupFolderId) { this.addDropdownItem({ @@ -166,6 +170,7 @@ class EmailDetailRecordView extends DetailRecordView { name: 'moveToTrash', hidden: this.isInTrash(), groupIndex: 2, + iconClass: 'far fa-trash-can', }); this.addDropdownItem({ @@ -181,6 +186,7 @@ class EmailDetailRecordView extends DetailRecordView { groupIndex: 2, hidden: this.isInArchive() || this.isInTrash(), onClick: () => this.actionMoveToArchive(), + iconClass: 'far fa-caret-square-down', }); this.addDropdownItem({ @@ -188,12 +194,14 @@ class EmailDetailRecordView extends DetailRecordView { name: 'moveToFolder', groupIndex: 2, hidden: this.isInTrash(), + iconClass: 'far fa-folder', }); } else { this.addDropdownItem({ label: 'Move to Folder', name: 'moveToFolder', groupIndex: 2, + iconClass: 'far fa-folder', }); } diff --git a/client/src/views/modal.ts b/client/src/views/modal.ts index 71d2d332a4..7dcd5861d1 100644 --- a/client/src/views/modal.ts +++ b/client/src/views/modal.ts @@ -557,6 +557,8 @@ class ModalView extends View { } } + o.iconCLass = item.iconClass; + o.onClick = o.onClick ?? ((_d, e: any) => { // noinspection ES6ConvertLetToConst let handler = o.handler || (o.data || {}).handler; diff --git a/client/src/views/modals/detail.js b/client/src/views/modals/detail.js index c8f200ec7a..29e42e9d70 100644 --- a/client/src/views/modals/detail.js +++ b/client/src/views/modals/detail.js @@ -255,6 +255,7 @@ class DetailModalView extends ModalView { name: 'duplicate', label: 'Duplicate', groupIndex: 0, + iconClass: 'far fa-copy', }); } } diff --git a/client/src/views/record/detail.ts b/client/src/views/record/detail.ts index ed706234bf..e5b24490b6 100644 --- a/client/src/views/record/detail.ts +++ b/client/src/views/record/detail.ts @@ -169,6 +169,12 @@ export interface DropdownItem { * A group index. */ groupIndex?: number; + /** + * An icon class. + * + * @since 10.0.0 + */ + iconClass?: string; } /** @@ -493,6 +499,7 @@ class DetailRecordView .btn-group { left: -10px; } -.dropdown-menu-with-icons li .item-text { - padding-left: 0; - padding-right: 8px; +ul { + &.dropdown-menu-with-icons, + &:has( > li > a > .icon-item) { + padding-left: 0; + padding-right: var(--8px); + } } .panel { diff --git a/frontend/less/espo/elements/dropdown.less b/frontend/less/espo/elements/dropdown.less index 2bf7935278..908ffbe644 100644 --- a/frontend/less/espo/elements/dropdown.less +++ b/frontend/less/espo/elements/dropdown.less @@ -148,22 +148,38 @@ ul.dropdown-menu { } } -ul.dropdown-menu-with-icons { - > li { - a { - padding-left: var(--10px); - } +ul { + &.dropdown-menu-with-icons, + &:has(> li > a > .item-icon) { + > li { + a { + padding-left: var(--10px); + } - .fas, .far, .glyphicon, .empty-icon { - width: var(--16px); - text-align: center; - display: inline-block; - color: var(--gray-soft); - } + .fas, + .far, + .glyphicon, + .empty-icon { + width: var(--16px); + text-align: center; + display: inline-block; + color: var(--gray-soft); + } - .item-text { - padding-left: var(--8px); - position: static; + .item-icon { + font-size: var(--12px); + } + + .item-text { + padding-left: var(--8px); + position: static; + } + } + } + + &:has(> li > a > .item-icon) { + > li > a:not(:has(> .item-icon)) { + padding-left: var(--26px); } } }