frontend/containers/Travel/Header.tsx (view raw)
1import moment from 'moment';
2import Linkify from 'linkify-react';
3import Typography from '@mui/material/Typography';
4import IconButton from '@mui/material/IconButton';
5import TuneIcon from '@mui/icons-material/Tune';
6import Box from '@mui/material/Box';
7import Link from '@mui/material/Link';
8import LinearProgress from '@mui/material/LinearProgress';
9import Chip from '@mui/material/Chip';
10import {useTheme} from '@mui/material/styles';
11import {useTranslation} from 'next-i18next';
12import getMapsLink from '../../lib/getMapsLink';
13import useMapStore from '../../stores/useMapStore';
14import usePermissions from '../../hooks/usePermissions';
15import useProfile from '../../hooks/useProfile';
16import DetailsLink from '../DetailsLink';
17import {TravelEntity} from '../../generated/graphql';
18import {getFormatedPhoneNumber} from '../../lib/phoneNumbers';
19import useEventStore from '../../stores/useEventStore';
20
21interface Props {
22 travel: TravelEntity;
23 toggleEditing: () => void;
24}
25
26const MAPBOX_CONFIGURED = process.env['MAPBOX_CONFIGURED'] || false;
27
28const Header = (props: Props) => {
29 const {travel, toggleEditing} = props;
30 const theme = useTheme();
31 const {t} = useTranslation();
32 const {
33 userPermissions: {canEditTravel, canSeeTravelDetails},
34 } = usePermissions();
35 const setFocusOnTravel = useMapStore(s => s.setFocusOnTravel);
36 const {userId} = useProfile();
37 const isReturnEvent = useEventStore(s => s.event?.isReturnEvent);
38 const isUserTripCreator =
39 userId && userId === travel.attributes.user?.data?.id;
40
41 const passengersCount = travel?.attributes.passengers?.data.length || 0;
42 const availableSeats = travel?.attributes.seats - passengersCount || 0;
43
44 const tripHasValidCoordinates =
45 travel.attributes.meeting_latitude && travel.attributes.meeting_longitude;
46
47 return (
48 <Box
49 p={2}
50 onClick={() => {
51 setFocusOnTravel(travel);
52 const mapElement = document?.getElementById('map');
53 mapElement?.scrollIntoView({behavior: 'smooth'});
54 }}
55 >
56 {canEditTravel(travel) && (
57 <IconButton
58 size="small"
59 color="primary"
60 sx={{
61 position: 'absolute',
62 top: theme.spacing(1),
63 right: 0,
64 margin: theme.spacing(1),
65 }}
66 onClick={e => {
67 e.stopPropagation();
68 toggleEditing();
69 }}
70 id="EditTravelBtn"
71 >
72 <TuneIcon />
73 </IconButton>
74 )}
75 {!!travel.attributes.departureDate && (
76 <Typography
77 variant="overline"
78 sx={{color: 'GrayText', textTransform: 'capitalize'}}
79 id="TravelDeparture"
80 >
81 {moment(travel.attributes.departureDate).format('dddd LL')}{' '}
82 {travel.attributes.departureTime}
83 </Typography>
84 )}
85 <Typography variant="subtitle1">
86 {travel.attributes.vehicleName}
87 {isUserTripCreator && (
88 <Typography component="span">
89 <Chip sx={{mx: 1}} label={t`generic.me`} variant="outlined" />
90 </Typography>
91 )}
92 </Typography>
93
94 {!!travel.attributes.phone_number && canSeeTravelDetails(travel) && (
95 <Box sx={{marginTop: 2}}>
96 <Typography variant="overline" sx={{color: 'GrayText'}}>
97 {t('travel.fields.phone')}
98 </Typography>
99 <Typography variant="body1" id="TravelPhone">
100 {getFormatedPhoneNumber({
101 phone: travel.attributes.phone_number,
102 phoneCountry: travel.attributes.phoneCountry,
103 })}
104 </Typography>
105 </Box>
106 )}
107 {!!travel.attributes.meeting && (
108 <Box sx={{marginTop: 2}}>
109 <Typography variant="overline" sx={{color: 'GrayText'}}>
110 {t(isReturnEvent ? 'travel.destination' : 'travel.meeting')}
111 </Typography>
112 <Typography variant="body1">
113 <Link
114 component="a"
115 target="_blank"
116 rel="noopener noreferrer"
117 href={getMapsLink(travel.attributes.meeting)}
118 >
119 {travel.attributes.meeting}
120 </Link>
121 </Typography>
122 {MAPBOX_CONFIGURED && !tripHasValidCoordinates && (
123 <Typography
124 variant="overline"
125 color="warning.main"
126 >{t`placeInput.noCoordinates`}</Typography>
127 )}
128 </Box>
129 )}
130 {!!travel.attributes.details && (
131 <Box sx={{marginTop: 2}}>
132 <Typography variant="overline" sx={{color: 'GrayText'}}>
133 {t('travel.fields.details')}
134 </Typography>
135
136 <Typography
137 variant="body1"
138 sx={{whiteSpace: 'pre-line'}}
139 onClick={e => e.stopPropagation()}
140 >
141 <Linkify options={{render: DetailsLink}}>
142 {travel.attributes.details}
143 </Linkify>
144 </Typography>
145 </Box>
146 )}
147 <LinearProgress
148 sx={{
149 width: 1,
150 mt: 2,
151 mb: 1,
152 backgroundColor: 'LightGray',
153 '& .MuiLinearProgress-bar': {
154 backgroundColor: 'Gray',
155 },
156 }}
157 value={(passengersCount / travel?.attributes.seats) * 100}
158 variant="determinate"
159 />
160 <Box display="flex" justifyContent="space-between" sx={{width: 1}}>
161 <Typography variant="body1" sx={{color: 'GrayText'}}>
162 {t('passenger.assign.seats', {count: availableSeats})}
163 </Typography>
164 </Box>
165 </Box>
166 );
167};
168
169export default Header;