mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 19:04:38 +00:00
Design setting page
This commit is contained in:
parent
4966c78f56
commit
bc248b9f54
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Shape</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Pages" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Content-Manager---Settings-view---Single" transform="translate(-668.000000, -604.000000)" fill="#007EFF" fill-rule="nonzero">
|
||||
<g id="Container" transform="translate(257.000000, 84.000000)">
|
||||
<g id="Forms" transform="translate(0.000000, 77.000000)">
|
||||
<g id="Settings">
|
||||
<g id="Attributes" transform="translate(12.000000, 327.000000)">
|
||||
<g id="Order-attributes" transform="translate(0.000000, 23.000000)">
|
||||
<g id="Link" transform="translate(35.000000, 0.000000)">
|
||||
<path d="M366.39604,102.155116 L366.9967,101.554455 L365.445545,100.0033 L364.844884,100.60396 L364.844884,101.310231 L365.689769,101.310231 L365.689769,102.155116 L366.39604,102.155116 Z M369.848185,96.029703 C369.848185,95.9328933 369.79978,95.8844884 369.70297,95.8844884 C369.658966,95.8844884 369.621562,95.89989 369.590759,95.9306931 L366.013201,99.5082508 C365.982398,99.5390539 365.966997,99.5764576 365.966997,99.620462 C365.966997,99.7172717 366.015402,99.7656766 366.112211,99.7656766 C366.156216,99.7656766 366.193619,99.750275 366.224422,99.7194719 L369.80198,96.1419142 C369.832783,96.1111111 369.848185,96.0737074 369.848185,96.029703 Z M369.491749,94.7623762 L372.237624,97.5082508 L366.745875,103 L364,103 L364,100.254125 L369.491749,94.7623762 Z M374,95.3960396 C374,95.6292629 373.918592,95.8272827 373.755776,95.990099 L372.660066,97.0858086 L369.914191,94.339934 L371.009901,93.2508251 C371.168317,93.0836084 371.366337,93 371.60396,93 C371.837184,93 372.037404,93.0836084 372.20462,93.2508251 L373.755776,94.7953795 C373.918592,94.9669967 374,95.1672167 374,95.3960396 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@ -13,6 +13,7 @@ import {
|
||||
import { flow, upperFirst } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import cn from 'classnames';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
@ -29,7 +30,8 @@ const draggableAttrSource = {
|
||||
},
|
||||
endDrag: (props, monitor, component) => {
|
||||
const el = findDOMNode(component);
|
||||
el.className = styles.draggableAttr;
|
||||
const className = props.isEditing ? `${styles.draggableAttr} ${styles.editingAttr}` : styles.draggableAttr;
|
||||
el.className = className;
|
||||
props.updateSiblingHoverState();
|
||||
|
||||
return {};
|
||||
@ -115,7 +117,7 @@ class DraggableAttr extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, name, isDragging, connectDragSource, connectDropTarget } = this.props;
|
||||
const { label, name, isDragging, isEditing, connectDragSource, connectDropTarget } = this.props;
|
||||
const { isHover } = this.state;
|
||||
const opacity = isDragging ? 0.2 : 1;
|
||||
|
||||
@ -123,13 +125,13 @@ class DraggableAttr extends React.Component {
|
||||
connectDragSource(
|
||||
connectDropTarget(
|
||||
<div
|
||||
className={styles.draggableAttr}
|
||||
className={cn(styles.draggableAttr, isEditing && styles.editingAttr)}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onClick={this.handleClickEdit}
|
||||
style={{ opacity }}
|
||||
>
|
||||
<i className="fa fa-th" aria-hidden="true"></i>
|
||||
<i className="fa fa-th" aria-hidden="true" />
|
||||
<span>{name}</span>
|
||||
{ isHover && !isDragging && (
|
||||
<div className={styles.info}>
|
||||
@ -141,7 +143,11 @@ class DraggableAttr extends React.Component {
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
<span className={styles.removeIcon} onClick={this.handleRemove} />
|
||||
{isEditing && !isHover? (
|
||||
<span className={styles.editIcon} onClick={this.handleClickEdit} />
|
||||
) : (
|
||||
<span className={styles.removeIcon} onClick={this.handleRemove} />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)
|
||||
@ -150,6 +156,7 @@ class DraggableAttr extends React.Component {
|
||||
}
|
||||
|
||||
DraggableAttr.defaultProps = {
|
||||
isEditing: false,
|
||||
onRemove: () => {},
|
||||
};
|
||||
|
||||
@ -159,6 +166,7 @@ DraggableAttr.propTypes = {
|
||||
index: PropTypes.number.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
isDraggingSibling: PropTypes.bool.isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
keys: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
border-radius: 2px;
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
font-size: 10px;
|
||||
font-size: 11px;
|
||||
color: #B3B5B9;
|
||||
}
|
||||
&:hover {
|
||||
@ -20,6 +20,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.editingAttr {
|
||||
background: #E6F0FB!important;
|
||||
}
|
||||
|
||||
.info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -47,6 +51,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.editIcon {
|
||||
width: 30px;
|
||||
background: #E6F0FB;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
float: right;
|
||||
|
||||
&:after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: auto;
|
||||
margin-top: -3px;
|
||||
background-image: url('../../assets/images/icon-edit-blue.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.dragged {
|
||||
position: relative;
|
||||
height: 28px;
|
||||
|
||||
@ -80,9 +80,24 @@ function appReducer(state = initialState, action) {
|
||||
return state
|
||||
.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => list.push(fromJS(action.data)));
|
||||
case ON_REMOVE:
|
||||
return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => (
|
||||
list.delete(action.index)
|
||||
));
|
||||
return state.updateIn(['modifiedSchema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']), list => {
|
||||
|
||||
// If the list is empty add the default Id attribute
|
||||
if (list.size -1 === 0) {
|
||||
const attrToAdd = state.getIn(['schema', 'models'].concat(action.keys.split('.')).concat(['listDisplay']))
|
||||
.filter(attr => {
|
||||
return attr.get('name') === '_id' || attr.get('name') === 'id';
|
||||
});
|
||||
|
||||
attrToAdd.setIn(['0', 'sortable'], () => true);
|
||||
|
||||
return list
|
||||
.delete(action.index)
|
||||
.push(attrToAdd.get('0'));
|
||||
}
|
||||
|
||||
return list.delete(action.index);
|
||||
});
|
||||
case ON_RESET:
|
||||
return state
|
||||
.update('modifiedSchema', () => state.get('schema'));
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ON_CLICK_EDIT_LIST_ITEM } from './constants';
|
||||
|
||||
export function onClickEditListItem(index) {
|
||||
export function onClickEditListItem(listItemToEdit) {
|
||||
return {
|
||||
type: ON_CLICK_EDIT_LIST_ITEM,
|
||||
index,
|
||||
listItemToEdit,
|
||||
};
|
||||
}
|
||||
@ -48,8 +48,17 @@ import styles from './styles.scss';
|
||||
class SettingPage extends React.PureComponent {
|
||||
state = { showWarning: false, isDraggingSibling: false, isOpen: false };
|
||||
|
||||
componentDidMount() {
|
||||
this.handleClickEditAttr(0);
|
||||
}
|
||||
|
||||
getDefaultSort = () => this.getValue(`${this.getPath()}.defaultSort`, 'string');
|
||||
|
||||
getDropDownItems = () => {
|
||||
const attributes = get(this.props.schema, `models.${this.getPath()}.attributes`, []);
|
||||
const name = get(this.props.schema, `models.${this.getPath()}.primaryKey`, 'id' );
|
||||
const defaultAttr = { [name]: { name, label: 'Id', type: 'string', searchable: true, sortable: true } };
|
||||
const attributes = Object.assign(get(this.props.schema, `models.${this.getPath()}.attributes`, {}), defaultAttr);
|
||||
|
||||
return Object.keys(attributes)
|
||||
.filter(attr => {
|
||||
return findIndex(this.getListDisplay(), ['name', attr]) === -1 && !attributes[attr].hasOwnProperty('collection') && !attributes[attr].hasOwnProperty('model');
|
||||
@ -81,11 +90,18 @@ class SettingPage extends React.PureComponent {
|
||||
}
|
||||
|
||||
getSelectOptions = (input) => {
|
||||
const { schema: { models } } = this.props;
|
||||
const currentAttributes = get(models, this.getModelName().concat(['attributes']), []);
|
||||
const selectOptions = [get(models, this.getModelName().concat(['primaryKey']), 'id')]
|
||||
.concat(Object.keys(currentAttributes)
|
||||
.filter(attr => currentAttributes[attr].type !== 'json' && currentAttributes[attr].type !== 'array'));
|
||||
const selectOptions = this.getListDisplay().reduce((acc, curr) => {
|
||||
|
||||
if (curr.sortable === true) {
|
||||
return acc.concat([curr.name]);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (selectOptions.length === 0) {
|
||||
selectOptions.push(this.getPrimaryKey());
|
||||
}
|
||||
|
||||
return input.name === 'defaultSort' ? selectOptions : input.selectOptions;
|
||||
}
|
||||
@ -107,6 +123,8 @@ class SettingPage extends React.PureComponent {
|
||||
]
|
||||
);
|
||||
|
||||
getPrimaryKey = () => get(this.props.schema, ['models', this.getModelName()].concat(['primaryKey']), 'id');
|
||||
|
||||
getValue = (keys, type) => {
|
||||
const value = get(this.props.schema, ['models'].concat(keys.split('.')));
|
||||
|
||||
@ -114,11 +132,60 @@ class SettingPage extends React.PureComponent {
|
||||
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const defaultSort = this.getDefaultSort();
|
||||
const name = e.target.name.split('.');
|
||||
name.pop();
|
||||
const attrName = get(this.props.schema.models, name.concat(['name']));
|
||||
const isDisablingDefaultSort = attrName === defaultSort && e.target.value === false;
|
||||
|
||||
if (isDisablingDefaultSort) {
|
||||
const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrName);
|
||||
|
||||
if (enableAttrsSort.length === 0) {
|
||||
strapi.notification.info('content-manager.notification.info.SettingPage.disableSort');
|
||||
} else {
|
||||
const newDefaultSort = enableAttrsSort.length === 0 ? this.getPrimaryKey() : enableAttrsSort[0];
|
||||
const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
|
||||
this.props.onChangeSettings({ target });
|
||||
this.props.onChangeSettings(e);
|
||||
}
|
||||
} else {
|
||||
this.props.onChangeSettings(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleClickEditAttr = (index) => {
|
||||
const attrToEdit = get(this.props.schema, ['models', this.getModelName()].concat(['listDisplay', index]), {});
|
||||
this.props.onClickEditListItem(attrToEdit);
|
||||
}
|
||||
|
||||
handleRemove = (index, keys) => {
|
||||
const attrToRemove = get(this.getListDisplay(), index, {});
|
||||
const defaultSort = this.getDefaultSort();
|
||||
const isRemovingDefaultSort = defaultSort === attrToRemove.name;
|
||||
|
||||
if (isRemovingDefaultSort) {
|
||||
const enableAttrsSort = this.getSelectOptions({ name: 'defaultSort' }).filter(attr => attr !== attrToRemove.name);
|
||||
const newDefaultSort = enableAttrsSort.length > 1 ? enableAttrsSort[0] : this.getPrimaryKey();
|
||||
const target = { name: `${this.getPath()}.defaultSort`, value: newDefaultSort };
|
||||
this.props.onChangeSettings({ target });
|
||||
}
|
||||
|
||||
this.props.onRemove(index, keys);
|
||||
}
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.setState({ showWarning: true });
|
||||
}
|
||||
|
||||
findIndexListItemToEdit = () => {
|
||||
const index = findIndex(this.getListDisplay(), ['name', get(this.props.settingPage, ['listItemToEdit', 'name'])]);
|
||||
|
||||
return index === -1 ? 0 : index;
|
||||
}
|
||||
|
||||
updateSiblingHoverState = () => {
|
||||
this.setState(prevState => ({ isDraggingSibling: !prevState.isDraggingSibling }));
|
||||
};
|
||||
@ -137,12 +204,7 @@ class SettingPage extends React.PureComponent {
|
||||
moveAttr,
|
||||
onChangeSettings,
|
||||
onClickAddAttr,
|
||||
onClickEditListItem,
|
||||
onRemove,
|
||||
onSubmit,
|
||||
settingPage: {
|
||||
indexListItemToEdit,
|
||||
},
|
||||
} = this.props;
|
||||
const namePath = this.getPath();
|
||||
|
||||
@ -213,57 +275,67 @@ class SettingPage extends React.PureComponent {
|
||||
<div>{index}.</div>
|
||||
<DraggableAttr
|
||||
index={index}
|
||||
isDraggingSibling={isDraggingSibling}
|
||||
isEditing={index === this.findIndexListItemToEdit()}
|
||||
key={attr.name}
|
||||
keys={this.getPath()}
|
||||
label={attr.label}
|
||||
name={attr.name}
|
||||
moveAttr={moveAttr}
|
||||
onClickEditListItem={onClickEditListItem}
|
||||
onRemove={onRemove}
|
||||
onClickEditListItem={this.handleClickEditAttr}
|
||||
onRemove={this.handleRemove}
|
||||
updateSiblingHoverState={this.updateSiblingHoverState}
|
||||
isDraggingSibling={isDraggingSibling}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<ButtonDropdown isOpen={isOpen} toggle={this.toggleDropdown}>
|
||||
<DropdownToggle caret>
|
||||
Button Dropdown
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{this.getDropDownItems().map((item, i) => {
|
||||
if (i !== this.getDropDownItems().length - 1 && this.getDropDownItems().length > 0) {
|
||||
return (
|
||||
<React.Fragment key={item.name}>
|
||||
<DropdownItem onClick={() => onClickAddAttr(item, this.getPath())}>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
<DropdownItem divider />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
<div className={styles.dropdownWrapper}>
|
||||
<ButtonDropdown isOpen={isOpen} toggle={this.toggleDropdown}>
|
||||
<DropdownToggle>
|
||||
<FormattedMessage id="content-manager.containers.SettingPage.addField">
|
||||
{msg => <p>{msg}</p>}
|
||||
</FormattedMessage>
|
||||
</DropdownToggle>
|
||||
<DropdownMenu>
|
||||
{this.getDropDownItems().map((item, i) => {
|
||||
if (i !== this.getDropDownItems().length - 1 && this.getDropDownItems().length > 0) {
|
||||
return (
|
||||
<React.Fragment key={item.name}>
|
||||
<DropdownItem onClick={() => onClickAddAttr(item, this.getPath())}>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
<DropdownItem divider />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownItem
|
||||
key={item.name}
|
||||
onClick={() => onClickAddAttr(item, this.getPath())}
|
||||
>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</ButtonDropdown>
|
||||
return (
|
||||
<DropdownItem
|
||||
key={item.name}
|
||||
onClick={() => onClickAddAttr(item, this.getPath())}
|
||||
>
|
||||
{item.label}
|
||||
</DropdownItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu>
|
||||
</ButtonDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-7">
|
||||
<div className={styles.editWrapper}>
|
||||
<div className="row">
|
||||
{forms.editList.map(input => {
|
||||
{forms.editList.map((input, i) => {
|
||||
const indexListItemToEdit = this.findIndexListItemToEdit();
|
||||
const inputName = `${namePath}.listDisplay.${indexListItemToEdit}.${input.name}`;
|
||||
|
||||
if (indexListItemToEdit === -1) {
|
||||
return <div key={i} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
key={input.name}
|
||||
onChange={onChangeSettings}
|
||||
onChange={this.handleChange}
|
||||
value={this.getValue(inputName, input.type)}
|
||||
{...input}
|
||||
name={inputName}
|
||||
|
||||
@ -7,13 +7,15 @@ import { fromJS } from 'immutable';
|
||||
import { ON_CLICK_EDIT_LIST_ITEM } from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
listItemToEdit: fromJS({}),
|
||||
indexListItemToEdit: 0,
|
||||
});
|
||||
|
||||
function settingPageReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case ON_CLICK_EDIT_LIST_ITEM:
|
||||
return state.update('indexListItemToEdit', () => action.index);
|
||||
return state
|
||||
.update('listItemToEdit', () => fromJS(action.listItemToEdit));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -28,8 +28,10 @@
|
||||
.draggedWrapper {
|
||||
display: flex;
|
||||
> div:first-child {
|
||||
margin-right: 10px;
|
||||
height: 30px;
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
text-align: right;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
@ -54,4 +56,72 @@
|
||||
padding: 24px 30px;
|
||||
background-color: #FAFAFB;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.dropdownWrapper {
|
||||
margin-left: 30px;
|
||||
> div {
|
||||
height: 28px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
background: #ffffff;
|
||||
color: #333740;
|
||||
border: 1px solid #E3E9F3;
|
||||
border-radius: 2px;
|
||||
> button {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
padding-left: 10px !important;
|
||||
line-height: 28px;
|
||||
width: 100%;
|
||||
color: #333740;
|
||||
text-align: left;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
&:focus, &:active, &:hover, &:visited {
|
||||
background-color: transparent!important;
|
||||
box-shadow: none;
|
||||
color: #333740;
|
||||
}
|
||||
> p {
|
||||
height: 100%;
|
||||
margin-left: 20px;
|
||||
margin-bottom: 0;
|
||||
margin-top: -1px;
|
||||
color: #007EFF !important;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
&:before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
content: '\f067';
|
||||
font-family: FontAwesome;
|
||||
font-size: 10px;
|
||||
color: #007EFF;
|
||||
}
|
||||
}
|
||||
> div {
|
||||
max-height: 180px;
|
||||
min-width: calc(100% + 2px);
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
overflow: scroll;
|
||||
|
||||
button {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
div {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,11 @@
|
||||
|
||||
.container {
|
||||
padding-top: 18px;
|
||||
> div:last-child {
|
||||
> div {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main_wrapper{
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"components.LimitSelect.itemsPerPage": "Items per page",
|
||||
"containers.List.errorFetchRecords": "Error",
|
||||
|
||||
"containers.SettingPage.addField": "Add new field",
|
||||
"containers.SettingPage.attributes": "Attributes fields",
|
||||
"containers.SettingPage.attributes.description": "Define the order of the attributes",
|
||||
|
||||
@ -98,6 +99,7 @@
|
||||
"form.Input.defaultSort": "Default sort attribute",
|
||||
|
||||
"notification.error.relationship.fetch": "An error occurred during relationship fetch.",
|
||||
"notification.info.SettingPage.disableSort": "You need to have one attribute with the sorting allowed",
|
||||
|
||||
"success.record.delete": "Deleted",
|
||||
"success.record.save": "Saved",
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"components.LimitSelect.itemsPerPage": "Éléments par page",
|
||||
"containers.List.errorFetchRecords": "Erreur",
|
||||
|
||||
"containers.SettingPage.addField": "Ajouter un nouveau champs",
|
||||
"containers.SettingPage.attributes": "Attributs",
|
||||
"containers.SettingPage.attributes.description": "Organiser les attributs du modèle",
|
||||
"containers.SettingPage.pluginHeaderDescription": "Configurez les paramètres de ce modèle",
|
||||
@ -95,6 +96,7 @@
|
||||
"form.Input.defaultSort": "Attribut de trie par défault",
|
||||
|
||||
"notification.error.relationship.fetch": "Une erreur est survenue en récupérant les relations.",
|
||||
"notification.info.SettingPage.disableSort": "Vous devez avoir au moins un attribut de trie par défaut",
|
||||
|
||||
"success.record.delete": "Supprimé",
|
||||
"success.record.save": "Sauvegardé",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user