UNPKG

@tractorzoom/equipment-card

Version:
287 lines (260 loc) 11.4 kB
import React, { useEffect, useRef, useState } from 'react'; import { formatNumberWithThousandSeparator, getLocation, getTopAttributesForCategoryAndSubCategory, } from '@tractorzoom/equipment-attributes'; import AddRoundedIcon from '@material-ui/icons/AddRounded'; import { Box } from '@material-ui/core'; import Card from '@material-ui/core/Card'; import CardActionArea from '@material-ui/core/CardActionArea'; import CardContent from '@material-ui/core/CardContent'; import CardMedia from '@material-ui/core/CardMedia'; import CheckRoundedIcon from '@material-ui/icons/CheckRounded'; import { DateTime } from 'luxon'; import Divider from '@material-ui/core/Divider'; import EquipmentCardSkeleton from './equipment-card-skeleton'; import IconButton from '@material-ui/core/IconButton'; import Lozenge from '@tractorzoom/lozenge'; import PropTypes from 'prop-types'; import Typography from '@material-ui/core/Typography'; import clsx from 'clsx'; import { useInView } from 'react-intersection-observer'; import useStyles from './styles'; const getAttrText = (attr, props) => { const attrName = attr.name; if (attrName === 'width' && (props['widthFeet'] || props['widthInches'])) { const ft = props['widthFeet']; const inches = props['widthInches']; if (ft && inches) { return `${formatNumberWithThousandSeparator(ft)}' ${inches}"`; } if (ft) { return `${formatNumberWithThousandSeparator(ft)} ft`; } return `${formatNumberWithThousandSeparator(inches)} in`; } if (attrName === 'capacity' && (props['ton'] || props['gallon'])) { const ton = props['ton']; const gallon = props['gallon']; if (ton) { return `${formatNumberWithThousandSeparator(ton)} T`; } return `${formatNumberWithThousandSeparator(gallon)} Gal`; } if (props[attrName]) { return formatNumberWithThousandSeparator(props[attr.name]) + ' ' + attr?.shortName; } return ''; }; const EquipmentCard = (props) => { const canvasRef = useRef(null); const classes = useStyles(); const getImageUrl = (url) => { if (url) { if (url.indexOf('http') >= 0) { return url.replace('tz-prod.s3.amazonaws.com', 's3.tractorzoom.com'); } return `https://s3.tractorzoom.com/${url}`; } return ''; }; const defaultImageUrl = props.imageUrl ? getImageUrl(props.imageUrl) : props.makeImageUrl ? getImageUrl(props.makeImageUrl) : '/img/nopicture.png'; const [imageUrl, setImageUrl] = useState(defaultImageUrl); const isSold = props.price > 0; const isDealer = props.sellerType === 'dealer'; const saleDate = props.saleDate ? props.saleDate : props.auctionDate; const formattedDate = DateTime.fromISO(saleDate).toLocaleString(); const auctionDate = DateTime.fromISO(saleDate); const formattedAuctionDate = auctionDate.toFormat('MMM d'); const formattedPrice = `$${formatNumberWithThousandSeparator(`${props.price}`)}`; const formattedListPrice = `$${formatNumberWithThousandSeparator(`${props.listPrice}`)}`; const isSelected = props.selectedEquipmentSet && props.selectedEquipmentSet.has(props.id); const { ref, inView } = useInView({ threshold: 0.7, }); const cardTitleText = `${props.year ? `${props.year} ` : ''}${props.make} ${props.model}`; const [hasBeenSeen, setHasBeenSeen] = useState(false); let styles = props.style; useEffect(() => { if (props.onVisible && inView && !hasBeenSeen) { setHasBeenSeen(true); props.onVisible(inView); } }, [props.onVisible, inView, hasBeenSeen]); if (props.isLoading) { return <EquipmentCardSkeleton {...props} />; } const attr = getTopAttributesForCategoryAndSubCategory(props.category, props.subcategory); if (!props.handleOpen) { styles = { ...styles, pointerEvents: 'none' }; } const handleToggleSelected = (event) => { props.handleEquipmentSelected(); event.stopPropagation(); }; const attributeArray = attr.map((attribute) => getAttrText(attribute, props)).filter((attr) => !!attr); const imageError = (e) => { e.target.onerror = null; if (imageUrl === getImageUrl(props.makeImageUrl) || !props.makeImageUrl) { setImageUrl('https://s3.tractorzoom.com/img/nopicture.png'); } else { setImageUrl(getImageUrl(props.makeImageUrl)); } }; const imageAltText = `${cardTitleText} Equipment Image`; const NextImage = () => ( <props.imageComponent alt={imageAltText} height={233} objectFit='cover' onError={imageError} src={imageUrl} width={350} /> ); return ( <div className={classes.cardHolder}> <canvas className={classes.canvas} ref={canvasRef}></canvas> <Card className={clsx(classes.root, { [classes.selectedCard]: isSelected, })} data-cy='equipment-card' data-guid={props.id} ref={ref} style={styles} variant='outlined' > <CardActionArea data-tour={props.shouldHaveDataTour ? 'equipment-card-action-area' : ''} onClick={props.handleOpen} > <CardMedia alt={imageAltText} className={classes.media} component={props.imageComponent ? NextImage : 'img'} data-cy='equipment-card-image' onError={imageError} src={imageUrl} title='Equipment Image' /> <CardContent classes={{ root: classes.cardContent }}> <Box alignItems='center' display='flex' justifyContent='space-between' style={{ marginBottom: 4 }} > <Typography className={classes.makeModelTitle} component='span' data-cy='equipment-card-make-model' variant='h5' > {cardTitleText} </Typography> {props.sellerType && ( <Lozenge backgroundColor={isDealer ? '#DAE7FE' : '#DFFCED'} color={isDealer ? '#183A99' : '#045C3E'} style={{ margin: 4 }} > {isDealer ? 'DEALER' : 'AUCTION'} </Lozenge> )} </Box> <div className={classes.details}> <div data-cy='equipment-card-variable-detail'> <Typography className={classes.primaryDetail}> {attributeArray.length > 0 ? attributeArray[0] : '---'} </Typography> </div> <Typography className={isDealer ? classes.listPrice : classes.price} data-cy='equipment-card-price' > {isSold ? formattedPrice : isDealer ? formattedListPrice : formattedAuctionDate} </Typography> </div> <Typography className={classes.secondaryDetail} style={{ marginBottom: 10 }}> {attributeArray.length > 1 ? attributeArray[1] : '\u00a0'} </Typography> <Divider className={classes.divider} /> <div className={classes.locationAndSaleDate}> {isSold ? ( <> <Typography className={classes.auctionDetailsText}> {getLocation(props, true)} </Typography> <Typography className={classes.auctionDetailsText} style={{ color: '#0E1C3699' }}> Sold {formattedDate} </Typography> </> ) : ( <Box display='flex' flexDirection='column'> <Typography className={classes.auctionDetailsText}>{props.auctioneer}</Typography> <Typography className={classes.auctionDetailsText} style={{ color: '#0E1C3699' }}> {getLocation(props, true)} </Typography> </Box> )} </div> </CardContent> </CardActionArea> {!!props.handleEquipmentSelected && ( <IconButton aria-label='select equipment' className={isSelected ? classes.checkedButton : classes.selectButton} color='primary' data-cy='equipment-card-toggle-selection-button' data-tour={props.shouldHaveDataTour ? 'equipment-card-select-equipment' : ''} onClick={handleToggleSelected} title={isSelected ? 'Remove from custom average' : 'Add to custom average'} > {isSelected ? <CheckRoundedIcon fontSize='large' /> : <AddRoundedIcon fontSize='large' />} </IconButton> )} </Card> </div> ); }; EquipmentCard.defaultProps = { auctionDate: '', distance: null, imageUrl: '', saleDate: '', shouldHaveDataTour: false, year: null, }; EquipmentCard.propTypes = { auctionDate: PropTypes.string, auctioneer: PropTypes.string, category: PropTypes.string, distance: PropTypes.number, handleEquipmentSelected: PropTypes.func, handleOpen: PropTypes.func, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), imageComponent: PropTypes.func, imageUrl: PropTypes.string, isLoading: PropTypes.bool, listPrice: PropTypes.string, make: PropTypes.string, makeImageUrl: PropTypes.string, model: PropTypes.string, onVisible: PropTypes.func, price: PropTypes.number, saleDate: PropTypes.string, selectedEquipmentSet: PropTypes.object, sellerType: PropTypes.number, shouldHaveDataTour: PropTypes.bool, state: PropTypes.string, style: PropTypes.object, subcategory: PropTypes.string, year: PropTypes.number, }; export default EquipmentCard;