Skip to content

Commit 05fbdbe

Browse files
authored
Merge pull request #585 from cornell-dti/employee-modal-update
Driver Card Availability
2 parents e4e8750 + 47bca8c commit 05fbdbe

11 files changed

Lines changed: 900 additions & 350 deletions

frontend/src/components/EmployeeModal/EmployeeModal.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
22
import { FormProvider, useForm } from 'react-hook-form';
33
import Modal from '../Modal/Modal';
44
import { Button } from '../FormElements/FormElements';
5-
import { Employee, ObjectType } from '../../types/index';
5+
import { ObjectType } from '../../types/index';
66
import EmployeeInfo from './EmployeeInfo';
77
import RoleSelector from './RoleSelector';
88
import StartDate from './StartDate';
@@ -13,13 +13,21 @@ import { useEmployees } from '../../context/EmployeesContext';
1313
import { useToast, ToastStatus } from '../../context/toastContext';
1414
import axios from '../../util/axios';
1515

16+
enum DayOfWeek {
17+
MONDAY = 'MON',
18+
TUESDAY = 'TUE',
19+
WEDNESDAY = 'WED',
20+
THURSDAY = 'THURS',
21+
FRIDAY = 'FRI',
22+
}
23+
1624
type AdminData = {
1725
type: string[];
1826
isDriver: boolean;
1927
};
2028

2129
type DriverData = {
22-
availability: any;
30+
availability: DayOfWeek[];
2331
startDate: string;
2432
};
2533

@@ -83,19 +91,6 @@ const EmployeeModal = ({
8391
setIsOpen(false);
8492
};
8593

86-
function parseAvailability(availability: ObjectType[]) {
87-
const result: ObjectType = {};
88-
if (availability === null || availability === undefined) {
89-
return result;
90-
}
91-
availability.forEach(({ startTime, endTime, days }) => {
92-
days.forEach((day: string) => {
93-
result[day] = { startTime, endTime };
94-
});
95-
});
96-
return result;
97-
}
98-
9994
/**
10095
* Uploads an employee's photo (in base64 format) to the backend.
10196
*
@@ -304,7 +299,7 @@ const EmployeeModal = ({
304299

305300
if (hasDriver) {
306301
driver_data = {
307-
availability: parseAvailability(formData.availability),
302+
availability: formData.availability || [],
308303
startDate: formData.startDate,
309304
};
310305
}

frontend/src/components/EmployeeModal/WorkingHours.tsx

Lines changed: 78 additions & 217 deletions
Original file line numberDiff line numberDiff line change
@@ -1,235 +1,96 @@
1-
import React, { useCallback, useEffect, useState } from 'react';
2-
import cn from 'classnames';
3-
import moment from 'moment';
4-
import { useFormContext, UseFormRegister } from 'react-hook-form';
1+
import React, { useState } from 'react';
2+
import { useFormContext } from 'react-hook-form';
53
import styles from './employeemodal.module.css';
6-
import { Input, Label } from '../FormElements/FormElements';
7-
import { WeekProvider, useWeek } from './WeekContext';
84

9-
type FormData = {
10-
availability: {
11-
startTime: string;
12-
endTime: string;
13-
days: string[];
14-
}[];
15-
};
16-
17-
type AvailabilityInputProps = {
18-
index: number;
19-
existingTimeRange?: string;
20-
existingDayArray?: string[];
21-
hide: boolean;
22-
};
23-
24-
const AvailabilityInput: React.FC<AvailabilityInputProps> = ({
25-
index,
26-
existingTimeRange,
27-
existingDayArray,
28-
hide,
29-
}) => {
30-
const {
31-
selectDay,
32-
deselectDay,
33-
isDayOpen,
34-
isDaySelectedByInstance,
35-
getSelectedDays,
36-
} = useWeek();
37-
const {
38-
register,
39-
setValue,
40-
getValues,
41-
formState: { errors },
42-
} = useFormContext<FormData>();
43-
const dayLabels = {
44-
Mon: 'M',
45-
Tue: 'T',
46-
Wed: 'W',
47-
Thu: 'T',
48-
Fri: 'F',
49-
};
50-
const [existingTime, setExistingTime] = useState<string[]>();
51-
const instance = `availability.${index}` as const;
52-
const days = getSelectedDays(index);
53-
54-
const handleClick = (day: string) => {
55-
if (isDaySelectedByInstance(day, index)) {
56-
deselectDay(day);
57-
} else if (isDayOpen(day)) {
58-
selectDay(day, index);
59-
}
60-
};
61-
62-
const prefillDays = useCallback(() => {
63-
existingDayArray?.forEach((day) => {
64-
selectDay(day, index);
65-
});
66-
}, [existingDayArray, index, selectDay]);
67-
68-
const prefillTimeRange = useCallback(() => {
69-
if (existingTimeRange) {
70-
let [startTime, endTime] = existingTimeRange.split('-');
71-
startTime = formatTime(startTime);
72-
endTime = formatTime(endTime);
73-
setExistingTime([startTime, endTime]);
74-
}
75-
}, [existingTimeRange]);
76-
77-
useEffect(() => {
78-
prefillDays();
79-
prefillTimeRange();
80-
}, [prefillDays, prefillTimeRange]);
81-
82-
useEffect(() => {
83-
setValue(`${instance}.days`, days);
84-
}, [instance, days, setValue]);
85-
86-
const formatTime = (time: string): string =>
87-
moment(time, 'ha').format('HH:mm');
88-
89-
return (
90-
<div className={styles.availabilityInput}>
91-
<div className={styles.timeFlexbox}>
92-
<Label htmlFor={`${instance}.startTime`}>Start Time</Label>
93-
<Input
94-
id={`${instance}.startTime`}
95-
type="time"
96-
className={styles.timeInput}
97-
defaultValue={existingTime?.[0]}
98-
{...register(`${instance}.startTime` as const, { required: !hide })}
99-
/>
100-
{errors.availability?.[index]?.startTime && (
101-
<p className={styles.error}>Please enter a valid start time</p>
102-
)}
103-
</div>
104-
<p className={styles.toText}>to</p>
105-
<div className={styles.timeFlexbox}>
106-
<Label htmlFor={`${instance}.endTime`}>End Time</Label>
107-
<Input
108-
id={`${instance}.endTime`}
109-
type="time"
110-
className={styles.timeInput}
111-
defaultValue={existingTime?.[1]}
112-
{...register(`${instance}.endTime` as const, {
113-
required: !hide,
114-
validate: (endTime: string) => {
115-
const startTime = getValues(`${instance}.startTime` as const);
116-
return hide ? true : startTime < endTime;
117-
},
118-
})}
119-
/>
120-
{errors.availability?.[index]?.endTime && (
121-
<p className={styles.error}>Please enter a valid end time</p>
122-
)}
123-
</div>
124-
<p className={styles.repeatText}>Repeat on</p>
125-
<div className={styles.timeFlexbox}>
126-
<div className={styles.daysBox}>
127-
{Object.entries(dayLabels).map(([day, label]) => (
128-
<Input
129-
key={day}
130-
type="button"
131-
value={label}
132-
className={cn(styles.day, {
133-
[styles.daySelected]: isDaySelectedByInstance(day, index),
134-
})}
135-
onClick={() => handleClick(day)}
136-
/>
137-
))}
138-
</div>
139-
{errors.availability?.[index]?.days && (
140-
<p className={cn(styles.error, styles.dayError)}>
141-
Please select at least one day
142-
</p>
143-
)}
144-
</div>
145-
</div>
146-
);
147-
};
5+
enum DayOfWeek {
6+
MONDAY = 'MON',
7+
TUESDAY = 'TUE',
8+
WEDNESDAY = 'WED',
9+
THURSDAY = 'THURS',
10+
FRIDAY = 'FRI',
11+
}
14812

14913
type WorkingHoursProps = {
150-
existingAvailability?: string[][];
14+
existingAvailability?: string[];
15115
hide: boolean;
15216
};
153-
const WorkingHours: React.FC<WorkingHoursProps> = ({
154-
existingAvailability,
155-
hide,
156-
}) => {
157-
// Determine if we have existing availability (a non-empty array)
158-
const hasExisting = existingAvailability && existingAvailability.length > 0;
15917

160-
// If there is existing availability, use its length; otherwise, start at 1.
161-
const [numAvailability, setNumAvailability] = useState(
162-
hasExisting ? existingAvailability!.length : 1
18+
const WorkingHours = ({
19+
existingAvailability = [],
20+
hide,
21+
}: WorkingHoursProps) => {
22+
const { register, setValue, getValues } = useFormContext();
23+
const [selectedDays, setSelectedDays] = useState<DayOfWeek[]>(
24+
(existingAvailability as DayOfWeek[]) || []
16325
);
16426

165-
// This array will be built from the existingAvailability data (if any)
166-
const [availabilityArray, setAvailabilityArray] = useState<
167-
[string, string[]][]
168-
>([]);
27+
const handleDayClick = (day: DayOfWeek) => {
28+
const updatedDays = selectedDays.includes(day)
29+
? selectedDays.filter((d) => d !== day)
30+
: [...selectedDays, day];
16931

170-
// When the "Add more" button is clicked, increment the number of availability inputs.
171-
const addAvailabilityInput = () => {
172-
setNumAvailability((n) => {
173-
const newValue = n + 1;
174-
console.log('New numAvailability:', newValue);
175-
return newValue;
176-
});
32+
setSelectedDays(updatedDays);
33+
setValue('availability', updatedDays);
17734
};
17835

179-
// Map the existingAvailability (an array of [day, timeRange] pairs)
180-
// into a Map where the key is the timeRange and the value is an array of days.
181-
const getAvailabilityMap = useCallback((): Map<string, string[]> => {
182-
const availabilityMap = new Map<string, string[]>();
183-
(existingAvailability || []).forEach((availability) => {
184-
const [day, timeRange] = availability;
185-
const dayArray = availabilityMap.get(timeRange) || [];
186-
dayArray.push(day);
187-
availabilityMap.set(timeRange, dayArray);
188-
});
189-
return availabilityMap;
190-
}, [existingAvailability]);
191-
192-
// Convert the Map into an array for easier mapping.
193-
const availabilityMapToArray = useCallback((map: Map<string, string[]>) => {
194-
const newAvailabilityArray: [string, string[]][] = Array.from(
195-
map,
196-
([timeRange, dayArray]) => [timeRange, dayArray]
197-
);
198-
setAvailabilityArray(newAvailabilityArray);
199-
}, []);
36+
// Register the availability field
37+
React.useEffect(() => {
38+
register('availability');
39+
setValue('availability', selectedDays);
40+
}, [register, setValue, selectedDays]);
20041

201-
useEffect(() => {
202-
if (hasExisting) {
203-
availabilityMapToArray(getAvailabilityMap());
204-
}
205-
}, [hasExisting, getAvailabilityMap, availabilityMapToArray]);
42+
if (hide) return null;
20643

20744
return (
208-
<div className={cn(styles.workingHours, { [styles.hidden]: hide })}>
209-
<p className={styles.workingHoursTitle}>Working Hours</p>
210-
<WeekProvider>
211-
{hasExisting
212-
? availabilityArray.map(([timeRange, dayArray], index) => (
213-
<AvailabilityInput
214-
key={index}
215-
index={index}
216-
existingTimeRange={timeRange}
217-
existingDayArray={dayArray}
218-
hide={hide}
219-
/>
220-
))
221-
: // If no existing availability, use fallback: create an array based on numAvailability.
222-
[...Array(numAvailability)].map((_, index) => (
223-
<AvailabilityInput key={index} index={index} hide={hide} />
224-
))}
225-
</WeekProvider>
226-
<button
227-
type="button"
228-
className={styles.addAvailabilityInput}
229-
onClick={addAvailabilityInput}
230-
>
231-
+ Add more
232-
</button>
45+
<div className={styles.availabilityContainer}>
46+
<h3 className={styles.workingHoursTitle}>Working Days</h3>
47+
<div className={styles.daysContainer}>
48+
<button
49+
type="button"
50+
className={`${styles.dayButton} ${
51+
selectedDays.includes(DayOfWeek.MONDAY) ? styles.daySelected : ''
52+
}`}
53+
onClick={() => handleDayClick(DayOfWeek.MONDAY)}
54+
>
55+
M
56+
</button>
57+
<button
58+
type="button"
59+
className={`${styles.dayButton} ${
60+
selectedDays.includes(DayOfWeek.TUESDAY) ? styles.daySelected : ''
61+
}`}
62+
onClick={() => handleDayClick(DayOfWeek.TUESDAY)}
63+
>
64+
T
65+
</button>
66+
<button
67+
type="button"
68+
className={`${styles.dayButton} ${
69+
selectedDays.includes(DayOfWeek.WEDNESDAY) ? styles.daySelected : ''
70+
}`}
71+
onClick={() => handleDayClick(DayOfWeek.WEDNESDAY)}
72+
>
73+
W
74+
</button>
75+
<button
76+
type="button"
77+
className={`${styles.dayButton} ${
78+
selectedDays.includes(DayOfWeek.THURSDAY) ? styles.daySelected : ''
79+
}`}
80+
onClick={() => handleDayClick(DayOfWeek.THURSDAY)}
81+
>
82+
T
83+
</button>
84+
<button
85+
type="button"
86+
className={`${styles.dayButton} ${
87+
selectedDays.includes(DayOfWeek.FRIDAY) ? styles.daySelected : ''
88+
}`}
89+
onClick={() => handleDayClick(DayOfWeek.FRIDAY)}
90+
>
91+
F
92+
</button>
93+
</div>
23394
</div>
23495
);
23596
};

0 commit comments

Comments
 (0)