Skip to content

Commit a0b61f0

Browse files
committed
Merge branch 'master' of https://github.com/cornell-dti/carriage-web into rj353/location_modal
2 parents ed92a24 + 11b5e40 commit a0b61f0

27 files changed

+1770
-391
lines changed

frontend/package-lock.json

Lines changed: 489 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
"description": "",
55
"main": "index.js",
66
"dependencies": {
7+
"@emotion/react": "^11.13.3",
8+
"@emotion/styled": "^11.13.0",
9+
"@mui/icons-material": "^6.1.5",
10+
"@mui/material": "^6.1.5",
711
"@react-aria/utils": "^3.25.2",
812
"@react-oauth/google": "^0.12.1",
913
"@types/crypto-js": "^4.2.2",
@@ -33,6 +37,7 @@
3337
"react-router-dom": "^6.26.2",
3438
"react-router-hash-link": "^2.4.3",
3539
"react-scripts": "^5.0.1",
40+
"react-select": "^5.8.1",
3641
"reactjs-popup": "^2.0.6"
3742
},
3843
"scripts": {
@@ -71,4 +76,4 @@
7176
"eslint-plugin-react-hooks": "^4.6.0",
7277
"prettier": "2.8.8"
7378
}
74-
}
79+
}

frontend/src/components/Card/card.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.card {
22
display: inline-block;
33
width: 17.2rem;
4-
height: 23rem;
4+
/* height: 23rem; */
55
background-color: white;
66
border-radius: 0.5rem;
77
overflow: hidden;

frontend/src/components/EmployeeCards/EmployeeCards.tsx

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { useState, useMemo } from 'react';
22
import { Link } from 'react-router-dom';
33
import Card, { CardInfo } from '../Card/Card';
44
import styles from './employeecards.module.css';
5-
import { clock, phone, wheel, user } from '../../icons/userInfo/index';
5+
import { phone, wheel, user } from '../../icons/userInfo/index'; // clock,
66
import { Employee } from '../../types';
77
import { useEmployees } from '../../context/EmployeesContext';
88
import { AdminType } from '../../../../server/src/models/admin';
99
import { DriverType } from '../../../../server/src/models/driver';
1010
import { Button } from '../FormElements/FormElements';
11+
import Pagination from '@mui/material/Pagination';
1112

1213
const formatPhone = (phoneNumber: string) => {
1314
const areaCode = phoneNumber.substring(0, 3);
@@ -65,32 +66,6 @@ const EmployeeCard = ({
6566
}: EmployeeCardProps) => {
6667
const netId = email.split('@')[0];
6768
const fmtPhone = formatPhone(phoneNumber);
68-
69-
/**
70-
* Formats availability, represented by an object that maps available days to
71-
* start and end times, into a printable string with availabilities formatted as '[day]: [start] - [end]'.
72-
* Ignores malformed availabilities, e.g. missing start or end times, from being printed.
73-
*
74-
* @param availability the driver's availability, represented as an object map of days to start and end times
75-
* @returns a string representation of a driver's availibility
76-
*/
77-
const formatAvail = (availability: {
78-
[key: string]: { startTime: string; endTime: string };
79-
}) => {
80-
if (!availability) {
81-
return 'N/A';
82-
}
83-
84-
return Object.entries(availability)
85-
.filter(([_, timeRange]) => timeRange?.startTime && timeRange?.endTime)
86-
.map(
87-
([day, timeRange]) =>
88-
`${day}: ${timeRange.startTime} - ${timeRange.endTime}`
89-
)
90-
.join('\n ');
91-
};
92-
93-
const parsedAvail = formatAvail(availability!);
9469
const isAdmin = isDriver !== undefined;
9570
const isBoth = isDriver && isDriver == true;
9671
const roles = (): string => {
@@ -106,7 +81,7 @@ const EmployeeCard = ({
10681
netId,
10782
type,
10883
phone: fmtPhone,
109-
availability: parsedAvail,
84+
// availability: parsedAvail,
11085
photoLink,
11186
startDate,
11287
};
@@ -130,15 +105,6 @@ const EmployeeCard = ({
130105
<CardInfo icon={phone} alt="phone">
131106
<p>{fmtPhone}</p>
132107
</CardInfo>
133-
134-
<CardInfo icon={clock} alt="clock">
135-
{parsedAvail ? (
136-
<p className={styles.timeText}>{parsedAvail}</p>
137-
) : (
138-
<p>N/A</p>
139-
)}
140-
</CardInfo>
141-
142108
<CardInfo
143109
icon={isAdmin || isBoth ? user : wheel}
144110
alt={isAdmin || isBoth ? 'admin' : 'wheel'}
@@ -150,6 +116,13 @@ const EmployeeCard = ({
150116
);
151117
};
152118

119+
/* <CardInfo icon={clock} alt="clock">
120+
{parsedAvail ? (
121+
<p className={styles.timeText}>{parsedAvail}</p>
122+
) : (
123+
<p>N/A</p>
124+
)}
125+
</CardInfo> */
153126
const searchableFields = (employee: DriverType | AdminType) => {
154127
const fields = [
155128
employee.firstName,
@@ -183,6 +156,10 @@ const EmployeeCards = ({ query }: EmployeeCardsProps) => {
183156
const [filterAdmin, setFilterAdmin] = useState(false);
184157
const [filterDriver, setFilterDriver] = useState(false);
185158

159+
// Pagination state
160+
const [page, setPage] = useState(1);
161+
const [pageSize] = useState(8);
162+
186163
const employees = useMemo(() => {
187164
const allEmployees = [...admins, ...drivers];
188165
const employeeSet: Record<string, DriverType | AdminType> = {};
@@ -210,6 +187,18 @@ const EmployeeCards = ({ query }: EmployeeCardsProps) => {
210187
return sortedEmployees.filter(matchesQuery(query));
211188
}, [admins, drivers, query, filterAdmin, filterDriver]);
212189

190+
// Calculate total pages and get the employees on given page
191+
const totalPages = Math.ceil(employees.length / pageSize);
192+
const paginatedEmployees = employees.slice(
193+
(page - 1) * pageSize,
194+
page * pageSize
195+
);
196+
const handlePageChange = (
197+
event: React.ChangeEvent<unknown>,
198+
value: number
199+
) => {
200+
setPage(value);
201+
};
213202
return (
214203
<>
215204
<div className={styles.filtersContainer}>
@@ -229,14 +218,26 @@ const EmployeeCards = ({ query }: EmployeeCardsProps) => {
229218
</Button>
230219
</div>
231220
<div className={styles.cardsContainer}>
232-
{employees.map((employee) => (
221+
{paginatedEmployees.map((employee) => (
233222
<EmployeeCard
234223
key={employee.id}
235224
id={employee.id}
236225
employee={employee}
237226
/>
238227
))}
239228
</div>
229+
{totalPages > 1 && (
230+
<div className={styles.paginationContainer}>
231+
<Pagination
232+
count={totalPages}
233+
page={page}
234+
onChange={handlePageChange}
235+
size="large"
236+
showFirstButton
237+
showLastButton
238+
/>
239+
</div>
240+
)}
240241
</>
241242
);
242243
};

frontend/src/components/EmployeeCards/employeecards.module.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
.cardsContainer {
2+
display: flex;
3+
flex-wrap: wrap;
4+
height: 38rem; /* Fix the cards to a certain height so that pagination component stays fixed */
25
margin-top: 1rem;
36
}
47

@@ -21,3 +24,10 @@
2124
display: flex;
2225
gap: 1rem;
2326
}
27+
28+
.paginationContainer {
29+
display: flex;
30+
justify-content: center;
31+
margin: 2rem 0;
32+
padding: 1rem;
33+
}

frontend/src/components/FormElements/FormElements.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import React, { SelectHTMLAttributes } from 'react';
22
import cn from 'classnames';
33
import styles from './formelements.module.css';
4+
import Select, { ActionMeta, Props as SelectProps } from 'react-select';
5+
import {
6+
Control,
7+
RegisterOptions,
8+
useController,
9+
Path,
10+
FieldValues,
11+
} from 'react-hook-form';
412

513
type LabelType = React.DetailedHTMLProps<
614
React.LabelHTMLAttributes<HTMLLabelElement>,
@@ -73,3 +81,61 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
7381
);
7482
}
7583
);
84+
85+
type Option = {
86+
id: string;
87+
name: string;
88+
};
89+
90+
type SelectOption = {
91+
value: string;
92+
label: string;
93+
};
94+
95+
type SelectComponentProps<TFieldValues extends FieldValues> = SelectProps & {
96+
control: Control<TFieldValues>;
97+
name: Path<TFieldValues>;
98+
datalist: Option[];
99+
className?: string;
100+
rules?: RegisterOptions<TFieldValues>;
101+
};
102+
103+
export const SelectComponent = <TFieldValues extends FieldValues>({
104+
control,
105+
name,
106+
datalist,
107+
className,
108+
rules,
109+
...rest
110+
}: SelectComponentProps<TFieldValues>) => {
111+
const {
112+
field: { onChange, value, ref, ...inputProps },
113+
} = useController<TFieldValues>({
114+
name,
115+
control,
116+
rules,
117+
});
118+
119+
const transformedOptions = datalist.map((data) => ({
120+
value: data.id,
121+
label: data.name,
122+
}));
123+
124+
const selectedOption = transformedOptions.find(
125+
(option) => option.value === value
126+
);
127+
128+
return (
129+
<Select
130+
{...inputProps}
131+
options={transformedOptions}
132+
className={cn(styles.customSelect, className)}
133+
onChange={(newValue: unknown) => {
134+
const option = newValue as SelectOption | null;
135+
onChange(option?.value);
136+
}}
137+
value={selectedOption}
138+
{...rest}
139+
/>
140+
);
141+
};

0 commit comments

Comments
 (0)