diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json
index 016c88685..d44fcf5c6 100644
--- a/public/locales/fr/translation.json
+++ b/public/locales/fr/translation.json
@@ -202,7 +202,8 @@
"noAccess": "Vous n'avez pas les droits pour accéder à cette ressource.",
"geometry": "Géométrie",
"true": "Oui",
- "false": "Non"
+ "false": "Non",
+ "other": "Autre"
}
},
"help": {
diff --git a/src/modules/CRUD/components/DataTable/DataTable.js b/src/modules/CRUD/components/DataTable/DataTable.js
index b691e12d9..d4bb0222b 100644
--- a/src/modules/CRUD/components/DataTable/DataTable.js
+++ b/src/modules/CRUD/components/DataTable/DataTable.js
@@ -31,7 +31,7 @@ class DataTable extends React.Component {
loadData = () => {
const { getFeaturesList } = this.props;
- const { layer: { id } } = this.getView();
+ const { layer: { id } = {} } = this.getView();
if (!id) return;
getFeaturesList(id);
this.setState({
@@ -87,7 +87,7 @@ class DataTable extends React.Component {
onHoverCell = () => null,
} = this.props;
- const { layer: { name }, name: displayName = name } = this.getView();
+ const { layer: { name } = {}, name: displayName = name } = this.getView();
const { data, columns, loading } = this.state;
diff --git a/src/modules/CRUD/components/Details/Details.js b/src/modules/CRUD/components/Details/Details.js
index 89222d9c2..b10b06b07 100644
--- a/src/modules/CRUD/components/Details/Details.js
+++ b/src/modules/CRUD/components/Details/Details.js
@@ -14,8 +14,12 @@ import './styles.scss';
class Details extends React.Component {
static propTypes = {
- view: PropTypes.shape({}).isRequired,
- feature: PropTypes.shape({}),
+ view: PropTypes.shape({
+ formSchema: PropTypes.shape({}),
+ }),
+ feature: PropTypes.shape({
+ properties: PropTypes.shape({}),
+ }),
fetchFeature: PropTypes.func.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
@@ -32,7 +36,12 @@ class Details extends React.Component {
};
static defaultProps = {
- feature: {},
+ view: {
+ formSchema: {},
+ },
+ feature: {
+ properties: {},
+ },
match: {
params: {
id: undefined,
@@ -85,7 +94,7 @@ class Details extends React.Component {
},
},
feature,
- feature: { properties } = {},
+ feature: { properties },
detailsHasLoaded,
} = this.props;
@@ -98,7 +107,7 @@ class Details extends React.Component {
}
if (
- properties !== prevProperties
+ (properties !== prevProperties)
|| prevParamId !== paramId
|| prevParamAction !== paramAction
) {
@@ -123,29 +132,39 @@ class Details extends React.Component {
}
}
- setSchema = () => {
+ buildSchema = (schemaProperties, properties = {}) => {
const {
match: { params: { id: paramId } },
- feature: { properties },
+ } = this.props;
+ return Object.keys(schemaProperties).reduce((list, prop) => ({
+ ...list,
+ [prop]: {
+ ...schemaProperties[prop],
+ ...(schemaProperties[prop].type === 'object')
+ ? { properties: this.buildSchema(schemaProperties[prop].properties, properties[prop]) }
+ : {},
+ ...(paramId !== ACTION_CREATE && properties[prop] && schemaProperties[prop].type !== 'object')
+ ? { default: properties[prop] }
+ : {},
+ },
+ }), {});
+ }
+
+ setSchema = () => {
+ const {
+ feature: { properties = {} },
view: { formSchema: schema = {} },
} = this.props;
- if (Object.keys(schema).length) {
- this.setState({
- schema: {
- type: 'object',
- ...schema,
- properties: Object.keys(schema.properties).reduce((list, prop) => ({
- ...list,
- [prop]: {
- ...schema.properties[prop],
- ...(properties && paramId !== ACTION_CREATE)
- ? { default: properties[prop] }
- : {},
- },
- }), {}),
- },
- });
+ if (!Object.keys(properties).length && !Object.keys(schema).length) {
+ return;
}
+ this.setState({
+ schema: {
+ type: 'object',
+ ...schema,
+ properties: this.buildSchema(schema.properties, properties),
+ },
+ });
}
renderContent = () => {
@@ -164,10 +183,11 @@ class Details extends React.Component {
updateControls={updateControls}
action={paramAction || paramId}
view={view}
+ feature={feature}
/>
);
}
- return ;
+ return ;
}
onSizeChange = () => {
@@ -189,7 +209,6 @@ class Details extends React.Component {
toast.displayError(t('CRUD.details.errorNoFeature'));
return ;
}
-
const isLoading = !Object.keys(feature).length && paramId !== ACTION_CREATE;
return (
diff --git a/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.js b/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.js
index ce6b6e4ed..81b45c6f1 100644
--- a/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.js
+++ b/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.js
@@ -2,27 +2,26 @@ import React from 'react';
import PropTypes from 'prop-types';
import { AnchorButton, ButtonGroup } from '@blueprintjs/core';
-const DownloadButtons = ({ files, id, ...rest }) => id && files.length > 0 && (
+const DownloadButtons = ({ documents, ...rest }) => documents.length > 0 && (
- {files.map(({ name, url }) => (
- {name}
+ {documents.map(({ download_url: url, template_file: file, template_name: name }) => (
+ {name}
))}
);
DownloadButtons.propTypes = {
- files: PropTypes.arrayOf(
+ documents: PropTypes.arrayOf(
PropTypes.shape({
- name: PropTypes.string,
- url: PropTypes.string,
+ template_name: PropTypes.string,
+ download_url: PropTypes.string,
+ template_file: PropTypes.string,
}),
),
- id: PropTypes.number,
};
DownloadButtons.defaultProps = {
- files: [],
- id: '',
+ documents: [],
};
export default DownloadButtons;
diff --git a/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.test.js b/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.test.js
index cfe85196e..4537795aa 100644
--- a/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.test.js
+++ b/src/modules/CRUD/components/Details/DownloadButtons/DownloadButtons.test.js
@@ -10,14 +10,15 @@ jest.mock('@blueprintjs/core', () => ({
const props = {
- files: [{
- name: 'FileName',
- url: 'path/to/file/{id}',
+ documents: [{
+ template_name: 'FileName',
+ download_url: 'path/to/file/',
+ template_file: 'file1.pdf',
}, {
- name: 'FileName2',
- url: 'path/to/file2/{id}',
+ template_name: 'FileName2',
+ download_url: 'path/to/file2/',
+ template_file: 'file2.pdf',
}],
- id: 3,
};
diff --git a/src/modules/CRUD/components/Details/Edit/Edit.js b/src/modules/CRUD/components/Details/Edit/Edit.js
index 4f3d4ee03..54b0616d4 100644
--- a/src/modules/CRUD/components/Details/Edit/Edit.js
+++ b/src/modules/CRUD/components/Details/Edit/Edit.js
@@ -34,7 +34,9 @@ function updateSchemaPropertiesValues (properties, formData) {
class Edit extends React.Component {
static propTypes = {
map: PropTypes.shape({}),
- feature: PropTypes.shape({}),
+ feature: PropTypes.shape({
+ title: PropTypes.string,
+ }),
saveFeature: PropTypes.func.isRequired,
view: PropTypes.shape({}).isRequired,
layerPaint: PropTypes.shape({}).isRequired,
@@ -54,7 +56,9 @@ class Edit extends React.Component {
static defaultProps = {
map: {},
- feature: {},
+ feature: {
+ title: undefined,
+ },
displayAddFeature: true,
displayChangeFeature: true,
updateControls () {},
@@ -284,6 +288,7 @@ class Edit extends React.Component {
paramId,
displayAddFeature,
displayChangeFeature,
+ feature: { title },
} = this.props;
if (
@@ -294,7 +299,6 @@ class Edit extends React.Component {
return ();
}
- const { name: { default: title } = {} } = properties || {};
const mainTitle = action === ACTION_CREATE
? t('CRUD.details.create', { layer: displayName })
: (title || t('CRUD.details.noFeature'));
diff --git a/src/modules/CRUD/components/Details/Edit/Edit.test.js b/src/modules/CRUD/components/Details/Edit/Edit.test.js
index 080c4de64..07b94c68b 100644
--- a/src/modules/CRUD/components/Details/Edit/Edit.test.js
+++ b/src/modules/CRUD/components/Details/Edit/Edit.test.js
@@ -55,7 +55,9 @@ beforeEach(() => {
props = {
settings: {},
map: {},
- feature: {},
+ feature: {
+ title: 'Title of the feature',
+ },
saveFeature: jest.fn(),
updateControls: jest.fn(),
view: {
@@ -76,6 +78,12 @@ beforeEach(() => {
properties: {
city: { type: 'boolean', title: 'City' },
name: { type: 'text', default: 'Title' },
+ group: {
+ type: 'object',
+ properties: {
+ name: { type: 'string', title: 'Group name' },
+ },
+ },
},
},
history: {
@@ -294,6 +302,12 @@ it('should update schema', () => {
city: { title: 'City', type: 'boolean' },
geometryFromMap: { default: false, title: 'CRUD.details.geometry', type: 'boolean' },
name: { default: 'Title', type: 'text' },
+ group: {
+ type: 'object',
+ properties: {
+ name: { type: 'string', title: 'Group name' },
+ },
+ },
},
},
});
diff --git a/src/modules/CRUD/components/Details/Edit/__snapshots__/Edit.test.js.snap b/src/modules/CRUD/components/Details/Edit/__snapshots__/Edit.test.js.snap
index 634b17f77..ede18be83 100644
--- a/src/modules/CRUD/components/Details/Edit/__snapshots__/Edit.test.js.snap
+++ b/src/modules/CRUD/components/Details/Edit/__snapshots__/Edit.test.js.snap
@@ -16,7 +16,7 @@ exports[`Snapshots should render correctly 1`] = `
- Title
+ Title of the feature
- CRUD.details.noFeature
+ Title of the feature
['', undefined].includes(value);
+const emptyStringNullOrUndef = value => ['', null, undefined].includes(value);
const isHTML = value => {
const div = document.createElement('div');
@@ -30,7 +30,7 @@ const formattedProp = ({ value, t }) => {
: t('CRUD.details.false');
}
- if (emptyStringOrUndef(value)) {
+ if (emptyStringNullOrUndef(value)) {
return t(NO_FEATURE);
}
@@ -53,59 +53,107 @@ const formattedProp = ({ value, t }) => {
return value;
};
-const Read = ({
- t,
- match: { params: { layer: paramLayer, id: paramId } },
- schema: { title: schemaTitle, properties = {} },
- displayViewFeature,
- view: { templates, uiSchema: { 'ui:order': order } = {} },
- feature: { id },
-}) => {
- if (!displayViewFeature) {
- toast.displayError(t('CRUD.details.noAccess'));
- return (
);
+
+class Read extends React.Component {
+ state = {
+ tabs: [],
+ }
+
+ componentDidMount () {
+ this.generatesTabs();
}
- const { name: { default: title } = {} } = properties;
- const hasProperties = !!Object.keys(properties).length;
+ componentDidUpdate ({
+ feature: { display_properties: prevDisplayProperties },
+ }) {
+ const {
+ feature: { display_properties: displayProperties },
+ } = this.props;
+ if (prevDisplayProperties !== displayProperties) {
+ this.generatesTabs();
+ }
+ }
- const orderedProperties = orderProperties(Object.keys(properties), order);
+ generatesTabs = () => {
+ const {
+ feature: { display_properties: displayProperties },
+ } = this.props;
+ this.setState({
+ tabs: Object.keys(displayProperties)
+ .map(tabs => ({ ...displayProperties[tabs] }))
+ .sort((a, b) => a.order - b.order),
+ });
+ }
- return (
-
-
-
{title || t(NO_FEATURE)}
-
-
- {hasProperties && (
-
- {schemaTitle && (
-
{schemaTitle}
- )}
-
- {orderedProperties.map(prop => (
- -
- {properties[prop].title || prop}
-
- {formattedProp({ value: properties[prop].default, t })}
-
-
- ))}
-
+ renderPanel = properties => {
+ const { t } = this.props;
+ return (
+
+ {Object.keys(properties).map(name => (
+ -
+ {name}
+
+ {formattedProp({ value: properties[name], t })}
+
+
+ ))}
+
+ );
+ }
+
+ render () {
+ const {
+ t,
+ match: { params: { layer: paramLayer, id: paramId } },
+ location: { hash },
+ displayViewFeature,
+ feature: { title: featureTitle, documents },
+ } = this.props;
+
+ if (!displayViewFeature) {
+ toast.displayError(t('CRUD.details.noAccess'));
+ return (
);
+ }
+
+ const { tabs } = this.state;
+ const hasProperties = !!tabs.length;
+
+ return (
+
+
+
{featureTitle || t(NO_FEATURE)}
+
- )}
-
-
- );
-};
+ {hasProperties && (
+
+
+ {tabs.map(({ title, slug = 'other', properties }) => (
+ {title || t('CRUD.details.other')}}
+ panel={this.renderPanel(properties)}
+ />
+ ))}
+
+
+
+ )}
+
+
+ );
+ }
+}
Read.propTypes = {
match: PropTypes.shape({
@@ -114,15 +162,14 @@ Read.propTypes = {
id: PropTypes.string,
}),
}),
- schema: PropTypes.shape({
- properties: PropTypes.shape({}),
+ location: PropTypes.shape({
+ hash: PropTypes.string,
}),
displayViewFeature: PropTypes.bool,
- view: PropTypes.shape({
- templates: PropTypes.array,
- }),
feature: PropTypes.shape({
- id: PropTypes.number,
+ title: PropTypes.string,
+ documents: PropTypes.array,
+ display_properties: PropTypes.shape({}),
}),
t: PropTypes.func,
};
@@ -134,15 +181,14 @@ Read.defaultProps = {
id: undefined,
},
},
- schema: {
- properties: {},
- },
+ location: PropTypes.shape({
+ hash: undefined,
+ }),
displayViewFeature: true,
- view: {
- templates: [],
- },
feature: {
- id: undefined,
+ title: '',
+ documents: [],
+ display_properties: {},
},
t: text => text,
};
diff --git a/src/modules/CRUD/components/Details/Read/Read.test.js b/src/modules/CRUD/components/Details/Read/Read.test.js
index d6e7f9a1b..160c2457b 100644
--- a/src/modules/CRUD/components/Details/Read/Read.test.js
+++ b/src/modules/CRUD/components/Details/Read/Read.test.js
@@ -4,8 +4,20 @@ import renderer from 'react-test-renderer';
import Read from './Read';
+jest.mock('@blueprintjs/core', () => {
+ const Tabs = ({ children }) =>
;
+ const Tab = ({ title, panel }) =>
{title}{panel};
+ Tabs.Expander = () => null;
+
+ return {
+ Tabs,
+ Tab,
+ };
+});
+
jest.mock('react-router-dom', () => ({
Redirect: () =>
Error because Redirect
,
+ Link: ({ children }) =>
{children},
}));
jest.mock('../../../../../utils/toast', () => ({
@@ -25,62 +37,38 @@ jest.mock('../Actions', () => () => (
Actions
));
const props = {
t: text => text,
match: { params: { layer: 'layerFoo', id: 'layerId' } },
- schema: {
- title: 'Foo Title',
- properties: {
- city: {
- type: 'string',
- title: 'Ville',
- default: 'Agen, Lot-et-Garonne, Nouvelle-Aquitaine',
- },
- name: {
- type: 'string',
- title: 'Nom',
- default: 'Cathedrale Saint-Caprais',
- },
- description: {
- type: 'string',
- title: 'Description',
- default: '',
- },
- numero: {
- type: 'integer',
- title: 'Numéro',
- default: 2,
- },
- validation: {
- type: 'boolean',
- title: 'Validation',
- default: true,
- },
- available: {
- type: 'boolean',
- title: 'Validation',
- default: false,
+ displayViewFeature: true,
+ location: {
+ hash: '',
+ },
+ feature: {
+ title: 'Title of the feature',
+ display_properties: {
+ 'Group 1': {
+ title: 'Name of the group',
+ slug: 'slug-group',
+ order: 1,
+ pictogram: null,
+ properties: {
+ Numéro: 2,
+ Validation: true,
+ Available: false,
+ Array: [1, 2, 3],
+ EmptyValue: ' ',
+ },
},
- labels: {
- type: 'array',
- items: {
- enum: [
- 'VPAH',
- 'PM 1979',
- 'PM 1981',
- 'PM 1991',
- 'PM 1992',
- ],
- type: 'string',
+ __default__: {
+ title: '',
+ pictogram: null,
+ order: 9999,
+ properties: {
+ 'key without value': null,
+ 'Hello foo bar': 'Value hello foo bar',
+ Contact: 'Foo contact',
},
- title: 'Labels',
- uniqueItems: true,
- default: ['PM 1979', 'PM 1981'],
},
},
},
- displayViewFeature: true,
- layer: {},
- feature: {
- id: undefined,
- },
};
diff --git a/src/modules/CRUD/components/Details/Read/__snapshots__/Read.test.js.snap b/src/modules/CRUD/components/Details/Read/__snapshots__/Read.test.js.snap
index 1a4162b05..88a4a0089 100644
--- a/src/modules/CRUD/components/Details/Read/__snapshots__/Read.test.js.snap
+++ b/src/modules/CRUD/components/Details/Read/__snapshots__/Read.test.js.snap
@@ -16,7 +16,7 @@ exports[`should render correctly 1`] = `
- Cathedrale Saint-Caprais
+ Title of the feature
DownloadButtons
@@ -25,125 +25,157 @@ exports[`should render correctly 1`] = `
-
- Foo Title
-
-
- -
-
- Ville
-
-
-
-
-
- -
-
- Nom
-
-
-
-
-
- -
-
- Description
-
-
- CRUD.details.noFeature
+
+ -
+
+ Name of the group
-
- -
-
- Numéro
-
-
- 2
-
-
- -
-
- Validation
-
-
- CRUD.details.true
-
+
-
+
+ Numéro
+
+
+ 2
+
+
+ -
+
+ Validation
+
+
+ CRUD.details.true
+
+
+ -
+
+ Available
+
+
+ CRUD.details.false
+
+
+ -
+
+ Array
+
+
+ 1, 2, 3
+
+
+ -
+
+ EmptyValue
+
+
+
+
+
+
- -
-
- Validation
-
-
- CRUD.details.false
+
-
+
+ CRUD.details.other
-
- -
-
- Labels
-
-
- PM 1979, PM 1981
-
+
-
+
+ key without value
+
+
+ CRUD.details.noFeature
+
+
+ -
+
+ Hello foo bar
+
+
+
+
+
+ -
+
+ Contact
+
+
+
+
+
+
diff --git a/src/modules/CRUD/services/features.js b/src/modules/CRUD/services/features.js
index 723eaa5b6..005c18ee6 100644
--- a/src/modules/CRUD/services/features.js
+++ b/src/modules/CRUD/services/features.js
@@ -1,19 +1,19 @@
import Api from '@terralego/core/modules/Api';
export const fetchFeaturesList = layerId =>
- Api.request(`layer/${layerId}/feature/`);
+ Api.request(`crud/layer/${layerId}/features/`);
export const fetchFeature = (layerId, featureId) =>
- Api.request(`layer/${layerId}/feature/${featureId}/`);
+ Api.request(`crud/layer/${layerId}/features/${featureId}/`);
const createFeature = (layerId, body) =>
- Api.request(`layer/${layerId}/feature/`, { method: 'POST', body });
+ Api.request(`crud/layer/${layerId}/features/`, { method: 'POST', body });
const updateFeature = (layerId, featureId, body) =>
- Api.request(`layer/${layerId}/feature/${featureId}/`, { method: 'PUT', body });
+ Api.request(`crud/layer/${layerId}/features/${featureId}/`, { method: 'PUT', body });
export const deleteFeature = (layerId, featureId) =>
- Api.request(`layer/${layerId}/feature/${featureId}/`, { method: 'DELETE' });
+ Api.request(`crud/layer/${layerId}/features/${featureId}/`, { method: 'DELETE' });
export const saveFeature = (layerId, featureId, body) => (
featureId
diff --git a/src/modules/CRUD/services/features.test.js b/src/modules/CRUD/services/features.test.js
index d1ff8c2fa..5df08c336 100644
--- a/src/modules/CRUD/services/features.test.js
+++ b/src/modules/CRUD/services/features.test.js
@@ -8,27 +8,27 @@ jest.mock('@terralego/core/modules/Api', () => ({
it('should fetch list of feature', () => {
fetchFeaturesList('foo');
- expect(Api.request).toHaveBeenCalledWith('layer/foo/feature/');
+ expect(Api.request).toHaveBeenCalledWith('crud/layer/foo/features/');
});
it('should fetch a feature', () => {
fetchFeature('foo', '1337');
- expect(Api.request).toHaveBeenCalledWith('layer/foo/feature/1337/');
+ expect(Api.request).toHaveBeenCalledWith('crud/layer/foo/features/1337/');
});
it('should delete a feature', () => {
deleteFeature('foo', '1337');
- expect(Api.request).toHaveBeenCalledWith('layer/foo/feature/1337/', { method: 'DELETE' });
+ expect(Api.request).toHaveBeenCalledWith('crud/layer/foo/features/1337/', { method: 'DELETE' });
});
it('should create a feature', () => {
saveFeature('foo', false, { bar: 'bar' });
- expect(Api.request).toHaveBeenCalledWith('layer/foo/feature/', { method: 'POST', body: { bar: 'bar' } });
+ expect(Api.request).toHaveBeenCalledWith('crud/layer/foo/features/', { method: 'POST', body: { bar: 'bar' } });
});
it('should update a feature', () => {
saveFeature('foo', '1337', { bar: 'bar' });
- expect(Api.request).toHaveBeenCalledWith('layer/foo/feature/1337/', { method: 'PUT', body: { bar: 'bar' } });
+ expect(Api.request).toHaveBeenCalledWith('crud/layer/foo/features/1337/', { method: 'PUT', body: { bar: 'bar' } });
});
it('should get bounds', () => {