RequestDetailsPage.tsx 36.9 KB
Newer Older
Matija Obreza's avatar
Matija Obreza committed
1
import * as React from 'react';
2
import memoize from 'memoize-one';
Matija Obreza's avatar
Matija Obreza committed
3
import { connect } from 'react-redux';
Maksym Tishchenko's avatar
Maksym Tishchenko committed
4
import { compose } from 'redux';
Matija Obreza's avatar
Matija Obreza committed
5
import { WithTranslation, withTranslation } from 'react-i18next';
Matija Obreza's avatar
Matija Obreza committed
6
7
// import update from 'immutability-helper';

8
9
// Model
import {
Maksym Tishchenko's avatar
Maksym Tishchenko committed
10
11
  OrderRequest, Inventory, OrderRequestItem,
  Cooperator, Accession, OrderRequestAction,
12
13
} from '@gringlobal-ce/client/model/gringlobal';
import { OrderRequestItemStatus } from '@gringlobal-ce/client/model/gringlobal/OrderRequestItem';
14
import WebOrderRequestItem from '@gringlobal-ce/client/model/gringlobal/WebOrderRequestItem';
Matija Obreza's avatar
Matija Obreza committed
15
// Action
16
import navigateTo from '@gringlobal-ce/client/action/navigation';
17
18
19
20
21
22
import {
  getOrderRequestAction,
  listOrderRequestItemsAction,
  receiveOrderRequestItemsSuccessAction,
  editOrderRequestAction,
  removeOrderRequestAction,
23
24
  removeOrderRequestItemsAction,
  editOrderRequestItemAction,
Maksym Tishchenko's avatar
Maksym Tishchenko committed
25
26
27
28
  receiveRequestAttachmentSuccessAction,
  removeRequestAttachmentAction,
  removeRequestAttachmentsAction,
  uploadRequestAttachment,
29
} from 'request/action/public';
30
31
import { showSnackbar } from '@gringlobal-ce/client/action/snackbar';
// Service
32
import { RequestService } from '@gringlobal-ce/client/service';
33
// UI
34
import Loading from '@gringlobal-ce/client/ui/common/Loading';
Matija Obreza's avatar
Matija Obreza committed
35
import { Card, CardContent, CardHeader, CardActions, Button } from '@material-ui/core';
36
37
38
import { Properties, PropertiesItem } from '@gringlobal-ce/client/ui/common/Properties';
import Table, { TextAlign } from '@gringlobal-ce/client/ui/common/table/Table';
import { CooperatorOwnedTableConfiguration as TableConfiguration } from '@gringlobal-ce/client/ui/common/table/TableConfiguration';
39
import { CodeValueDisplay } from 'common/CodeValue';
40
41
import ButtonBar from '@gringlobal-ce/client/ui/common/button/ButtonBar';
import PageTitle from '@gringlobal-ce/client/ui/common/PageTitle';
Oleksii Savran's avatar
Oleksii Savran committed
42
import { BasicRequestActionsTable as RequestActionsTable } from 'request/ui/c/RequestActionsTable';
43
import { BasicRequestItemActionsTable as RequestItemActionsTable } from 'request/ui/c/RequestItemActionsTable';
Oleksii Savran's avatar
Oleksii Savran committed
44
import Tab from '@material-ui/core/Tab';
45
46
47
import HeaderTabs from '@gringlobal-ce/client/ui/common/tabs/HeaderTabs';
import TabPanel from '@gringlobal-ce/client/ui/common/tabs/TabPanel';
import SlotLayout from '@gringlobal-ce/client/ui/common/layout/SlotLayout';
48
import AuditDataDisplay from 'common/AuditDataDisplay';
49
import withTabs, { IWithTabs } from 'ui/common/withTabs';
50
import { withStyles, WithStyles } from '@material-ui/core/styles';
51
import { AccessionLink, CooperatorLink, InventoryLink } from 'ui/common/Links';
52
import InventorySelector from 'inventory/ui/c/InventorySelector';
53
import AccessionInventorySelector from 'accession/ui/c/AccessionInventorySelector';
54
import OrderRequestForm from 'request/ui/c/OrderRequestForm';
55
import QRCode from 'ui/barcode/QRCode';
56
57
58
import { PrintSpecies } from 'common/Taxonomy';
import { loadAllPages } from '@gringlobal-ce/client/utilities';
import PrintIcon from '@material-ui/icons/Print';
59
import SourceInventorySelector from 'request/ui/c/SourceInventorySelector';
Maksym Tishchenko's avatar
Maksym Tishchenko committed
60
import confirm from '@gringlobal-ce/client/utilities/confirmAlert';
Maksym Tishchenko's avatar
Maksym Tishchenko committed
61
import { Page } from '@gringlobal-ce/client/model/page';
62
import OrderRequestItemFormWithDetails from 'request/ui/c/OrderRequestItemFormWithDetails';
63
import Number from '@gringlobal-ce/client/ui/common/Number';
64
65
66
67
68
69
70
71
72
73
74
75
import FABMenu from '@gringlobal-ce/client/ui/common/button/FABMenu';
import AddNewButton from '@gringlobal-ce/client/ui/common/button/AddNewButton';
import PostAddIcon from '@material-ui/icons/PostAdd';
import AssignmentOutlinedIcon from '@material-ui/icons/AssignmentOutlined';
import FiberNewOutlinedIcon from '@material-ui/icons/FiberNewOutlined';
import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
import UpdateOutlinedIcon from '@material-ui/icons/UpdateOutlined';
import PauseOutlinedIcon from '@material-ui/icons/PauseOutlined';
import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined';
import ListAltOutlinedIcon from '@material-ui/icons/ListAltOutlined';
import CallSplitIcon from '@material-ui/icons/CallSplit';
import LocalShippingIcon from '@material-ui/icons/LocalShipping';
76
import EditIcon from '@material-ui/icons/Edit';
77
import AdjustIcon from '@material-ui/icons/Adjust';
78
79
import OrderRequestActionForm from 'request/ui/c/RequestActionForm';
import OrderRequestItemAction from '@gringlobal-ce/client/model/gringlobal/OrderRequestItemAction';
80
81
import { Alert } from '@material-ui/lab';
import { Link } from 'react-router-dom';
Maksym Tishchenko's avatar
Maksym Tishchenko committed
82
83
84
import AttachmentsDisplay from 'repository/ui/c/AttachmentsDisplay';
import FileUploader from '@gringlobal-ce/client/ui/common/file-uploader';
import RequestAttachForm from 'request/ui/c/RequestAttachForm';
85
import RequestItemActionDialog from 'request/ui/c/RequestItemActionDialog';
86
import CodeValueInfo from '@gringlobal-ce/client/model/gringlobal/CodeValueInfo';
87
import CreateCooperatorAction from 'cooperator/ui/c/CreateCooperatorAction';
Matija Obreza's avatar
Matija Obreza committed
88
89
import PrintReportDialog from 'common/PrintReportDialog';
import { showDialog } from '@gringlobal-ce/client/action/dialog';
90
91
92
93
94
95

const styles = (theme) => ({
  tableWrapper: {
    position: "relative" as const,
  },
});
96

97
export const OrderRequestItemTableConfig = new TableConfiguration({
98
  defaultColumns: [
99
100
    'sequenceNumber', 'statusCode',
    'accession', 'improvementStatusCode', 'taxonomySpecies',
101
    'inventory', 'withdrawnInventory', 'name', 'quantityOnHand', 'quantityShipped', 'quantityShippedUnitCode'
102
103
104
105
106
107
  ],
  ignoredColumns: [
    'id', '_class', 'createdBy', 'modifiedBy', 'ownedBy', 'ownedDate',
  ],
  defaultColumnSettings: {
    id: { readonly: true, align: TextAlign.right },
Matija Obreza's avatar
Matija Obreza committed
108
109
    sequenceNumber: { align: TextAlign.right },
    statusCode: { align: TextAlign.center },
110
111
112
    accession: { label: 'client:model.Accession.accessionNumber', sort: 'inventory.accession.accessionNumber' },
    improvementStatusCode: { label: 'client:model.Accession.improvementStatusCode' },
    taxonomySpecies: { label: 'client:model.Accession.taxonomySpecies', sort: 'inventory.accession.taxonomySpecies.name' },
113
    quantityOnHand: { label: 'client:model.Inventory.quantityOnHand', sort: 'inventory.quantityOnHand' }
114
  },
115
  columnRenderers: {
Maksym Tishchenko's avatar
Maksym Tishchenko committed
116
    statusCode: ({ value }: { value: string }) => <CodeValueDisplay codeGroup={ OrderRequestItem.CodeValue.statusCode } value={ value } />,
117
    inventory: ({ value }: { value: Inventory }) => <InventoryLink inventory={ value } />,
118
    withdrawnInventory: ({ value }: { value: Inventory }) => value && <InventoryLink inventory={ value } />,
119
120
121
    accession: ({ row }: { row: OrderRequestItem }) => row?.inventory?.accession && <AccessionLink accession={ row.inventory.accession } />,
    taxonomySpecies: ({ row }: { row: OrderRequestItem }): JSX.Element => <PrintSpecies taxonomySpecies={ row?.inventory?.accession?.taxonomySpecies } />,
    improvementStatusCode: ({ row }: { row: OrderRequestItem }) => <CodeValueDisplay codeGroup={ Accession.CodeGroup.improvementStatusCode } value={ row?.inventory?.accession?.improvementStatusCode } />,
122
123
124
    quantityOnHand: ({ row }: { row: OrderRequestItem }) => <Number value={ row?.inventory?.quantityOnHand } />,
    sourceCooperator: ({ value }: { value: Cooperator }): JSX.Element => <CooperatorLink cooperator={ value } />,
    webOrderRequestItem: ({ value }: { value: WebOrderRequestItem}) => value && <span>{ value.id } { value.name }</span>
Matija Obreza's avatar
Matija Obreza committed
125
  },
126
});
Matija Obreza's avatar
Matija Obreza committed
127

128
129
130
131
132
/**
 * Cooperator address component
 */
const RequestorAddress = ({ cooperator }: { cooperator: Cooperator }) =>
  <>
133
134
135
    <div><CooperatorLink cooperator={ cooperator } /></div>
    { cooperator.lastName && <div>{ cooperator?.organization }</div> }
    { [ 'addressLine1', 'addressLine2', 'addressLine3' ].map((property) => (
136
137
138
139
140
141
142
143
      <div key={ property }>
        { cooperator?.[property] }
      </div>
    )) }
    <div>{ cooperator?.postalIndex } { cooperator?.city }</div>
    <div>{ cooperator?.geography?.countryCode }</div>
  </>

144
145
enum RequestTabs {
  REQUEST = 'request',
Maksym Tishchenko's avatar
Maksym Tishchenko committed
146
  ATTACHMENTS = 'attachments',
147
  ACTIONS = 'actions',
148
  ITEM_ACTIONS = 'itemActions'
149
150
}

Maksym Tishchenko's avatar
Maksym Tishchenko committed
151
class OrderRequestDetailsPage extends React.Component<PropsFromRedux & WithTranslation & WithStyles & IWithTabs> {
Matija Obreza's avatar
Matija Obreza committed
152
153

  protected static needs = [
154
    ({ params: { id } }) => getOrderRequestAction(id),
155
    ({ params: { id } }) => listOrderRequestItemsAction(id),
Matija Obreza's avatar
Matija Obreza committed
156
157
  ];

Matija Obreza's avatar
Matija Obreza committed
158
  private static PRINT_DIALOG_KEY = 'order-print-pdf-dialog';
159
160
  public static defaultTab = RequestTabs.REQUEST;

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  public state: {
    actionList: OrderRequestAction[],
    itemActionList: OrderRequestItemAction[],
    selectedItems: number[],
    selectedActions: OrderRequestAction[],
    sourceInventoryDialogIsOpen: boolean,
    itemsInventoryDialogIsOpen: boolean,
    itemsAccessionDialogIsOpen: boolean,
    orderRequestDialogIsOpen: boolean,
    actionDialogIsOpen: boolean,
    itemActionDialogIsOpen: boolean,
    createNewAction: boolean,
    error: string,
    orderRequestItemDialogIsOpen: boolean,
  } = {
    actionList: null,
    itemActionList: null,
Matija Obreza's avatar
Matija Obreza committed
178
    selectedItems: [],
179
    selectedActions: [],
180
    sourceInventoryDialogIsOpen: false,
181
182
    itemsInventoryDialogIsOpen: false,
    itemsAccessionDialogIsOpen: false,
183
    orderRequestDialogIsOpen: false,
184
185
186
187
    actionDialogIsOpen: false,
    itemActionDialogIsOpen: false,
    createNewAction: false,
    error: null,
188
    orderRequestItemDialogIsOpen: false,
Matija Obreza's avatar
Matija Obreza committed
189
190
  }

Matija Obreza's avatar
Matija Obreza committed
191
192
193
194
195
  public constructor(props) {
    super(props);
  }

  public componentDidMount(): void {
Matija Obreza's avatar
Matija Obreza committed
196
    const { id, requestCall } = this.props;
197
    if (id && !requestCall || requestCall.data.id !== +id) {
Matija Obreza's avatar
Matija Obreza committed
198
      this.reloadData();
Matija Obreza's avatar
Matija Obreza committed
199
200
201
    }
  }

202
  public componentDidUpdate(prevProps) {
203
    const { id, requestCall ,currentTab, showSnackbar } = this.props;
204
    if (!requestCall || !requestCall.loading && (!requestCall.error && id && +id !== requestCall.data.id)) {
Matija Obreza's avatar
Matija Obreza committed
205
      this.reloadData();
Matija Obreza's avatar
Matija Obreza committed
206
    }
207

208
    if ((id && +id !== requestCall?.data?.id) || currentTab === RequestTabs.ACTIONS && currentTab !== prevProps.currentTab) {
Maksym Tishchenko's avatar
Maksym Tishchenko committed
209
210
      loadAllPages(RequestService, RequestService.listActionsByOrderRequest, id).then((data) => {
          this.setState({ actionList: data })
211
212
213
      }).catch((e) => {
        showSnackbar(e.data && e.data.error || e.toString());
      });
214
    }
215

216
    if ((id && +id !== requestCall?.data?.id) || currentTab === RequestTabs.ITEM_ACTIONS && currentTab !== prevProps.currentTab) {
Maksym Tishchenko's avatar
Maksym Tishchenko committed
217
218
      loadAllPages(RequestService, RequestService.listItemActionsByOrderRequest, id).then((data) => {
        this.setState({ itemActionList: data })
219
220
221
      }).catch((e) => {
        showSnackbar(e.data && e.data.error || e.toString());
      });
222
    }
Matija Obreza's avatar
Matija Obreza committed
223
224
  }

Matija Obreza's avatar
Matija Obreza committed
225
  private reloadData = () => {
226
    const { id, getOrderRequestAction, listOrderRequestItemsAction } = this.props;
Matija Obreza's avatar
Matija Obreza committed
227
    getOrderRequestAction(id);
228
    listOrderRequestItemsAction(id);
Matija Obreza's avatar
Matija Obreza committed
229
230
  }

231
232
233
234
235
  private loadMoreItems = (): void => {
    const { id, listOrderRequestItemsAction, requestItemsCall } = this.props;
    listOrderRequestItemsAction(id, {}, Page.nextPage(requestItemsCall.data));
  };

236
237
238
  private handleUpdateItems = (updatedItems) => {
    const { id, listOrderRequestItemsAction, showSnackbar } = this.props;
    RequestService.updateItems(updatedItems).then((res) => {
239
      listOrderRequestItemsAction(id);
240
241
242
243
244
245
      this.closeSourceInventoryDialog();
    }).catch((e) => {
      showSnackbar(e.data && e.data.error || e.toString());
    });
  }

246
247
248
249
250
251
252
253
  private printRequest = () => {
    const { id, receiveOrderRequestItemsSuccessAction } = this.props;
    loadAllPages(RequestService, RequestService.filterItems, {}, id).then((data) => {
      receiveOrderRequestItemsSuccessAction(data);
      window.print()
    });
  }

254
255
256
257
258
259
260
261
262
  private renumberItems = () => {
    const { id, listOrderRequestItemsAction } = this.props;
    RequestService.renumber(id).then(() => {
      listOrderRequestItemsAction(id);
    }).catch((e) => {
      showSnackbar(e.data && e.data.error || e.toString());
    });
  }

263
264
265
266
267
268
269
270
271
  private generateInventories = () => {
    const { id, listOrderRequestItemsAction } = this.props;
    RequestService.generateInventories(id).then(() => {
      listOrderRequestItemsAction(id);
    }).catch((e) => {
      showSnackbar(e.data && e.data.error || e.toString());
    });
  }

Maksym Tishchenko's avatar
Maksym Tishchenko committed
272
273
  private rowsToggled = (selectedRows: number[]) => {
    this.setState({ selectedItems: selectedRows });
Matija Obreza's avatar
Matija Obreza committed
274
275
  }

Maksym Tishchenko's avatar
Maksym Tishchenko committed
276
277
278
279
280
281
282
283
284
285
286
  // private toggleSelectAll = () => {
  //   const { requestItemsCall: { data: { content } } } = this.props;
  //   const { selectedItems } = this.state;
  //   let items = content.map((i, idx) => idx);
  //   if (selectedItems.length === items.length) {
  //     items = [];
  //   }
  //   this.setState({
  //     selectedItems: [ ...items ],
  //   });
  // }
287

288
289
290
291
292
293
294
295
296
297
298
299
300
  private removeItems = () => {
    const { t, removeOrderRequestItemsAction,  requestItemsCall: { data: { content: items } }, id } = this.props;
    const { selectedItems } = this.state;

    confirm(t('common:label.deleteListConfirm', { count: selectedItems.length, what: t('client:model.name.OrderRequestItem', { count: selectedItems.length }) }), {
      confirmLabel: t('common:label.yes'),
      abortLabel: t('common:label.no'),
    }).then(() => {
      removeOrderRequestItemsAction(items.filter((item, index) => selectedItems.includes(index)).map((i) => i.id), +id)
      this.setState({ selectedItems: [] })
    });
  };

Matija Obreza's avatar
Matija Obreza committed
301
302
303
304
305
306
307
308
309
  private updateItemStatus = (newStatus: OrderRequestItemStatus) => () => {
    const { id, listOrderRequestItemsAction, requestCall: { data: request }, requestItemsCall: { data: { content: items } } } = this.props;

    const selectedItems = this.state.selectedItems.map((i) => items[i]);
    console.log(`Updating selected items status to ${newStatus}`, selectedItems);

    RequestService.updateItemStatus(request.id, newStatus, selectedItems.map((item) => item.id))
      .then((data) => {
        console.log('Items updated!!', data);
310
        listOrderRequestItemsAction(id);
Matija Obreza's avatar
Matija Obreza committed
311
312
313
314
315
      }).catch((err) => {
        console.log('Error updating items', err);
      });
  }

316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
  private getItemStatusIcon = (value: string) => {
    switch (value) {
      case OrderRequestItemStatus.NEW: {
        return <FiberNewOutlinedIcon/>
      }
      case OrderRequestItemStatus.CANCEL: {
        return <CancelOutlinedIcon/>
      }
      case OrderRequestItemStatus.PENDING: {
        return <UpdateOutlinedIcon/>
      }
      case OrderRequestItemStatus.HOLD: {
        return <PauseOutlinedIcon/>
      }
      case OrderRequestItemStatus.INSPECT: {
        return <SearchOutlinedIcon/>
      }
      case OrderRequestItemStatus.QUALITYTEST: {
        return <ListAltOutlinedIcon/>
      }
      case OrderRequestItemStatus.SPLIT: {
        return <CallSplitIcon/>
      }
      case OrderRequestItemStatus.SHIPPED: {
        return <LocalShippingIcon/>
      }
      default: {
        return <AdjustIcon/>
      }
    }
  }

348
  private verifyItemList = () => {
349
    const { requestCall: { data: request }, showSnackbar, t } = this.props;
350
351
    RequestService.startOrderItemActions(request.id, OrderRequestItem.Actions.VERIFY).then((data) => {
      console.log('Action started for {} items!', data.length);
352
353
354
      showSnackbar(t('common:label.actionScheduled', { count: data.length }));
    }).catch((e) => {
      showSnackbar(e.data && e.data.error || e.toString());
355
356
357
    });
  }

358
359
360
361
  private handleSubmit = (formData: OrderRequest) => {
    const { editOrderRequestAction } = this.props;
    editOrderRequestAction(formData);
    this.closeOrderRequestDialog();
362
363
  };

364
365
  private handleEditItem = (formData: OrderRequestItem) => {
    const { editOrderRequestItemAction } = this.props;
366
367
368
    if (formData.sourceCooperator) {
      formData.sourceCooperator = { id: formData.sourceCooperator as any } as Cooperator
    }
369
370
371
372
    editOrderRequestItemAction(formData);
    this.closeOrderRequestItemDialog();
  };

373
  private handleSubmitAction = (formData: OrderRequestAction) => {
374
375
376
    const { requestCall: { data: request } } = this.props;
    const { actionList, selectedActions } = this.state;
    let updatedActionList = [ ...actionList ]
377
378
379
380
381
382
383
384
385
386
387
    this.resetError()
    formData.orderRequest = { id: request?.id } as OrderRequest;
    let requestActionPromise: Promise<OrderRequestAction>;
    if (formData.id) {
      requestActionPromise = RequestService.updateAction(formData);
    } else {
      requestActionPromise = RequestService.createAction(formData);
    }
    requestActionPromise
      .then((requestAction: OrderRequestAction) => {
        console.log('success', requestAction);
388
389
390
391
392
393
        if (formData.id) {
          const foundIndex = actionList?.findIndex((data) => +data.id === +requestAction.id)
          updatedActionList[foundIndex] = requestAction
        } else {
          updatedActionList = [ requestAction, ...actionList ]
        }
394
        this.setState({ actionList: [ ...updatedActionList ], selectedActions: formData.id ? [ requestAction ] : selectedActions });
395
396
397
398
399
400
401
402
403
        this.closeActionDialog();
      })
      .catch((e) => {
        console.log('error', e);
        this.setState({ error: e.data && e.data.error || e.toString() });
      });
  };

  private handleSubmitItemAction = async (formData: OrderRequestItemAction) => {
404
405
    const { showSnackbar, t,  requestItemsCall: { data: { content: items } } } = this.props;
    const { itemActionList } = this.state;
406
407
408
409
410
    const selectedItems = this.state.selectedItems.map((i) => items[i]);
    this.resetError()
    for (const item of selectedItems) {
      try {
        const requestItemAction = await RequestService.createItemAction({ ...formData, orderRequestItem: { id: item.id } as OrderRequestItem });
411
        this.setState({ itemActionList: [ requestItemAction, ...(itemActionList ?? []) ] })
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
      } catch (e) {
        console.log('error', e);
        this.setState({ error: e.data && e.data.error || e.toString() });
        showSnackbar(t('request.public.p.details.unableToAddActionForItem', { itemId: item.id }))
        return;
      }
    }
    this.closeItemActionDialog();
  };

  private resetError = () => {
    return this.setState({ error: null });
  }

  private onActionsSelected = (selectedRows: number[]) => {
427
428
    const { actionList } = this.state;
    const selectedActions = selectedRows.map((rowIndex) => actionList[rowIndex])
429
430
431
432
    this.setState({ selectedActions });
  }

  private onItemActionsSelected = (selectedRows: number[]) => {
433
    const { itemActionList } = this.state;
434
435
    const selectedItemActions = selectedRows.map((rowIndex) => itemActionList[rowIndex])
    this.setState({ selectedItemActions });
436
437
  }

438
439
  // source inventory
  private openSourceInventoryDialog = () => this.setState({ sourceInventoryDialogIsOpen: true });
440

441
  private closeSourceInventoryDialog = () => this.setState({ sourceInventoryDialogIsOpen: false });
442

443
444
  // by accession
  private openItemsAccessionDialog = () => this.setState({ itemsAccessionDialogIsOpen: true });
445

446
447
448
449
450
451
  private closeItemsAccessionDialog = () => this.setState({ itemsAccessionDialogIsOpen: false });

  // by inventory
  private openItemsInventoryDialog = () => this.setState({ itemsInventoryDialogIsOpen: true });

  private closeItemsInventoryDialog = () => this.setState({ itemsInventoryDialogIsOpen: false });
452

453
  // edit request
454
455
456
457
  private openOrderRequestDialog = () => this.setState({ orderRequestDialogIsOpen: true });

  private closeOrderRequestDialog = () => this.setState({ orderRequestDialogIsOpen: false });

458
459
460
461
462
  // edit request item
  private openOrderRequestItemDialog = () => this.setState({ orderRequestItemDialogIsOpen: true });

  private closeOrderRequestItemDialog = () => this.setState({ orderRequestItemDialogIsOpen: false });

463
464
465
466
467
468
469
470
471
472
  //edit request action
  private openActionDialog = (createNewAction = true) => {
    this.setState({ actionDialogIsOpen: true, createNewAction });
  };

  private closeActionDialog = () => {
    this.setState({ actionDialogIsOpen: false, createNewAction: false });
  };

  // edit request item action
473
474
475
  private openItemActionDialog = (createNewAction = true) => {
    this.setState({ itemActionDialogIsOpen: true, createNewAction });
  }
476

477
478
479
  private closeItemActionDialog = () => {
    this.setState({ itemActionDialogIsOpen: false, createNewAction: false });
  }
480

481
  private onInventoriesSelect = (selected, byAccession: boolean) => {
482
483
    const { id, showSnackbar, listOrderRequestItemsAction } = this.props;
    RequestService.addInventories(id, selected).then(() => {
484
      listOrderRequestItemsAction(id);
485
      byAccession ? this.closeItemsAccessionDialog() : this.closeItemsInventoryDialog();
486
487
488
489
490
    }).catch((e) => {
      showSnackbar(e.data && e.data.error || e.toString());
    });
  }

Maksym Tishchenko's avatar
Maksym Tishchenko committed
491
492
493
494
495
496
497
498
499
500
  public handleRemove = () => {
    const { removeOrderRequestAction, t, id, requestCall: { data: request } } = this.props;
    confirm(t('common:label.deleteItemConfirm', { title: request.localNumber, what: t('client:model.name.OrderRequest') }), {
      confirmLabel: t('common:label.yes'),
      abortLabel: t('common:label.no'),
    }).then(() => {
      removeOrderRequestAction(+id);
    });
  };

Maksym Tishchenko's avatar
Maksym Tishchenko committed
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
  private handleUploading = (files: File[]) => {
    const { requestCall, uploadRequestAttachment } = this.props;
    uploadRequestAttachment(requestCall.data.id, files);
  };

  private handleAttachmentSubmit = (data, closeDialog) => {
    const { receiveRequestAttachmentSuccessAction } = this.props
    if(data.request && !data.request.id) {
      data.request = { id: data.request }
    }
    RequestService.updateAttach(data)
      .then((updatedAttach) => {
        closeDialog();
        receiveRequestAttachmentSuccessAction(updatedAttach);
      })
      .catch((e) => {
        this.setState({ error: e.data && e.data.error || e.toString() })
      })
  }

  private handleEditFile = (fileId, openDialog, updateFileMeta) => {
    RequestService.getAttach(fileId)
      .then((fileMeta) => {
        updateFileMeta(fileMeta);
        openDialog();
      })
  }

529
  private getCodeValueItemStatusActions = memoize((codeValue: { [key: string]: CodeValueInfo }) => {
530
    return codeValue ? Object.keys(codeValue).map((value) => ({
531
532
533
      title: <CodeValueDisplay value={ value } codeGroup={ OrderRequestItem.CodeValue.statusCode } />,
      action: this.updateItemStatus(value as OrderRequestItemStatus),
      icon: this.getItemStatusIcon(value),
534
    })) : []
535
536
  })

Matija Obreza's avatar
Matija Obreza committed
537
  public render(): React.ReactNode {
538
    const { requestCall, requestItemsCall, codeValuesCall, removeRequestAttachmentAction, removeRequestAttachmentsAction, t, currentTab, onTabChange, classes } = this.props;
539
    const {
540
      actionList, itemActionList, selectedItems, selectedActions, sourceInventoryDialogIsOpen, itemsInventoryDialogIsOpen, orderRequestItemDialogIsOpen,
541
542
      itemsAccessionDialogIsOpen, orderRequestDialogIsOpen, actionDialogIsOpen, itemActionDialogIsOpen, createNewAction, error
    } = this.state;
543
544
    const columns = OrderRequestItemTableConfig.getColumns(requestItemsCall && requestItemsCall.data && requestItemsCall.data.content ? requestItemsCall.data.content[0] : null);
    if (!requestCall) {
Matija Obreza's avatar
Matija Obreza committed
545
546
547
      return null;
    }

548
549
550
551
552
553
554
555
556
    const actionActions = [
      {
        title: 'common:action.edit',
        action: () => this.openActionDialog(false),
        icon: <EditIcon/>,
        disabled: selectedActions.length !== 1
      },
    ]

557
558
559
560
561
562
563
564
565
566
567
568
569
570
    const addItemActions = [
      {
        title:'request.public.p.details.byAccession',
        action: this.openItemsAccessionDialog,
        icon: <PostAddIcon/>,
      },
      {
        title:'request.public.p.details.byInventory',
        action: this.openItemsInventoryDialog,
        icon: <PostAddIcon/>,
      },
    ]

    const itemActions = [
571
572
573
574
575
576
577
578
579
580
581
      {
        title: 'common:action.remove',
        action: this.removeItems,
        icon: <CancelOutlinedIcon/>,
      },
      {
        title: 'common:action.edit',
        action: this.openOrderRequestItemDialog,
        icon: <EditIcon/>,
        disabled: selectedItems.length !== 1
      },
582
583
584
585
586
      {
        title:t('common:action.add', { what: t('client:model.name.OrderRequestItemAction') }),
        action: this.openItemActionDialog,
        icon: <PostAddIcon/>,
      },
587
      {
588
        title:'request.public.p.details.changeInventory',
589
590
591
        action: this.openSourceInventoryDialog,
        icon: <AssignmentOutlinedIcon/>,
      },
592
      ...this.getCodeValueItemStatusActions(codeValuesCall?.data?.[OrderRequestItem.CodeValue.statusCode])
593
594
    ]

595
    const { loading, data: request } = requestCall;
596
    const selectedItem = requestItemsCall?.data?.content?.[selectedItems[0]];
Matija Obreza's avatar
Matija Obreza committed
597
598
599

    return (
      <>
600
        <PageTitle title={ request ? request.localNumber ? request.localNumber : `${t('request.public.p.details.title')} ${request.id}` : t('request.public.p.details.title') }/>
Oleksii Savran's avatar
Oleksii Savran committed
601
        <HeaderTabs
602
          value={ currentTab }
Oleksii Savran's avatar
Oleksii Savran committed
603
          textColor="primary"
604
          onChange={ onTabChange }
Oleksii Savran's avatar
Oleksii Savran committed
605
606
607
608
          variant="scrollable"
          scrollButtons="auto"
          aria-label="Request tabs"
        >
609
          <Tab value={ RequestTabs.REQUEST } label={ t('request.public.p.details.title') } />
Maksym Tishchenko's avatar
Maksym Tishchenko committed
610
          <Tab value={ RequestTabs.ATTACHMENTS } label={ t('request.public.p.details.attachments') } />
611
          <Tab value={ RequestTabs.ACTIONS } label={ t('request.public.p.details.actions') } />
612
          <Tab value={ RequestTabs.ITEM_ACTIONS } label={ t('request.public.p.details.itemActions') } />
Oleksii Savran's avatar
Oleksii Savran committed
613
        </HeaderTabs>
614
        <TabPanel value={ currentTab } index={ RequestTabs.REQUEST }>
Oleksii Savran's avatar
Oleksii Savran committed
615
616
617
618
619
620
          { loading && <Loading/> }
          { request &&
            <SlotLayout
              fixedContent={
                <>
                  <Card>
621
622
623
                    { request && <div style={ { float: 'left' } }>
                      <QRCode width="80px" value={ `OR:${request.id}` } />
                    </div> }
Oleksii Savran's avatar
Oleksii Savran committed
624
625
626
627
                    <CardHeader title={
                      request
                        ? <>{ request.localNumber ? request.localNumber : request.id }</>
                        : t('request.public.p.details.title')
628
                    }
629
                      subheader={ request && <>
630
631
632
                        <CodeValueDisplay codeGroup={ OrderRequest.CodeGroup.orderTypeCode } value={ request.orderTypeCode } />
                        { request.intendedUseCode && <span> &mdash; </span> }
                        <CodeValueDisplay codeGroup={ OrderRequest.CodeGroup.intendedUseCode } value={ request.intendedUseCode } />
633
                      </> }
634
                    />
635
636
                    { request.originalOrderRequest && request.originalOrderRequest !== request.id &&
                      <Alert style={{ clear: 'both', borderRadius: 0 }} severity="info">
637
                        <Link to={ `/dist/request/${request.originalOrderRequest}` } title={ t('common:action.navigateTo', { where: t(['client:model.OrderRequest.originalOrderRequest', 'client:model._.originalOrderRequest']) }) }>
638
639
640
641
                          { t('request.public.p.details.splitFromRequest', { originalRequestId: request.originalOrderRequest }) }
                        </Link>
                      </Alert>
                    }
Oleksii Savran's avatar
Oleksii Savran committed
642
643
                    <CardContent>
                      <Properties>
644
                        {/* <PropertiesItem title={ t(['client:model.OrderRequest.orderTypeCode', 'client:model._.orderTypeCode']) }>
645
                          <CodeValueDisplay codeGroup={ OrderRequest.CodeGroup.orderTypeCode } value={ request.orderTypeCode } />
Matija Obreza's avatar
Matija Obreza committed
646
647
                        </PropertiesItem> */}
                        { [ /* 'localNumber', 'intendedUseNote', */ 'orderObtainedVia', 'specialInstruction', 'note' ].map((property) => (
648
                          <PropertiesItem key={ property } title={ t([`client:model.OrderRequest.${property}`, `client:model._.${property}`]) }>
Oleksii Savran's avatar
Oleksii Savran committed
649
650
651
                            { request[property] }
                          </PropertiesItem>
                        )) }
652
                        <PropertiesItem title={ t(['client:model.OrderRequest.intendedUseCode', 'client:model._.intendedUseCode']) }>
653
                          <CodeValueDisplay codeGroup={ OrderRequest.CodeGroup.intendedUseCode } value={ request.intendedUseCode } />
Oleksii Savran's avatar
Oleksii Savran committed
654
                        </PropertiesItem>
655

656
                        { request.finalRecipientCooperator && <PropertiesItem title={ t(['client:model.OrderRequest.finalRecipientCooperator', 'client:model._.finalRecipientCooperator']) }>
657
                          <RequestorAddress cooperator={ request.finalRecipientCooperator } />
658
                        </PropertiesItem> }
659
                        { request.shipToCooperator && <PropertiesItem title={ t(['client:model.OrderRequest.shipToCooperator', 'client:model._.shipToCooperator']) }>
660
661
                          { request.shipToCooperator?.id === request?.finalRecipientCooperator?.id ? t('request.public.p.details.sameAsFinalRecipient') : <RequestorAddress cooperator={ request.shipToCooperator } /> }
                        </PropertiesItem> }
662
                        { request.requestorCooperator && <PropertiesItem title={ t(['client:model.OrderRequest.requestorCooperator', 'client:model._.requestorCooperator']) }>
663
                          { request.finalRecipientCooperator?.id === request.requestorCooperator?.id ? t('request.public.p.details.sameAsFinalRecipient') : <RequestorAddress cooperator={ request.requestorCooperator } /> }
664
665
                        </PropertiesItem> }

666
                        <AuditDataDisplay item={ request } withoutAuditFields/>
Oleksii Savran's avatar
Oleksii Savran committed
667
668
669
670
                      </Properties>
                    </CardContent>
                    <CardActions>
                      <ButtonBar>
Matija Obreza's avatar
Matija Obreza committed
671
672
673
                        <Button variant="outlined" color="secondary" onClick={ () => this.props.showDialog(OrderRequestDetailsPage.PRINT_DIALOG_KEY) }>
                          { t('common:action.generatePdf') }
                        </Button>
674
675
676
                        <Button variant="contained" color="primary" onClick={ this.printRequest } startIcon={ <PrintIcon /> }>
                          { t('request.public.p.retrievalList.label.printRequest') }
                        </Button>
677
678
679
                        <Button variant="contained" color="primary" onClick={ this.renumberItems }>
                          { t('request.public.p.details.renumberItems') }
                        </Button>
680
681
682
                        <Button variant="contained" color="primary" onClick={ this.generateInventories }>
                          { t('request.public.p.details.createWithdrawnInventories') }
                        </Button>
683
                        <Button variant="contained" color="primary" onClick={ this.verifyItemList }>{ t('request.public.p.details.verifyItemList') }</Button>
684
                        <Button variant="contained" color="secondary" onClick={ this.openOrderRequestDialog }>{ t('common:action.edit') }</Button>
Maksym Tishchenko's avatar
Maksym Tishchenko committed
685
                        <Button variant="outlined" color="secondary" onClick={ this.handleRemove }>{ t('common:action.remove') }</Button>
Oleksii Savran's avatar
Oleksii Savran committed
686
687
688
689
                      </ButtonBar>
                    </CardActions>
                  </Card>
                </>
Oleksii Savran's avatar
Oleksii Savran committed
690
              }
Oleksii Savran's avatar
Oleksii Savran committed
691
692
            >
              <Card>
693
                <CardHeader title={ t('request.public.p.details.items', { count: requestItemsCall?.data?.totalElements }) } />
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
                <div className={ classes.tableWrapper }>
                  <Table
                    noWrap
                    tableKey="request-items-list"
                    type={ 'OrderRequestItem' }
                    columns={ columns }
                    data={ requestItemsCall && requestItemsCall.data && requestItemsCall.data.content }
                    tableConfig={ OrderRequestItemTableConfig }
                    total={ requestItemsCall && requestItemsCall.data && requestItemsCall.data.content && requestItemsCall.data.totalElements }
                    // sort={ requestItemsCall && requestItemsCall.data && requestItemsCall.data.sort }
                    // onSortChange={ onSortChange }
                    onRowsToggled={ this.rowsToggled }
                    loadMore={ this.loadMoreItems }
                  />
                </div>
709
                <SourceInventorySelector items={ selectedItems.map((index) => requestItemsCall?.data?.content?.[index]) } onSubmit={ this.handleUpdateItems } isOpen={ sourceInventoryDialogIsOpen } onClose={ this.closeSourceInventoryDialog }/>
710
711
                <AccessionInventorySelector onInventoriesSelect={ this.onInventoriesSelect } isOpen={ itemsAccessionDialogIsOpen } onClose={ this.closeItemsAccessionDialog }/>
                <InventorySelector onInventoriesSelect={ this.onInventoriesSelect } isOpen={ itemsInventoryDialogIsOpen } onClose={ this.closeItemsInventoryDialog }/>
Oleksii Savran's avatar
Oleksii Savran committed
712
713
714
              </Card>
            </SlotLayout>
          }
715
716
717
718
          <OrderRequestForm
            isOpen={ orderRequestDialogIsOpen }
            onClose={ this.closeOrderRequestDialog }
            onSubmit={ this.handleSubmit }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
719
            formId="order-request-form"
720
            title={ request?.localNumber }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
721
            size="lg"
722
            initialValues={ request }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
723
            additionalActions={
724
              <CreateCooperatorAction/>
Maksym Tishchenko's avatar
Maksym Tishchenko committed
725
            }
726
          />
727
          <RequestItemActionDialog
728
729
730
            isOpen={ itemActionDialogIsOpen }
            onClose={ this.closeItemActionDialog }
            onSubmit={ this.handleSubmitItemAction }
731
732
            formId="request-item-action-form"
            items={ selectedItems.map((index) => requestItemsCall?.data?.content?.[index]) }
733
734
735
            error={ error }
            resetError={ this.resetError }
          />
736
737
738
739
740
741
742
743
744
          { selectedItems.length === 0 &&
            <AddNewButton
              actions={ addItemActions }
              addTitle
              tooltipOpen
            />
          }
          { selectedItems.length > 0 &&
            <FABMenu
Maksym Tishchenko's avatar
Maksym Tishchenko committed
745
              title="request.public.p.details.setStatus"
746
747
748
              actions={ itemActions }
            />
          }
749
          <OrderRequestItemFormWithDetails
750
751
752
            isOpen={ orderRequestItemDialogIsOpen }
            onClose={ this.closeOrderRequestItemDialog }
            onSubmit={ this.handleEditItem }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
753
            formId="order-request-item-form"
754
            title={ selectedItem?.name || t('request.public.p.edit.itemTitle', { number: selectedItem?.sequenceNumber }) }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
755
            size="lg"
756
757
            initialValues={ selectedItem || {} }
          />
Matija Obreza's avatar
Matija Obreza committed
758
759
760
761
762
763
          <PrintReportDialog
            endpoint="order"
            modelName="OrderRequest"
            selectedItemIds={ [ request?.id ] }
            dialogKey={ OrderRequestDetailsPage.PRINT_DIALOG_KEY }
          />
Oleksii Savran's avatar
Oleksii Savran committed
764
        </TabPanel>
Maksym Tishchenko's avatar
Maksym Tishchenko committed
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
        <TabPanel value={ currentTab } index={ RequestTabs.ATTACHMENTS }>
          <Card>
            <CardHeader title={ t('request.public.p.details.attachments') } />
            { request?.attachments &&
              <AttachmentsDisplay
                AttachmentForm={ RequestAttachForm }
                handleSubmit={ this.handleAttachmentSubmit }
                handleEdit={ this.handleEditFile }
                removeAttachmentAction={ removeRequestAttachmentAction }
                removeAttachmentsAction={ removeRequestAttachmentsAction }
                id={ request.id }
                attachments={ request.attachments }
              />
            }
            <div className="p-1rem">
              <FileUploader id="crop-attachment" handleUploading={ this.handleUploading }/>
            </div>
          </Card>
        </TabPanel>
784
        <TabPanel value={ currentTab } index={ RequestTabs.ACTIONS }>
785
          { actionList && <RequestActionsTable onActionSelected={ this.onActionsSelected } actions={ actionList }/> }
786
787
788
789
790
          { selectedActions.length === 0 &&
            <AddNewButton action={ this.openActionDialog }/>
          }
          { selectedActions.length > 0 &&
            <FABMenu
Maksym Tishchenko's avatar
Maksym Tishchenko committed
791
              title="common:label.openActionList"
792
793
794
795
796
797
798
              actions={ actionActions }
            />
          }
          <OrderRequestActionForm
            isOpen={ actionDialogIsOpen }
            onClose={ this.closeActionDialog }
            onSubmit={ this.handleSubmitAction }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
799
            formId="request-action-form"
800
801
802
803
            title={ ! createNewAction && selectedActions.length === 1
              ? t('common:action.editItem', { what: t('client:model.name.OrderRequestAction') })
              : t('common:action.add', { what: t('client:model.name.OrderRequestAction') })
            }
Maksym Tishchenko's avatar
Maksym Tishchenko committed
804
            size="lg"
805
806
807
808
809
810
            error={ error }
            resetError={ this.resetError }
            initialValues={ ! createNewAction ? selectedActions[0] : {} }
          />
        </TabPanel>
        <TabPanel value={ currentTab } index={ RequestTabs.ITEM_ACTIONS }>
811
          { itemActionList && <RequestItemActionsTable onActionSelected={ this.onItemActionsSelected } actions={ itemActionList }/> }
Oleksii Savran's avatar
Oleksii Savran committed
812
        </TabPanel>
Matija Obreza's avatar
Matija Obreza committed
813
814
815
816
817
818
      </>
    );
  }
}

const mapStateToProps = (state, ownProps) => ({
819
  id: ownProps.routeParams.id,
820
821
  requestCall: state.request.public.request,
  requestItemsCall: state.request.public.requestItems,
822
  codeValuesCall: state.codeValue.public.codeValuesCall,
Matija Obreza's avatar
Matija Obreza committed
823
824
});

Maksym Tishchenko's avatar
Maksym Tishchenko committed
825
const mapDispatch = {
826
827
  getOrderRequestAction,
  listOrderRequestItemsAction,
828
  receiveOrderRequestItemsSuccessAction,
Maksym Tishchenko's avatar
Maksym Tishchenko committed
829
830
831
832
  uploadRequestAttachment,
  removeRequestAttachmentAction,
  removeRequestAttachmentsAction,
  receiveRequestAttachmentSuccessAction,
833
  editOrderRequestAction,
Maksym Tishchenko's avatar
Maksym Tishchenko committed
834
  removeOrderRequestAction,
835
836
  removeOrderRequestItemsAction,
  editOrderRequestItemAction,
837
  navigateTo,
838
  showSnackbar,
Matija Obreza's avatar
Matija Obreza committed
839
  showDialog,
Maksym Tishchenko's avatar
Maksym Tishchenko committed
840
}
Matija Obreza's avatar
Matija Obreza committed
841

Maksym Tishchenko's avatar
Maksym Tishchenko committed
842
type PropsFromRedux = ReturnType<typeof mapStateToProps> & typeof mapDispatch;
Matija Obreza's avatar
Matija Obreza committed
843
844

export default compose(
Maksym Tishchenko's avatar
Maksym Tishchenko committed
845
  connect(mapStateToProps, mapDispatch),
Matija Obreza's avatar
Matija Obreza committed
846
  withTranslation(),
847
  withStyles(styles),
848
)(withTabs(OrderRequestDetailsPage));