Commit 359d0805 authored by Oleksii Savran's avatar Oleksii Savran

Merge branch '123-inventory-maintenance-policy' into 'master'

Inventory maintenance policy

Closes #123

See merge request !101
parents d3e29bb8 53be5f1c
......@@ -37,6 +37,30 @@
"sid": "SID",
"description": "Description"
},
"name": {
"Accession": "Accession",
"Accession_plural": "Accessions",
"CodeValue": "Code value",
"CodeValue_plural": "Code values",
"Cooperator": "Cooperator",
"Cooperator_plural": "Cooperators",
"Crop": "Crop",
"Crop_plural": "Crops",
"Inventory": "Inventory",
"Inventory_plural": "Inventories",
"InventoryGroup": "Inventory group",
"InventoryMaintenancePolicy": "Inventory maintenance policy",
"InventoryMaintenancePolicy_plural": "Inventory maintenance policies",
"InventoryGroup_plural": "Inventory groups",
"OrderRequest": "Request",
"OrderRequest_plural": "Requests",
"OAuthClient": "OAuth client",
"OAuthClient_plural": "OAuth clients",
"Site": "Site",
"Site_plural": "Sites",
"User": "User",
"User_plural": "Users"
},
"Cooperator": {
"id": "Cooperator ID",
"currentCooperator": "Current Cooperator",
......@@ -369,6 +393,20 @@
"completedDate": "Completed Date",
"completedDateCode": "Completed Date Format"
},
"InventoryMaintenancePolicy": {
"curatorCooperator": "Curator",
"distributionCriticalQuantity": "Distribution critical quantity",
"distributionDefaultFormCode": "Distribution default form code",
"distributionDefaultQuantity": "Distribution default quantity",
"distributionUnitCode": "Distribution unit code",
"formTypeCode": "Form type code",
"isAutoDeducted": "Is auto deducted",
"maintenanceName": "Maintenance name",
"quantityOnHandUnitCode": "Quantity on hand unit code",
"regenerationCriticalQuantity": "Regeneration critical quantity",
"regenerationMethodCode": "Regeneration method code",
"webAvailabilityNote": "Web availability note"
},
"OrderRequest": {
"id": "Order Request ID",
"originalOrderRequest": "Original Order",
......
......@@ -23,6 +23,8 @@
"label": {
"allRightsReserved": "All rights reserved",
"deleteDescription": "Deleting this will remove all related data.",
"deleteItemConfirm": "Do you really want to delete {{what, lowercase}}: {{title}}?",
"deleteListConfirm": "Do you really want to delete {{count}} {{what, lowercase}}?",
"description": "Description",
"either": "Either",
"errorHappened": "Error happened while processing request",
......
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
export default class AuditedModel {
public createdBy: number | Cooperator;
public createdDate: Date;
public modifiedBy: number | Cooperator;
public modifiedDate: Date;
}
import AuditedModel from '@gringlobal/client/model/common/AuditedModel';
import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
export default class CooperatorOwnedModel extends AuditedModel {
public ownedBy: Cooperator;
public ownedDate: Date;
}
......@@ -6,6 +6,8 @@ import Cooperator from '@gringlobal/client/model/gringlobal/Cooperator';
* GRIN-Global CE API
*/
class InventoryMaintenancePolicy {
public static clazz: string = 'org.gringlobal.model.InventoryMaintenancePolicy';
public createdBy: Cooperator;
public createdDate: Date;
public modifiedBy: Cooperator;
......@@ -27,7 +29,13 @@ class InventoryMaintenancePolicy {
public regenerationMethodCode: string;
public webAvailabilityNote: string;
public static CodeValues = {
distributionDefaultFormCode: 'GERMPLASM_FORM',
distributionUnitCode: 'UNIT_OF_QUANTITY',
quantityOnHandUnitCode: 'UNIT_OF_QUANTITY',
regenerationMethodCode: 'REGENERATION_METHOD',
formTypeCode: 'GERMPLASM_FORM',
}
}
export default InventoryMaintenancePolicy;
......@@ -26,6 +26,30 @@
"sid": "SID",
"description": "Description"
},
"name": {
"Accession": "Accession",
"Accession_plural": "Accessions",
"CodeValue": "Code value",
"CodeValue_plural": "Code values",
"Cooperator": "Cooperator",
"Cooperator_plural": "Cooperators",
"Crop": "Crop",
"Crop_plural": "Crops",
"Inventory": "Inventory",
"Inventory_plural": "Inventories",
"InventoryGroup": "Inventory group",
"InventoryMaintenancePolicy": "Inventory maintenance policy",
"InventoryMaintenancePolicy_plural": "Inventory maintenance policies",
"InventoryGroup_plural": "Inventory groups",
"OrderRequest": "Request",
"OrderRequest_plural": "Requests",
"OAuthClient": "OAuth client",
"OAuthClient_plural": "OAuth clients",
"Site": "Site",
"Site_plural": "Sites",
"User": "User",
"User_plural": "Users"
},
"Cooperator": {
"id": "Cooperator ID",
"currentCooperator": "Current Cooperator",
......@@ -358,6 +382,20 @@
"completedDate": "Completed Date",
"completedDateCode": "Completed Date Format"
},
"InventoryMaintenancePolicy": {
"curatorCooperator": "Curator",
"distributionCriticalQuantity": "Distribution critical quantity",
"distributionDefaultFormCode": "Distribution default form code",
"distributionDefaultQuantity": "Distribution default quantity",
"distributionUnitCode": "Distribution unit code",
"formTypeCode": "Form type code",
"isAutoDeducted": "Is auto deducted",
"maintenanceName": "Maintenance name",
"quantityOnHandUnitCode": "Quantity on hand unit code",
"regenerationCriticalQuantity": "Regeneration critical quantity",
"regenerationMethodCode": "Regeneration method code",
"webAvailabilityNote": "Web availability note"
},
"OrderRequest": {
"id": "Order Request ID",
"originalOrderRequest": "Original Order",
......
......@@ -302,7 +302,7 @@ class InventoryService {
* @param data Request body
* @param xhrConfig additional xhr config
*/
public update_3 = (data: InventoryMaintenancePolicy, xhrConfig?: AxiosRequestConfig): Promise<InventoryMaintenancePolicy> => {
public updateInventoryMaintenancePolicy = (data: InventoryMaintenancePolicy, xhrConfig?: AxiosRequestConfig): Promise<InventoryMaintenancePolicy> => {
const apiUrl = URL_UPDATE_3;
// console.log(`Fetching from ${apiUrl}`);
......
......@@ -11,6 +11,7 @@
"inventory": "Inventory",
"inventorygroup": "Groups",
"inventoryAction": "Inventory actions",
"inventoryPolicy": "Inventory maintenance policy",
"taxonomyGenus": "Genera",
"taxonomySpecies": "Species",
"request": "Requests"
......@@ -61,7 +62,7 @@
"description": "Overview of the inventory data"
},
"inventorygroup": {
"title": "Inventory Groups",
"title": "Inventory groups",
"description": "Browse inventory groups"
},
"inventoryadjust": {
......@@ -277,8 +278,7 @@
},
"details": {
"title": "Inventory group",
"members": "Members",
"delete": "Do you really want to delete {{name}} group?"
"members": "Members"
},
"acquisition": {
"names": "Names",
......@@ -301,6 +301,21 @@
"method": "Acquisition method"
}
},
"inventorypolicy": {
"admin": {
"p": {
"browse": {
"title": "List of inventory maintenance policy"
},
"details": {
"title": "Inventory maintenance policy"
},
"edit": {
"title": "New inventory maintenance policy"
}
}
}
},
"kpi": {
"admin": {
"c": {
......@@ -582,10 +597,12 @@
"title": "New site location"
},
"browse": {
"title": "List of sites"
"title": "List of sites",
"delete": "Do you really want to delete {{count}} sites?"
},
"details": {
"title": "Site"
"title": "Site",
"delete": "Do you really want to delete {{name}} site?"
},
"edit": {
"title": "New site"
......
......@@ -22,7 +22,6 @@ import { FilteredPage } from '@gringlobal/client/model/page';
import Loading from '@gringlobal/client/ui/common/Loading';
import { Card, CardContent, CardHeader, CardActions, Button } from '@material-ui/core';
import { Properties, PropertiesItem } from '@gringlobal/client/ui/common/Properties';
import { Link } from 'react-router-dom';
import PrettyDate from '@gringlobal/client/ui/common/time/PrettyDate';
import Table, { TextAlign } from '@gringlobal/client/ui/common/table/Table';
import { PrintSpecies } from 'common/Taxonomy';
......@@ -33,6 +32,7 @@ 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 'repository/ui/c/AttachmentsDisplay';
import AuditDataDisplay from 'common/AuditDataDisplay';
const InventoryTableConfig = new TableConfiguration(TableConfiguration.merge(
......@@ -243,39 +243,7 @@ class AccessionDetailsPage extends React.Component<IDetailsPageProps> {
{ accession.note }
</PropertiesItem>
}
{ accession.createdDate &&
<PropertiesItem title={ t('client:model._.createdDate') }>
<PrettyDate value={ accession.createdDate } />
</PropertiesItem>
}
{ /* accession.createdBy &&
<PropertiesItem title={ t('client:model._.createdBy') }>
{ accession.createdBy }
</PropertiesItem>
*/ }
{ accession.modifiedDate &&
<PropertiesItem title={ t('client:model._.modifiedDate') }>
<PrettyDate value={ accession.modifiedDate } />
</PropertiesItem>
}
{ /* accession.modifiedBy &&
<PropertiesItem title={ t('client:model._.modifiedBy') }>
{ accession.modifiedBy }
</PropertiesItem>
*/ }
{ accession.ownedDate &&
<PropertiesItem title={ t('client:model._.ownedDate') }>
<PrettyDate value={ accession.ownedDate } />
</PropertiesItem>
}
{ accession.ownedBy &&
<PropertiesItem title={ t('client:model._.ownedBy') }>
<Link to={ `/cooperator/${ accession.ownedBy.id }` }>
{ accession.ownedBy.firstName }
</Link>
</PropertiesItem>
}
<AuditDataDisplay item={ accession }/>
</Properties>
</CardContent>
</Card>
......
import * as React from 'react';
import { createStyles, withStyles, WithStyles } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import { PropertiesItem } from '@gringlobal/client/ui/common/Properties';
import PrettyDate from '@gringlobal/client/ui/common/time/PrettyDate';
import { CooperatorLink } from 'ui/common/Links';
import CooperatorOwnedModel from '@gringlobal/client/model/common/CooperatorOwnedModel';
const styles = (theme) => createStyles({});
interface IAuditDataDisplay extends React.ClassAttributes<any> {
item: CooperatorOwnedModel & Record<string, any>;
withoutOwnedFields?: boolean;
withoutAuditFields?: boolean;
}
function AuditDataDisplay(props: IAuditDataDisplay & Partial<WithStyles>) {
const { item, withoutOwnedFields = false, withoutAuditFields = false } = props;
const { t } = useTranslation();
return (
<>
{ !withoutAuditFields &&
<>
{ item.createdDate &&
<PropertiesItem title={ t('client:model._.createdDate') }>
<PrettyDate value={ item.createdDate } />
</PropertiesItem>
}
{ item.modifiedDate &&
<PropertiesItem title={ t('client:model._.modifiedDate') }>
<PrettyDate value={ item.modifiedDate } />
</PropertiesItem>
}
</>
}
{ !withoutOwnedFields &&
<>
{ item.ownedDate &&
<PropertiesItem title={ t('client:model._.ownedDate') }>
<PrettyDate value={ item.ownedDate } />
</PropertiesItem>
}
{ item.ownedBy &&
<PropertiesItem title={ t('client:model._.ownedBy') }>
<CooperatorLink cooperator={ item.ownedBy }/>
</PropertiesItem>
}
</>
}
</>
);
}
export default withStyles(styles)(AuditDataDisplay);
......@@ -66,7 +66,7 @@ class CodeValueField extends React.Component<ICPlProps & WithStyles & WithTransl
fullWidth
variant="filled"
{ ...input }
error={ meta && !!meta.error }
error={ meta && meta.touched && !!meta.error }
>
{ options ? Object.keys(options).map((key, i) => (
<MenuItem key={ `option-${key}-${i}` } value={ key }>
......
......@@ -6,6 +6,7 @@ import { FilteredPage } from '@gringlobal/client/model/page';
import { ListItemText } from '@material-ui/core';
import { CooperatorService } from '@gringlobal/client/service';
import Autocomplete from '@gringlobal/client/ui/common/form/Autocomplete';
import SysLang from '@gringlobal/client/model/gringlobal/SysLang';
interface ICoopAutocomplete {
label: string;
......@@ -33,7 +34,18 @@ class CooperatorAutocomplete extends React.Component<ICoopAutocomplete & FieldRe
);
};
private mapOptions = (page: FilteredPage<Cooperator>): Cooperator[] => page.content;
private mapOptions = (page: FilteredPage<Cooperator>): Cooperator[] => {
page.content.forEach((coop, i) => {
if (coop.ownedBy) {
const coopId = coop.ownedBy.id;
(coop.ownedBy as any) = coopId; // fixes cycle references
}
const sysLangId = coop.sysLang;
coop.sysLang = new SysLang();
(coop.sysLang as any).id = sysLangId;
});
return page.content;
};
private getHelperText = (cooperator: Cooperator): string => cooperator &&
(cooperator.lastName ? cooperator.organization || '' : '')
......
......@@ -17,8 +17,8 @@ import { Properties, PropertiesItem } from '@gringlobal/client/ui/common/Propert
import Loading from '@gringlobal/client/ui/common/Loading';
import PageTitle from '@gringlobal/client/ui/common/PageTitle';
import Email from '@gringlobal/client/ui/common/Email';
import PrettyDate from '@gringlobal/client/ui/common/time/PrettyDate';
import { CodeValueDisplay } from 'common/CodeValue';
import AuditDataDisplay from 'common/AuditDataDisplay';
interface ICooperatorDetailsPage extends React.ClassAttributes<any>, WithTranslation {
......@@ -178,11 +178,7 @@ class CooperatorDetailsPage extends React.Component<ICooperatorDetailsPage> {
<PropertiesItem title={ t(['client:model.Cooperator.site', 'client:model._.site']) }>
{ cooperator.site && <>{ cooperator.site.siteShortName }</> }
</PropertiesItem>
{ [ 'createdDate', 'modifiedDate' ].map((property) => (
<PropertiesItem key={ property } title={ t([`client:model.Cooperator.${property}`, `client:model._.${property}`]) }>
{ cooperator[property] && <PrettyDate value={ cooperator[property] } /> }
</PropertiesItem>
)) }
<AuditDataDisplay item={ cooperator } withoutOwnedFields/>
</Properties>
</CardContent>
</Card>
......
......@@ -17,6 +17,7 @@ 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 { inventoryPolicyAdminSagas } from 'inventorypolicy/action/admin';
import { AxiosRequestConfig } from 'axios';
......@@ -38,6 +39,7 @@ export default function*() {
...repositoryAdminSagas,
...userAdminSagas,
...cropAdminSagas,
...inventoryPolicyAdminSagas,
// if action has method
takeEvery((action) => action.type === 'API' && !! action.method, appendAxiosConfig),
......
......@@ -17,6 +17,7 @@ import site from 'site/reducer';
import kpi from 'kpi/reducer';
import repository from 'repository/reducer';
import crop from 'crop/reducer';
import inventoryPolicy from 'inventorypolicy/reducer';
const rootReducer = (history?) => (combineReducers({
// express reducers
......@@ -30,6 +31,7 @@ const rootReducer = (history?) => (combineReducers({
taxonomy,
inventory,
inventorygroup,
inventoryPolicy,
request,
user,
site,
......
......@@ -23,7 +23,7 @@ import TabPanel from '@gringlobal/client/ui/common/tabs/TabPanel';
import { Properties, PropertiesItem } from '@gringlobal/client/ui/common/Properties';
import PrettyDate from '@gringlobal/client/ui/common/time/PrettyDate';
import { PrintSpecies } from 'common/Taxonomy';
import { AccessionLink, CooperatorLink, InventoryLink } from 'ui/common/Links';
import { AccessionLink, InventoryLink } from 'ui/common/Links';
import ButtonBar from '@gringlobal/client/ui/common/button/ButtonBar';
import { CodeValueDisplay } from 'common/CodeValue';
import PageTitle from '@gringlobal/client/ui/common/PageTitle';
......@@ -31,6 +31,7 @@ import { BasicInventoryActionsTable as InventoryActionsTable } from 'inventory/
import { InventoryAuditLogsTable } from 'inventory/ui/c/InventoryAuditLogsTable';
import AttachmentsDisplay from 'repository/ui/c/AttachmentsDisplay';
import FileUploader from '@gringlobal/client/ui/common/file-uploader';
import AuditDataDisplay from 'common/AuditDataDisplay';
interface IDetailsPageProps extends React.ClassAttributes<any>, WithTranslation {
......@@ -361,37 +362,7 @@ class InventoryDetailsPage extends React.Component<IDetailsPageProps> {
{ inventory.note }
</PropertiesItem>
}
{ inventory.createdDate &&
<PropertiesItem title={ t(['client:model.Inventory.createdDate', 'client:model._.createdDate']) }>
<PrettyDate value={ inventory.createdDate } />
</PropertiesItem>
}
{ /* inventory.createdBy &&
<PropertiesItem title={ t('client:model.Inventory.createdBy') }>
{ inventory.createdBy }
</PropertiesItem>
*/ }
{ inventory.modifiedDate &&
<PropertiesItem title={ t(['client:model.Inventory.modifiedDate', 'client:model._.modifiedDate']) }>
<PrettyDate value={ inventory.modifiedDate } />
</PropertiesItem>
}
{ /* inventory.modifiedBy &&
<PropertiesItem title={ t('client:model.Inventory.modifiedBy') }>
{ inventory.modifiedBy }
</PropertiesItem>
*/ }
{ inventory.ownedDate &&
<PropertiesItem title={ t(['client:model.Inventory.ownedDate', 'client:model._.ownedDate']) }>
<PrettyDate value={ inventory.ownedDate } />
</PropertiesItem>
}
{ inventory.ownedBy &&
<PropertiesItem title={ t(['client:model.Inventory.ownedBy', 'client:model._.ownedBy']) }>
<CooperatorLink cooperator={ inventory.ownedBy }/>
</PropertiesItem>
}
<AuditDataDisplay item={ inventory }/>
</Properties>
</CardContent>
</Card>
......
......@@ -6,8 +6,7 @@
},
"details": {
"title": "Inventory group",
"members": "Members",
"delete": "Do you really want to delete {{name}} group?"
"members": "Members"
},
"acquisition": {
"names": "Names",
......
......@@ -13,7 +13,7 @@ import TabPanel from '@gringlobal/client/ui/common/tabs/TabPanel';
import { Properties, PropertiesItem } from '@gringlobal/client/ui/common/Properties';
import PrettyDate from '@gringlobal/client/ui/common/time/PrettyDate';
// import { PrintSpecies } from 'common/Taxonomy';
import { CooperatorLink, AccessionLink, InventoryLink } from 'ui/common/Links';
import { AccessionLink, InventoryLink } from 'ui/common/Links';
import AccessionInvGroup from '@gringlobal/client/model/gringlobal/AccessionInvGroup';
import Table, { TextAlign } from '@gringlobal/client/ui/common/table/Table';
import { CooperatorOwnedTableConfiguration as TableConfiguration } from '@gringlobal/client/ui/common/table/TableConfiguration';
......@@ -24,6 +24,7 @@ import { dereferenceReferences3 } from '@gringlobal/client/utilities';
import { Page, SortDirection, IPageRequest } from '@gringlobal/client/model/page';
import AccessionInvGroupMap from '@gringlobal/client/model/gringlobal/AccessionInvGroupMap';
import PageTitle from '@gringlobal/client/ui/common/PageTitle';
import AuditDataDisplay from 'common/AuditDataDisplay';
import EditGroupDialog from 'inventorygroup/ui/c/EditGroupDialog';
import confirm from '@gringlobal/client/utilities/confirmAlert';
......@@ -151,7 +152,7 @@ class InventoryDetailsPage extends React.Component<IDetailsPageProps> {
public handleRemove = () => {
const { removeInventoryGroupAction, t, groupId, inventoryGroupCall } = this.props;
confirm(t('inventorygroup.public.p.details.delete', { name: inventoryGroupCall.data.groupName }), {
confirm(t('common:label.deleteItemConfirm', { title: inventoryGroupCall.data.groupName, what: t('client:model.name.InventoryGroup') }), {
confirmLabel: t('common:label.yes'),
abortLabel: t('common:label.no'),
}).then(() => {
......@@ -228,26 +229,7 @@ class InventoryDetailsPage extends React.Component<IDetailsPageProps> {
<PropertiesItem title={ t(['client:model.AccessionInvGroup.isWebVisible', 'client:model._.isWebVisible']) }>
{ inventoryGroup.isWebVisible }
</PropertiesItem>
{ inventoryGroup.createdDate &&
<PropertiesItem title={ t(['client:model.AccessionInvGroup.createdDate', 'client:model._.createdDate']) }>
<PrettyDate value={ inventoryGroup.createdDate } />
</PropertiesItem>
}
{ inventoryGroup.modifiedDate &&
<PropertiesItem title={ t(['client:model.AccessionInvGroup.modifiedDate', 'client:model._.modifiedDate']) }>
<PrettyDate value={ inventoryGroup.modifiedDate } />
</PropertiesItem>
}
{ inventoryGroup.ownedDate &&
<PropertiesItem title={ t(['client:model.AccessionInvGroup.ownedDate', 'client:model._.ownedDate']) }>
<PrettyDate value={ inventoryGroup.ownedDate } />
</PropertiesItem>
}
{ inventoryGroup.ownedBy &&
<PropertiesItem title={ t(['client:model.AccessionInvGroup.ownedBy', 'client:model._.ownedBy']) }>
<CooperatorLink cooperator={ inventoryGroup.ownedBy }/>
</PropertiesItem>
}
<AuditDataDisplay item={ inventoryGroup }/>
</Properties>
</CardContent>
<CardActions>
......
import { put, call, take, takeEvery } from 'redux-saga/effects';
// Constants
import {
RECEIVE_INVENTORY_POLICIES,
RECEIVE_INVENTORY_POLICY,
REMOVE_INVENTORY_POLICY,
SAGA_REMOVE_INVENTORY_POLICY,
SAGA_RECEIVE_INVENTORY_POLICIES,
SAGA_CREATE_INVENTORY_POLICY,
SAGA_RECEIVE_INVENTORY_POLICY,
SAGA_EDIT_INVENTORY_POLICY,
} from 'inventorypolicy/constants';
// Model
import InventoryMaintenancePolicy from '@gringlobal/client/model/gringlobal/InventoryMaintenancePolicy';
import InventoryMaintenancePolicyFilter from '@gringlobal/client/model/gringlobal/InventoryMaintenancePolicyFilter';
import { IPageRequest, FilteredPage, Page } from '@gringlobal/client/model/page';
// Service
import { InventoryService } from '@gringlobal/client/service';
import { dereferenceReferences3 } from '@gringlobal/client/utilities';
import { sagaNavigate } from '@gringlobal/client/action/navigation';
export const inventoryPolicyAdminSagas = [
takeEvery(SAGA_RECEIVE_INVENTORY_POLICIES, listInventoryPoliciesSaga),
takeEvery(SAGA_CREATE_INVENTORY_POLICY, createInventoryPolicySaga),
takeEvery(SAGA_EDIT_INVENTORY_POLICY, editInventoryPolicySaga),
takeEvery(SAGA_RECEIVE_INVENTORY_POLICY, getInventoryPolicySaga),
takeEvery(SAGA_REMOVE_INVENTORY_POLICY, removeInventoryPolicySaga),
];
export const listInventoryPoliciesAction = (filter: Partial<InventoryMaintenancePolicyFilter> = {}, pageR: IPageRequest = { page: 0, size: 100 }) => ({
type: SAGA_RECEIVE_INVENTORY_POLICIES,
payload: {
filter,
pageR,
},
});
export const loadMoreInventoryPoliciesAction = (page: FilteredPage<InventoryMaintenancePolicy>) => ({
type: SAGA_RECEIVE_INVENTORY_POLICIES,
payload: {
filter: page.filter || {},