field details

This commit is contained in:
Yuri Kuznetsov
2025-03-11 14:19:55 +02:00
parent c5e43e6e49
commit 80fac162af
6 changed files with 561 additions and 12 deletions
@@ -236,7 +236,8 @@
"autocompleteOnEmpty": "Autocomplete on empty input",
"relateOnImport": "Relate on Import",
"aclScope": "ACL Scope",
"onlyAdmin": "Only for Admin"
"onlyAdmin": "Only for Admin",
"notStorable": "Not Storable"
},
"strings" : {
"rebuildRequired": "Rebuild is required"
@@ -3,7 +3,8 @@
"Dynamic Logic": "Dynamic Logic",
"Name": "Name",
"Label": "Label",
"Type": "Type"
"Type": "Type",
"View Details": "View Details"
},
"options": {
"dateTimeDefault": {
@@ -170,5 +171,8 @@
"fieldAlreadyExists": "Field '{field}' already exists in '{entityType}'.",
"linkWithSameNameAlreadyExists": "Link with the name '{field}' already exists in '{entityType}'.",
"namingFieldLinkConflict": "Name '{field}' conflicts with link."
},
"otherFields": {
"attributes": "Attributes"
}
}
@@ -46,13 +46,22 @@
<td>{{name}}</td>
<td>{{translate type category='fieldTypes' scope='Admin'}}</td>
<td style="text-align: right">
{{#if isCustom}}
<div class="btn-group row-dropdown-group">
<button
class="btn btn-link btn-sm dropdown-toggle"
data-toggle="dropdown"
><span class="caret"></span></button>
<ul class="dropdown-menu pull-right">
<div class="btn-group row-dropdown-group">
<button
class="btn btn-link btn-sm dropdown-toggle"
data-toggle="dropdown"
><span class="caret"></span></button>
<ul class="dropdown-menu pull-right">
<li>
<a
role="button"
tabindex="0"
data-action="viewDetails"
data-name="{{name}}"
>{{translate 'View Details' scope='FieldManager'}}</a>
</li>
{{#if isCustom}}
<li class="divider"></li>
<li>
<a
role="button"
@@ -61,9 +70,9 @@
data-name="{{name}}"
>{{translate 'Remove'}}</a>
</li>
</ul>
</div>
{{/if}}
{{/if}}
</ul>
</div>
</td>
</tr>
{{/each}}
@@ -0,0 +1,84 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import BaseFieldView from 'views/fields/base';
export default class FieldAttributesFieldView extends BaseFieldView {
// language=Handlebars
detailTemplateContent = `
{{#if dataList.length}}
<table class="table table-bordered">
<thead>
<tr>
<th
>{{translate 'name' category='fields' scope='Admin'}}</th>
<th style="width: 33%"
>{{translate 'type' category='fields' scope='Admin'}}</th>
<th style="width: 16%"
>{{translate 'notStorable' category='fields' scope='Admin'}}</th>
<th style="width: 16%"
>{{translate 'readOnly' category='fields' scope='Admin'}}</th>
</tr>
</thead>
<tbody>
{{#each dataList}}
<tr>
<td>{{name}}</td>
<td>{{type}}</td>
<td>
<input
type="checkbox"
class="form-checkbox form-checkbox-simple"
disabled
{{#if notStorable}} checked {{/if}}
>
</td>
<td>
<input
type="checkbox"
class="form-checkbox form-checkbox-simple"
disabled
{{#if readOnly}} checked {{/if}}
>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<span class="none-value">{{translate 'None'}}</span>
{{/if}}
`
data() {
return {
dataList: this.model.attributes.attributes || [],
};
}
}
@@ -27,6 +27,7 @@
************************************************************************/
import View from 'view';
import ViewDetailsModalView from 'views/admin/field-manager/modals/view-details';
class FieldManagerListView extends View {
@@ -55,6 +56,8 @@ class FieldManagerListView extends View {
}
setup() {
this.addActionHandler('viewDetails', (e, target) => this.viewDetails(target.dataset.name));
this.scope = this.options.scope;
this.isCustomizable =
@@ -200,6 +203,21 @@ class FieldManagerListView extends View {
$row.removeClass('hidden');
});
}
/**
* @private
* @param {string} name
*/
async viewDetails(name) {
const view = new ViewDetailsModalView({
field: name,
entityType: this.scope,
});
await this.assignView('modal', view);
await view.render();
}
}
export default FieldManagerListView;
@@ -0,0 +1,433 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
import ModalView from 'views/modal';
import Model from 'model';
import VarcharFieldView from 'views/fields/varchar';
import FieldAttributesFieldView from 'views/admin/field-manager/detail-fields/attributes';
import DetailRecordView from 'views/record/detail';
import BoolFieldView from 'views/fields/bool';
export default class ViewDetailsModalView extends ModalView {
templateContent = `
<div class="record-container no-side-margin">{{{record}}}</div>
`
backdrop = true
/**
* @private
* @type {DetailRecordView}
*/
recordView
/**
* @private
* @type {Model}
*/
formModel
/**
* @private
* @type {string}
*/
fieldType
/**
* @private
* @type {{
* type?: string|undefined,
* notStorable?: boolean,
* } & Record}
*/
fieldDefs
/**
* @param {{
* field: string,
* entityType: string,
* }} options
*/
constructor(options) {
super(options);
this.field = options.field;
this.entityType = options.entityType;
}
setup() {
this.headerText =
this.translate(this.entityType, 'scopeNames') + ' · ' +
this.translate(this.field, 'fields', this.entityType);
/** @type {{type: string|undefined}} */
this.fieldDefs = this.getMetadata().get(`entityDefs.${this.entityType}.fields.${this.field}`) || {};
this.fieldType = this.fieldDefs.type;
this.formModel = new Model({
name: this.field,
attributes: this.getFieldAttributes(),
readOnly: this.fieldDefs.readOnly || false,
type: this.fieldDefs.type,
});
this.recordView = new DetailRecordView({
model: this.formModel,
detailLayout: [
{
rows: [
[
{
view: new VarcharFieldView({
name: 'name',
labelText: this.translate('name', 'fields'),
}),
},
false
],
[
{
view: new VarcharFieldView({
name: 'type',
labelText: this.translate('type', 'fields', 'Admin'),
}),
},
false
],
[
{
view: new BoolFieldView({
name: 'readOnly',
labelText: this.translate('readOnly', 'fields', 'Admin'),
}),
},
false
],
[
{
view: new FieldAttributesFieldView({
name: 'attributes',
labelText: this.translate('attributes', 'otherFields', 'FieldManager'),
}),
},
],
]
}
],
readOnly: true,
sideView: null,
isWide: true,
buttonsDisabled: true,
});
this.assignView('record', this.recordView);
}
/**
* @private
* @return {{
* name: string,
* type: string,
* notStorable: boolean,
* readOnly: boolean,
* }[]}
*/
getFieldAttributes() {
if (
this.fieldType === 'link' ||
this.fieldType === 'linkOne' ||
this.fieldType === 'file' ||
this.fieldType === 'image'
) {
return [
{
name: this.field + 'Id',
type: 'varchar',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Name',
type: 'varchar',
notStorable: true,
readOnly: true,
},
]
}
if (this.fieldType === 'linkParent') {
return [
{
name: this.field + 'Id',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Type',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Name',
type: 'string',
readOnly: true,
},
];
}
if (
this.fieldType === 'linkMultiple' ||
this.fieldType === 'attachmentMultiple'
) {
const attributes = [
{
name: this.field + 'Ids',
type: 'string[]',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Names',
type: 'Record.<string, string>',
notStorable: true,
readOnly: this.fieldDefs.readOnly || true,
},
];
if (this.fieldDefs.columns) {
attributes.push({
name: this.field + 'Columns',
type: 'Record.<string, Record.<string, string>>',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
});
}
return attributes;
}
if (this.fieldType === 'currency') {
return [
{
name: this.field,
type: this.fieldDefs.decimal ? 'string' : 'float',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Currency',
type: 'string',
notStorable: this.fieldDefs.notStorable || false || this.fieldDefs.onlyDefaultCurrency,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'personName') {
return [
{
name: this.field,
type: 'string',
notStorable: true,
readOnly: true,
},
{
name: 'salutation' + Espo.Utils.upperCaseFirst(this.field),
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: 'first' + Espo.Utils.upperCaseFirst(this.field),
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: 'last' + Espo.Utils.upperCaseFirst(this.field),
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: 'middle' + Espo.Utils.upperCaseFirst(this.field),
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'address') {
return [
{
name: this.field,
type: 'string',
notStorable: true,
readOnly: true,
},
{
name: this.field + 'Street',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'City',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'Country',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'State',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
{
name: this.field + 'PostalCode',
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (
[
'varchar',
'text',
'wysiwyg',
'colorpicker',
'barcode',
'url',
'enum',
'date',
'datetime',
'datetimeOptional',
].includes(this.fieldType)
) {
return [
{
name: this.field,
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'bool') {
return [
{
name: this.field,
type: 'boolean',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'int' || this.fieldType === 'enumInt') {
return [
{
name: this.field,
type: 'int',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'float' || this.fieldType === 'enumFloat') {
return [
{
name: this.field,
type: 'float',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
if (this.fieldType === 'autoincrement') {
return [
{
name: this.field,
type: 'int',
notStorable: this.fieldDefs.notStorable || false,
readOnly: true,
},
];
}
if (this.fieldType === 'number') {
return [
{
name: this.field,
type: 'string',
notStorable: this.fieldDefs.notStorable || false,
readOnly: true,
},
];
}
if (
[
'multiEnum',
'checklist',
'array',
'urlMultiple'
].includes(this.fieldType)
) {
return [
{
name: this.field,
type: 'string[]',
notStorable: this.fieldDefs.notStorable || false,
readOnly: this.fieldDefs.readOnly || false,
},
];
}
return undefined;
}
}