· 7 years ago · Feb 19, 2019, 07:28 PM
1import * as React from "react";
2import NumberFormat from "react-number-format";
3import { connect } from "react-redux";
4import { RouteComponentProps } from "react-router";
5import { Link } from "react-router-dom";
6import { Dispatch } from "redux";
7import {
8 change, Field, focus, InjectedFormProps, reduxForm, registerField, unregisterField
9} from "redux-form";
10import { TextField } from "redux-form-material-ui";
11
12import Button from "@material-ui/core/Button/Button";
13import Card from "@material-ui/core/Card";
14import CardContent from "@material-ui/core/CardContent";
15import MenuItem from "@material-ui/core/MenuItem/MenuItem";
16import Paper from "@material-ui/core/Paper/Paper";
17import { Theme } from "@material-ui/core/styles/createMuiTheme";
18import createStyles from "@material-ui/core/styles/createStyles";
19import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
20import Table from "@material-ui/core/Table/Table";
21import TableBody from "@material-ui/core/TableBody/TableBody";
22import TableCell from "@material-ui/core/TableCell/TableCell";
23import TableHead from "@material-ui/core/TableHead/TableHead";
24import TableRow from "@material-ui/core/TableRow/TableRow";
25import Typography from "@material-ui/core/Typography/Typography";
26
27import { AssetPostDto, SubCategoryItemPostDto } from "../../interfaces/dtos/post/AssetPostDto";
28import { AssetPutDto, SubCategoryItemPutDto } from "../../interfaces/dtos/put/AssetPutDto";
29import {
30 Asset, LovList, Subcategory, SubcategoryItem
31} from "../../interfaces/dtos/result/ApplicationParamsResultDto";
32import { ApplicationState } from "../../store";
33import { ApplicationItem, ApplicationItemDetails } from "../../store/applicationUser/types";
34import {
35 createAssetRequest, deleteAssetRequest, updateAssetRequest
36} from "../../store/assets/actions";
37import { ApplicationControlType } from "../../utils/enums";
38import { required } from "../../utils/helpers";
39import Autosuggest from "../shared/Autosuggest";
40// import Autosuggest from "../shared/Autosuggest";
41import CurrencyFormatCustom from "../shared/CurrencyFormatCustom";
42import Loader from "../shared/Loader";
43import SelectField from "../shared/SelectField";
44
45const styles = (theme: Theme) =>
46 createStyles({
47 title: {
48 fontFamily: "BrandonGrotesqueMedium, Lato, Arial",
49 fontSize: 16,
50 fontWeight: 900,
51 letterSpacing: 0.4
52 },
53 cardBorder: {
54 borderTop: "solid 4px",
55 borderColor: "#12b9ce"
56 },
57 category: {
58 minWidth: 200,
59 maxWidth: 200
60 },
61 submitButton: {
62 color: theme.palette.common.white,
63 minWidth: 200
64 },
65 row: {
66 "&:nth-of-type(odd)": {
67 backgroundColor: theme.palette.background.default
68 }
69 },
70 tableWrapper: {
71 overflow: "auto"
72 }
73 });
74
75interface Values {
76 [key: string]: string;
77}
78
79interface MatchParams {
80 id: string;
81}
82
83interface Props extends RouteComponentProps<MatchParams> {
84 category: string;
85}
86
87interface PropsFromState {
88 loading: boolean;
89 errors: string;
90 assetsLoading: boolean;
91 assetsItems: ApplicationItem[];
92 assetParams: Asset | null;
93 lovListParams: LovList;
94}
95
96interface PropsFromDispatch {
97 createAssetRequest: typeof createAssetRequest;
98 updateAssetRequest: typeof updateAssetRequest;
99 deleteAssetRequest: typeof deleteAssetRequest;
100 dispatch: Dispatch;
101}
102
103interface State {
104 items: SubcategoryItem[];
105 selectedBank: any | null;
106 suggestionsInputValue: string;
107}
108
109type AssetsProps = WithStyles<typeof styles> &
110 InjectedFormProps<Values> &
111 PropsFromState &
112 PropsFromDispatch &
113 Props;
114
115class Assets extends React.Component<AssetsProps, State> {
116 /**
117 * Based on the route we get the asset details if we have an identifier in the router parameters.
118 * In case we find the asset, we enable the input fields in the form dynamically, otherwise we read the
119 * subcategory_items key and show them instead
120 * @param nextProps
121 * @param prevState
122 */
123 public static getDerivedStateFromProps(
124 nextProps: AssetsProps,
125 prevState: State
126 ) {
127 if (!nextProps.assetParams || nextProps.assetsLoading) {
128 return {
129 items: prevState.items
130 };
131 }
132
133 const foundItem = nextProps.assetsItems.find(
134 assetItem => assetItem.item_id === parseInt(nextProps.match.params.id, 10)
135 );
136 if (!foundItem) {
137 return {
138 items: prevState.items
139 };
140 }
141 const foundItemParams = nextProps.assetParams.subcategory.find(
142 (subCategory: Subcategory) =>
143 subCategory.subcategory_code === foundItem.subcategory_code
144 );
145 if (!foundItemParams) {
146 return {
147 items: prevState.items
148 };
149 }
150
151 return {
152 // items: prevState.items.length > 0 ? prevState.items : foundItemParams.subcategory_items
153 items:
154 JSON.stringify(prevState.items) ===
155 JSON.stringify(foundItemParams.subcategory_items)
156 ? prevState.items
157 : foundItemParams.subcategory_items
158 };
159 }
160
161 public state: State = {
162 items: [],
163 selectedBank: null,
164 suggestionsInputValue: ""
165 };
166
167 public componentDidMount(): void {
168 if (this.props.match.params.id !== "new") {
169 const foundApplicationItem = this.props.assetsItems.find(
170 item => item.item_id === parseInt(this.props.match.params.id, 10)
171 ) as ApplicationItem;
172 if (!foundApplicationItem) {
173 this.props.history.push("new");
174 }
175 }
176 }
177
178 /**
179 * Only render the component if :
180 * 1) we have a different id
181 * 2) the params have populated
182 * 3) The form items array has changed, meaning we have a different category selected
183 * 4) The assets loading flag is changed
184 *
185 * @param nextProps
186 * @param nextState
187 */
188 public shouldComponentUpdate(
189 nextProps: Readonly<
190 WithStyles<typeof styles> &
191 InjectedFormProps<Values> &
192 PropsFromState &
193 PropsFromDispatch &
194 Props
195 >,
196 nextState: Readonly<State>
197 ): boolean {
198 return (
199 this.props.assetParams !== nextProps.assetParams ||
200 this.props.match.params.id !== nextProps.match.params.id ||
201 this.state.items !== nextState.items ||
202 this.props.assetsLoading !== nextProps.assetsLoading ||
203 this.props.assetsItems.length !== nextProps.assetsItems.length
204 );
205 }
206
207 /**
208 * Handling of the save button. We either create or update an asset based on the url
209 *
210 * @param formData
211 */
212 public handleFormSubmit = (formData: Values) => {
213 if (this.props.match.params.id !== "new") {
214 const items: SubCategoryItemPutDto[] = Object.keys(formData).map(key => {
215 const foundApplicationItem = this.props.assetsItems.find(
216 item => item.item_id === parseInt(this.props.match.params.id, 10)
217 ) as ApplicationItem;
218 const foundApplicationDetailsItem = foundApplicationItem.items.find(
219 item => item.item_code === key
220 );
221
222 return {
223 item_value: formData[key],
224 item_code: key,
225 item_detail_id: foundApplicationDetailsItem
226 ? foundApplicationDetailsItem.item_detail_id
227 : null
228 };
229 });
230 const foundSubcategoryItem = items.find(
231 item => item.item_code === "subcategory_code"
232 );
233 const postData: AssetPutDto = {
234 category: this.props.category,
235 subcategory: foundSubcategoryItem
236 ? foundSubcategoryItem.item_value
237 : "",
238 item_id: parseInt(this.props.match.params.id, 10),
239 details: items.filter(
240 item =>
241 item.item_code !== "subcategory_code" &&
242 item.item_code !== "category" &&
243 item.item_code !== "item_detail_id"
244 )
245 };
246 this.props.updateAssetRequest(postData, postData.item_id);
247 } else {
248 const items: SubCategoryItemPostDto[] = Object.keys(formData).map(key => {
249 return {
250 item_value: formData[key],
251 item_code: key
252 };
253 });
254
255 const foundSubcategoryItem = items.find(
256 item => item.item_code === "subcategory_code"
257 );
258 const postData: AssetPostDto = {
259 category: this.props.category,
260 subcategory: foundSubcategoryItem
261 ? foundSubcategoryItem.item_value
262 : "",
263 details: items.filter(
264 item =>
265 item.item_code !== "subcategory_code" &&
266 item.item_code !== "category" &&
267 item.item_code !== "item_detail_id"
268 )
269 };
270 this.props.createAssetRequest(postData);
271 }
272
273 this.props.reset();
274 this.setState({
275 items: []
276 });
277 };
278
279 /**
280 * This method renders the form fields in the screen that are sub fields of the selected category
281 * @param event
282 * @param value
283 */
284 public renderSubFields = (
285 event: React.ChangeEvent<HTMLElement>,
286 value: string
287 ) => {
288 if (!this.props.assetParams) {
289 return;
290 }
291 const foundItem = this.props.assetParams.subcategory.find(
292 (subCategory: Subcategory) => subCategory.subcategory_code === value
293 );
294 if (!foundItem) {
295 return;
296 }
297
298 this.setState(
299 {
300 items: foundItem.subcategory_items
301 },
302
303 () => {
304 if (foundItem.subcategory_items.length !== 0) {
305 const formFields = Object.keys(this.props.initialValues).filter(
306 key => key !== "category" && key !== "subcategory_code"
307 );
308
309 for (const field of formFields) {
310 this.props.dispatch(change("ASSETS", field, null));
311 }
312 for (const subCategoryItem of this.state.items) {
313 this.props.dispatch(
314 unregisterField("ASSETS", subCategoryItem.item_code)
315 );
316 }
317 for (const subCategoryItem of foundItem.subcategory_items) {
318 this.props.dispatch(
319 registerField("ASSETS", subCategoryItem.item_code, "Field")
320 );
321 }
322 setTimeout(
323 () => (
324 this.props.dispatch(
325 focus("ASSETS", foundItem.subcategory_items[0].item_code)
326 ),
327 3000
328 )
329 );
330 }
331 }
332 );
333 };
334
335 /**
336 * Present the asset name in the table. We must filter it based on the category code
337 * @param asset
338 */
339 public renderAssetName(asset: ApplicationItem) {
340 if (!this.props.assetParams) {
341 return null;
342 }
343
344 const foundAsset = this.props.assetParams.subcategory.find(
345 category => category.subcategory_code === asset.subcategory_code
346 );
347
348 if (foundAsset) {
349 return foundAsset.subcategory_name;
350 }
351 return null;
352 }
353
354 public suggestionsChangeHandler = (selectedItem: any) => {
355 console.log("hey");
356 this.setState({
357 selectedBank: selectedItem,
358 suggestionsInputValue: selectedItem
359 });
360 };
361 public suggestionsStateChangeHandler = (changes: any) => {
362 this.setState({
363 selectedBank: changes.selectedItem ? changes.selectedItem : null
364 });
365 };
366 public suggestionsOnInputChange = (event: any) => {
367 const value = event.target.value;
368 console.log(value);
369 this.setState({ suggestionsInputValue: value });
370 };
371
372 /**
373 * Present the value of the asset in the table. We filter the value property of all the items
374 * @param items
375 */
376 public renderAssetValue(items: ApplicationItemDetails[]) {
377 const item = items.find(i => i.is_value_estimate);
378 if (item) {
379 return (
380 <NumberFormat
381 displayType={"text"}
382 value={item.item_value}
383 thousandSeparator={true}
384 prefix={"$"}
385 />
386 );
387 }
388 return null;
389 }
390 public handleChange = (
391 event: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>
392 ) => {
393 const value = event.target.value;
394 return value;
395 };
396
397 public deleteAsset = (asset: ApplicationItem) => (
398 e: React.MouseEvent<HTMLElement>
399 ) => {
400 this.props.deleteAssetRequest(asset.item_id);
401 this.props.reset();
402
403 this.setState({
404 items: []
405 });
406 };
407
408 public render() {
409 const {
410 classes,
411 handleSubmit,
412 assetParams,
413 loading,
414 assetsLoading,
415 assetsItems,
416 lovListParams
417 } = this.props;
418
419 if (!assetParams || loading || assetsLoading) {
420 return <Loader />;
421 }
422
423 return (
424 <Card className={classes.cardBorder}>
425 <CardContent>
426 <Typography className={classes.title}>
427 {this.props.category.toUpperCase()}
428 </Typography>
429 <Typography color="textSecondary">
430 Take a moment to review your advanced profile. You can edit any
431 section before submitting.
432 </Typography>
433 <br />
434 <br />
435 <br />
436 <form onSubmit={handleSubmit(this.handleFormSubmit)}>
437 <div>
438 <Typography variant={"subtitle1"}>
439 <b>Category</b>
440 </Typography>
441 <Field
442 name="subcategory_code"
443 component={SelectField}
444 validate={[required]}
445 onChange={this.renderSubFields}
446 className={classes.category}
447 >
448 {assetsItems.length > 0
449 ? assetParams.subcategory
450 .slice(1, assetParams.subcategory.length - 1)
451 .map((subCategory: Subcategory, index: number) => (
452 <MenuItem
453 key={index}
454 value={subCategory.subcategory_code}
455 >
456 {subCategory.subcategory_name}
457 </MenuItem>
458 ))
459 : assetParams.subcategory.map(
460 (subCategory: Subcategory, index: number) => (
461 <MenuItem
462 key={index}
463 value={subCategory.subcategory_code}
464 >
465 {subCategory.subcategory_name}
466 </MenuItem>
467 )
468 )}
469 </Field>
470 </div>
471 {this.state.items.map((subcategoryItem: SubcategoryItem) => (
472 <React.Fragment>
473 <br />
474 <Typography variant={"subtitle1"}>
475 <b>{subcategoryItem.item_name}</b>
476 </Typography>
477 {subcategoryItem.control_type ===
478 ApplicationControlType.CURRENCY && (
479 <Field
480 className={classes.category}
481 name={subcategoryItem.item_code}
482 variant="outlined"
483 margin="dense"
484 component={TextField}
485 InputProps={{
486 inputComponent: CurrencyFormatCustom
487 }}
488 validate={[required]}
489 />
490 )}
491 {(subcategoryItem.control_type ===
492 ApplicationControlType.TEXT ||
493 subcategoryItem.control_type ===
494 ApplicationControlType.TEXTAREA) && (
495 <Field
496 className={classes.category}
497 name={subcategoryItem.item_code}
498 variant="outlined"
499 margin="dense"
500 label={subcategoryItem.item_name}
501 component={TextField}
502 multiline={
503 subcategoryItem.control_type ===
504 ApplicationControlType.TEXTAREA
505 }
506 validate={[required]}
507 />
508 )}
509 {subcategoryItem.control_type ===
510 ApplicationControlType.SEARCH &&
511 subcategoryItem.item_type ===
512 ApplicationControlType.SELECT && (
513 <Field
514 className={classes.category}
515 name={subcategoryItem.item_code}
516 variant="outlined"
517 margin="dense"
518 label={subcategoryItem.item_name}
519 component={Autosuggest as any}
520 validate={[required]}
521 items={lovListParams[1].filter(banks => {
522 console.log(
523 "filter",
524 banks,
525 this.state.suggestionsInputValue
526 );
527 return (
528 banks
529 .toLocaleLowerCase()
530 .indexOf(
531 this.state.suggestionsInputValue.toLocaleLowerCase()
532 ) !== -1
533 );
534 })}
535 selectedItem={this.state.selectedBank}
536 inputValue={this.state.suggestionsInputValue}
537 changeHandler={this.suggestionsChangeHandler}
538 onInputChange={this.suggestionsOnInputChange}
539 stateChangeHandler={this.suggestionsStateChangeHandler}
540 />
541 )}
542 {subcategoryItem.control_type ===
543 ApplicationControlType.SELECT && (
544 <Field
545 className={classes.category}
546 name={subcategoryItem.item_code}
547 variant="outlined"
548 margin="dense"
549 label={subcategoryItem.item_name}
550 component={SelectField as any}
551 validate={[required]}
552 >
553 {/* {subcategoryItem.list_of_values &&
554 subcategoryItem.list_of_values.map(val => (
555 <MenuItem value={val}>{val}</MenuItem>
556 ))} */}
557 {lovListParams &&
558 lovListParams[subcategoryItem.lov_list_id!].map(val => (
559 <MenuItem value={val}>{val} </MenuItem>
560 ))}
561 </Field>
562 )}
563 <br />
564 </React.Fragment>
565 ))}
566 <br />
567 <br />
568 <Button
569 type="submit"
570 variant="contained"
571 color="primary"
572 disabled={
573 assetsItems[0] && assetsItems[0].subcategory_code === "no_asset"
574 ? true
575 : assetsItems[0] &&
576 assetsItems[0].subcategory_code === "no_liablty"
577 ? true
578 : false
579 }
580 className={classes.submitButton}
581 >
582 <Typography color={"inherit"}>
583 <b>SAVE</b>
584 </Typography>
585 </Button>
586 </form>
587 <br />
588 <br />
589 {assetsItems.length > 0 && (
590 <Paper className={classes.tableWrapper}>
591 <Table>
592 <TableHead>
593 <TableRow>
594 <TableCell>Category</TableCell>
595 <TableCell>Value</TableCell>
596 <TableCell numeric={true} />
597 </TableRow>
598 </TableHead>
599 <TableBody>
600 {assetsItems.map(asset => (
601 <TableRow className={classes.row} key={asset.item_id}>
602 <TableCell>{this.renderAssetName(asset)}</TableCell>
603 <TableCell>
604 {this.renderAssetValue(asset.items)}
605 </TableCell>
606
607 <TableCell numeric={true}>
608 {assetsItems[0] &&
609 assetsItems[0].subcategory_code !== "no_liablty" ? (
610 assetsItems[0].subcategory_code !== "no_asset" ? (
611 // Check if assetsItems exists, and then it checks first
612 // if the code is of no liablty and then if its no asset
613 // The check could prolly happen in one line, but i could
614 // not get it work properly
615 <Button
616 component={(props: any) => (
617 <Link {...props} to={`${asset.item_id}`} />
618 )}
619 >
620 <Typography>EDIT</Typography>
621 </Button>
622 ) : null
623 ) : null}
624 <Button onClick={this.deleteAsset(asset)}>
625 <Typography color={"error"}>DELETE</Typography>
626 </Button>
627 </TableCell>
628 </TableRow>
629 ))}
630 </TableBody>
631 </Table>
632 </Paper>
633 )}
634 </CardContent>
635 </Card>
636 );
637 }
638}
639
640const mapStateToProps = (
641 { applicationUser, assets }: ApplicationState,
642 ownProps: Props
643) => {
644 let assetParams: Asset | null = null;
645 let lovListParams: LovList | null = null;
646 if (applicationUser.applicationParams) {
647 lovListParams = applicationUser.applicationParams.lov_list;
648 }
649 if (applicationUser.applicationParams) {
650 assetParams = applicationUser.applicationParams[ownProps.category];
651 }
652
653 let assetsItems: ApplicationItem[] = [];
654
655 if (
656 applicationUser.data &&
657 applicationUser.data.application.application_items
658 ) {
659 assetsItems = applicationUser.data.application.application_items.filter(
660 item => item.category_code === ownProps.category
661 );
662 }
663
664 const initialValues: Values = {};
665 const foundItem = assetsItems.find(
666 assetItem => assetItem.item_id === parseInt(ownProps.match.params.id, 10)
667 );
668 if (foundItem) {
669 initialValues.category = foundItem.category_code;
670 initialValues.subcategory_code = foundItem.subcategory_code;
671 for (const item of foundItem.items) {
672 initialValues[item.item_code] = item.item_value.toString();
673 }
674 }
675 return {
676 loading: applicationUser.loadingParams,
677 assetsItems,
678 assetsLoading: assets.loading,
679 errors: applicationUser.errors,
680 assetParams,
681 lovListParams,
682 initialValues,
683 enableReinitialize: true
684 };
685};
686const mapDispatchToProps = {
687 createAssetRequest,
688 updateAssetRequest,
689 deleteAssetRequest
690};
691
692const MyForm = reduxForm<Values>({
693 form: "ASSETS"
694})(Assets);
695
696export default connect(
697 mapStateToProps,
698 mapDispatchToProps
699)(withStyles(styles)(MyForm));