Commit 8d61eeec authored by Matija Obreza's avatar Matija Obreza

Merge branch '116-crops-module' into 'master'

Crops module

Closes #116

See merge request !95
parents 07aae561 3371c805
......@@ -72,6 +72,10 @@
"secondaryEmail": "Secondary Email",
"webCooperator": "Web Cooperator"
},
"Crop": {
"id": "Crop ID",
"name": "Crop"
},
"Geography": {
"geography": "Geography",
"currentGeography": "Current Valid Geography",
......
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
/**
* Crop
*
* GRIN-Global CE API
*/
class Crop {
public createdBy: number;
public createdDate: Date;
public modifiedBy: number;
public modifiedDate: Date;
public ownedBy: Cooperator;
public ownedDate: Date;
public id: number;
public name: string;
public note: string;
}
export default Crop;
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
import Crop from '@gringlobal/client/model/gringlobal/Crop';
import RepositoryFile from '@gringlobal/client/model/repository/RepositoryFile';
/**
* CropAttach
*
* GRIN-Global CE API
*/
class CropAttach {
public createdBy: number;
public createdDate: Date;
public modifiedBy: number;
public modifiedDate: Date;
public ownedBy: Cooperator;
public ownedDate: Date;
public title: string;
public virtualPath: string;
public thumbnailVirtualPath: string;
public contentType: string;
public isWebVisible: string;
public sortOrder: number;
public note: string;
public repositoryFile: RepositoryFile;
public id: number;
public attachCooperator: Cooperator;
public attachDate: Date;
public attachDateCode: string;
public categoryCode: string;
public crop: Crop;
public description: string;
}
export default CropAttach;
import CropAttach from '@gringlobal/client/model/gringlobal/CropAttach';
import RepositoryFile from '@gringlobal/client/model/repository/RepositoryFile';
/**
* CropAttachmentRequest
*
* GRIN-Global CE API
*/
class CropAttachmentRequest {
public fileMetadata: RepositoryFile;
public attachMetadata: CropAttach;
}
export default CropAttachmentRequest;
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
import CropAttach from '@gringlobal/client/model/gringlobal/CropAttach';
/**
* CropDetails
*
* GRIN-Global CE API
*/
class CropDetails {
public createdBy: number;
public createdDate: Date;
public modifiedBy: number;
public modifiedDate: Date;
public ownedBy: Cooperator;
public ownedDate: Date;
public id: number;
public name: string;
public note: string;
public attachments: CropAttach[];
}
export default CropDetails;
import CooperatorFilter from '@gringlobal/client/model/gringlobal/CooperatorFilter';
import DateFilter from '@gringlobal/client/model/common/DateFilter';
import StringFilter from '@gringlobal/client/model/common/StringFilter';
/**
* CropFilter
*
* GRIN-Global CE API
*/
class CropFilter {
public NOT: CropFilter;
public NULL: string[];
public NOTNULL: string[];
public id: number[];
public createdBy: number[];
public createdDate: DateFilter;
public modifiedBy: number[];
public modifiedDate: DateFilter;
public ownedBy: CooperatorFilter;
public ownedDate: DateFilter;
public name: StringFilter;
}
export default CropFilter;
......@@ -61,6 +61,10 @@
"secondaryEmail": "Secondary Email",
"webCooperator": "Web Cooperator"
},
"Crop": {
"id": "Crop ID",
"name": "Crop"
},
"Geography": {
"geography": "Geography",
"currentGeography": "Current Valid Geography",
......
import * as UrlTemplate from 'url-template';
import * as QueryString from 'query-string';
import { AxiosInstance, AxiosRequestConfig } from 'axios';
import { FilteredPage, IPageRequest, Page } from '@gringlobal/client/model/page';
import Crop from '@gringlobal/client/model/gringlobal/Crop';
import CropFilter from '@gringlobal/client/model/gringlobal/CropFilter';
import TaxonomySpeciesFilter from '@gringlobal/client/model/gringlobal/TaxonomySpeciesFilter';
import CropDetails from '@gringlobal/client/model/gringlobal/CropDetails';
import CropAttachmentRequest from '@gringlobal/client/model/gringlobal/CropAttachmentRequest';
import CropAttach from '@gringlobal/client/model/gringlobal/CropAttach';
const URL_REMOVE_FILE = UrlTemplate.parse('/api/v1/crop/attach/{cropId}/{attachmentId}');
const URL_UPLOAD_FILE = UrlTemplate.parse('/api/v1/crop/attach/{cropId}');
const URL_LIST_CROP_SPECIES = UrlTemplate.parse('/api/v1/crop/species/{cropId}');
const URL_CROP_DETAILS = UrlTemplate.parse('/api/v1/crop/details/{id}');
const URL_GET = UrlTemplate.parse('/api/v1/crop/{id}');
const URL_DELETE_CROP = UrlTemplate.parse('/api/v1/crop/{id}');
const URL_UPDATE_CROP = '/api/v1/crop';
const URL_CREATE_CROP = '/api/v1/crop';
const URL_LIST_CROPS = '/api/v1/crop/list';
const URL_FILTER_CROPS = '/api/v1/crop/filter';
/**
* Crop service
*
* GRIN-Global CE API
*/
class CropService {
private _axios: AxiosInstance;
public constructor(axios: AxiosInstance) {
this._axios = axios;
}
/**
* cropDetails at /api/v1/crop/details/{id}
*
* @param id undefined
* @param xhrConfig additional xhr config
*/
public cropDetails = (id: number, xhrConfig?: AxiosRequestConfig): Promise<CropDetails> => {
const apiUrl = URL_CROP_DETAILS.expand({ id });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as CropDetails);
};
/**
* removeFile at /api/v1/crop/attach/{cropId}/{attachmentId}
*
* @param attachmentId undefined
* @param cropId undefined
* @param xhrConfig additional xhr config
*/
public removeFile = (attachmentId: number, cropId: number, xhrConfig?: AxiosRequestConfig): Promise<any>=> {
const apiUrl = URL_REMOVE_FILE.expand({ attachmentId, cropId });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'DELETE',
...content,
}).then(({ data }) => data as CropAttach);
}
/**
* uploadFile at /api/v1/crop/attach/{cropId}
*
* @param file File
* @param metadata metadata
* @param cropId crop ID
* @param xhrConfig additional xhr config
*/
public uploadFile = (file: File, metadata: CropAttachmentRequest, cropId: number, xhrConfig?: AxiosRequestConfig): Promise<any>=> {
const apiUrl = URL_UPLOAD_FILE.expand({ cropId });
// console.log(any`Fetching from ${apiUrl}`);
const data = new FormData();
data.append('file', file);
if (metadata) {
data.append('metadata', new Blob([ JSON.stringify(metadata) ], { type : 'application/json' }));
}
const content = { data };
const oldHeaders = xhrConfig && xhrConfig.headers || {};
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
headers: { ...oldHeaders, 'Content-Type': 'multipart/form-data' },
...content,
}).then(({ data }) => {
return data as CropAttach;
});
};
/**
* listCropSpecies at /api/v1/crop/species/{cropId}
*
* @param data Request body
* @param cropId undefined
* @param xhrConfig additional xhr config
*/
public listCropSpecies = (data: TaxonomySpeciesFilter, cropId: number, xhrConfig?: AxiosRequestConfig): Promise<any> => {
const apiUrl = URL_LIST_CROP_SPECIES.expand({ cropId });
// console.log(`Fetching from ${apiUrl}`);
const content = { data };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as undefined);
}
/**
* get_2 at /api/v1/crop/{id}
*
* @param id undefined
* @param xhrConfig additional xhr config
*/
public get = (id: number, xhrConfig?: AxiosRequestConfig): Promise<Crop>=> {
const apiUrl = URL_GET.expand({ id });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as Crop);
}
/**
* deleteCrop at /api/v1/crop/{id}
*
* @param id undefined
* @param xhrConfig additional xhr config
*/
public deleteCrop = (id: number, xhrConfig?: AxiosRequestConfig): Promise<Crop>=> {
const apiUrl = URL_DELETE_CROP.expand({ id });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'DELETE',
...content,
}).then(({ data }) => data as Crop);
}
/**
* updateCrop at /api/v1/crop
*
* @param data Request body
* @param xhrConfig additional xhr config
*/
public updateCrop = (data: Crop, xhrConfig?: AxiosRequestConfig): Promise<Crop>=> {
const apiUrl = URL_UPDATE_CROP;
// console.log(`Fetching from ${apiUrl}`);
const content = { data };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'PUT',
...content,
}).then(({ data }) => data as Crop);
}
/**
* createCrop at /api/v1/crop
*
* @param data Request body
* @param xhrConfig additional xhr config
*/
public createCrop = (data: Crop, xhrConfig?: AxiosRequestConfig): Promise<Crop>=> {
const apiUrl = URL_CREATE_CROP;
// console.log(`Fetching from ${apiUrl}`);
const content = { data };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as Crop);
}
/**
* listCrops at /api/v1/crop/list
*
* @param data Request body
* @param page undefined
* @param xhrConfig additional xhr config
*/
public listCrops = (data: CropFilter, page?: IPageRequest, xhrConfig?: AxiosRequestConfig): Promise<Page<Crop>> => {
const qs = QueryString.stringify({
p: page.page || undefined,
l: page.size || undefined,
d: page.direction ? page.direction : undefined,
s: page.properties || undefined,
}, {});
const apiUrl = URL_LIST_CROPS + (qs ? `?${qs}` : '');
// console.log(`Fetching from ${apiUrl}`);
const content = { data };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as Page<Crop>);
}
/**
* filterCrops at /api/v1/crop/filter
*
* @param data Request body
* @param f undefined
* @param page undefined
* @param xhrConfig additional xhr config
*/
public filterCrops = (data: CropFilter, f?: string, page?: IPageRequest, xhrConfig?: AxiosRequestConfig): Promise<FilteredPage<Crop>> => {
const qs = QueryString.stringify({
f: f || undefined,
p: page.page || undefined,
l: page.size || undefined,
d: page.direction ? page.direction : undefined,
s: page.properties || undefined,
}, {});
const apiUrl = URL_FILTER_CROPS + (qs ? `?${qs}` : '');
// console.log(`Fetching from ${apiUrl}`);
const content = { data };
return this._axios.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as FilteredPage<Crop>);
}
}
export default CropService;
......@@ -14,6 +14,7 @@ import SystemStatusService from '@gringlobal/client/service/SystemStatusService'
import RepositoryService from '@gringlobal/client/service/RepositoryService';
import GeographyService from '@gringlobal/client/service/GeographyService';
import OAuthManagementService from '@gringlobal/client/service/OAuthManagementService';
import CropService from '@gringlobal/client/service/CropService';
import axios from 'axios';
import { clearCookies } from '@gringlobal/client/utilities';
import { ApiError } from '@gringlobal/client/model/common';
......@@ -101,6 +102,7 @@ const ConfiguredRepositoryService = new RepositoryService(serviceAxios);
const ConfiguredMethodService = new MethodService(serviceAxios);
const ConfiguredGeographyService = new GeographyService(serviceAxios);
const ConfiguredOAuthManagementService = new OAuthManagementService(serviceAxios);
const ConfiguredCropService = new CropService(serviceAxios);
export {
ConfiguredCooperatorService as CooperatorService,
......@@ -119,4 +121,5 @@ export {
ConfiguredMethodService as MethodService,
ConfiguredGeographyService as GeographyService,
ConfiguredOAuthManagementService as OAuthManagementService,
ConfiguredCropService as CropService,
}
......@@ -2,6 +2,7 @@
"navigation": {
"accessionAction": "Accession actions",
"admin": "Admin tools",
"crops": "Crops",
"home": "Home",
"cooperators": "Cooperators",
"users": "Users",
......@@ -94,6 +95,10 @@
"todo": {
"title": "TODO",
"description": "Suggest a new card!"
},
"crop": {
"title": "Crops",
"description": "Browse all crops"
}
}
}
......@@ -152,9 +157,6 @@
"c": {
"filters": {
"text": "Full-text search"
},
"attachmentsDisplay": {
"files": "Files"
}
}
}
......@@ -179,6 +181,23 @@
}
}
},
"crop": {
"public": {
"p": {
"browse": {
"title": "List of crops"
},
"details": {
"title": "Crop details",
"attachments": "Attachments",
"tabs": {
"traits": "Traits",
"species": "Species"
}
}
}
}
},
"inventory": {
"public": {
"p": {
......@@ -453,6 +472,9 @@
"form": {
"name": "Folder name"
}
},
"attachmentsDisplay": {
"files": "Files"
}
}
},
......
......@@ -30,9 +30,6 @@
"c": {
"filters": {
"text": "Full-text search"
},
"attachmentsDisplay": {
"files": "Files"
}
}
}
......
......@@ -32,7 +32,7 @@ import { CodeValueDisplay } from 'common/CodeValue';
import ButtonBar from '@gringlobal/client/ui/common/button/ButtonBar';
import PageTitle from '@gringlobal/client/ui/common/PageTitle';
import FileUploader from '@gringlobal/client/ui/common/file-uploader';
import AttachmentsDisplay from 'accession/ui/c/AttachmentsDisplay';
import AttachmentsDisplay from 'repository/ui/c/AttachmentsDisplay';
const InventoryTableConfig = new TableConfiguration(TableConfiguration.merge(
......
......@@ -15,6 +15,8 @@ import { requestPublicSagas } from 'request/action/public';
import { kpiAdminSagas } from 'kpi/action/admin';
import { siteAdminSagas } from 'site/action/admin';
import { repositoryAdminSagas } from 'repository/action/admin';
import { cropPublicSagas } from 'crop/action/public';
import { cropAdminSagas } from 'crop/action/admin';
import { AxiosRequestConfig } from 'axios';
......@@ -29,11 +31,13 @@ export default function*() {
...inventoryPublicSagas,
...inventoryGroupPublicSagas,
...requestPublicSagas,
...cropPublicSagas,
...siteAdminSagas,
...kpiAdminSagas,
...repositoryAdminSagas,
...userAdminSagas,
...cropAdminSagas,
// if action has method
takeEvery((action) => action.type === 'API' && !! action.method, appendAxiosConfig),
......
......@@ -16,6 +16,7 @@ import request from 'request/reducer';
import site from 'site/reducer';
import kpi from 'kpi/reducer';
import repository from 'repository/reducer';
import crop from 'crop/reducer';
const rootReducer = (history?) => (combineReducers({
// express reducers
......@@ -34,6 +35,7 @@ const rootReducer = (history?) => (combineReducers({
site,
kpi,
repository,
crop,
...coreReducers(history),
}));
......
import { put, takeEvery, call, take } from 'redux-saga/effects';
// Constants
import {
ADMIN_RECEIVE_CROP,
SAGA_ADMIN_CREATE_CROP,
SAGA_ADMIN_RECEIVE_CROP,
SAGA_ADMIN_EDIT_CROP,
} from 'crop/constants';
// Model
import Crop from '@gringlobal/client/model/gringlobal/Crop';
// Service
import { CropService } from '@gringlobal/client/service';
import { sagaNavigate } from '@gringlobal/client/action/navigation';
import { receiveCropDetailsSaga } from './public';
export const cropAdminSagas = [
takeEvery(SAGA_ADMIN_CREATE_CROP, createCropSaga),
takeEvery(SAGA_ADMIN_EDIT_CROP, editCropSaga),
takeEvery(SAGA_ADMIN_RECEIVE_CROP, getCropSaga),
];
export const createCropAction = (crop: Crop) => ({
type: SAGA_ADMIN_CREATE_CROP,
payload: { crop },
});
export const editCropAction = (crop: Crop) => ({
type: SAGA_ADMIN_EDIT_CROP,
payload: { crop },
});
export const getCropActionAdmin = (id: string | number) => ({
type: SAGA_ADMIN_RECEIVE_CROP,
payload: { id },
});
function* createCropSaga(action) {
yield put({
type: 'API',
target: ADMIN_RECEIVE_CROP,
method: CropService.createCrop,
params: [action.payload.crop],
onSuccess: (crop: Crop) => {
return crop;
},
});
yield take(ADMIN_RECEIVE_CROP);