Skip to content

Commit 3b21508

Browse files
committed
fixed schedule button
1 parent 65be553 commit 3b21508

File tree

8 files changed

+1274
-13
lines changed

8 files changed

+1274
-13
lines changed
279 Bytes
Loading

frontend/src/components/dashboard/EditProfileModal.tsx

Lines changed: 553 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
import React, { useState, useEffect } from 'react';
2+
import {
3+
Box,
4+
Heading,
5+
Text,
6+
Button,
7+
HStack,
8+
Textarea,
9+
Image,
10+
} from '@chakra-ui/react';
11+
import TimeScheduler from './TimeScheduler';
12+
import type { TimeSlot } from './types';
13+
import { getUserData, updateMyAvailability, AvailabilityTemplateResponse } from '@/APIClients/userDataAPIClient';
14+
15+
interface ScheduleCallModalProps {
16+
isOpen: boolean;
17+
onClose: () => void;
18+
participantName: string;
19+
onSend: (timeSlots: TimeSlot[], additionalInfo: string) => void;
20+
}
21+
22+
const ScheduleCallModal: React.FC<ScheduleCallModalProps> = ({
23+
isOpen,
24+
onClose,
25+
participantName,
26+
onSend,
27+
}) => {
28+
const [selectedTimeSlots, setSelectedTimeSlots] = useState<TimeSlot[]>([]);
29+
const [additionalInfo, setAdditionalInfo] = useState('');
30+
const [isEditingAvailability, setIsEditingAvailability] = useState(false);
31+
const [availabilityTimeSlots, setAvailabilityTimeSlots] = useState<TimeSlot[]>([]);
32+
const [savingAvailability, setSavingAvailability] = useState(false);
33+
const [loadingAvailability, setLoadingAvailability] = useState(true);
34+
35+
// Helper function to convert AvailabilityTemplates to TimeSlots
36+
const convertTemplatesToTimeSlots = (templates: AvailabilityTemplateResponse[]): TimeSlot[] => {
37+
const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
38+
const timeSlots: TimeSlot[] = [];
39+
40+
templates.forEach(template => {
41+
const dayName = dayNames[template.dayOfWeek];
42+
const parseTime = (timeStr: string): { hour: number; minute: number } => {
43+
const parts = timeStr.split(':');
44+
return {
45+
hour: parseInt(parts[0], 10),
46+
minute: parseInt(parts[1], 10),
47+
};
48+
};
49+
50+
const startTime = parseTime(template.startTime);
51+
const endTime = parseTime(template.endTime);
52+
53+
for (let hour = startTime.hour; hour < endTime.hour; hour++) {
54+
timeSlots.push({
55+
day: dayName,
56+
time: `${hour}:00 - ${hour + 1}:00`,
57+
selected: true
58+
});
59+
}
60+
});
61+
62+
return timeSlots;
63+
};
64+
65+
// Load availability when modal opens
66+
useEffect(() => {
67+
if (isOpen) {
68+
setLoadingAvailability(true);
69+
const loadAvailability = async () => {
70+
try {
71+
const userData = await getUserData();
72+
if (userData?.availability && userData.availability.length > 0) {
73+
const timeSlots = convertTemplatesToTimeSlots(userData.availability);
74+
setAvailabilityTimeSlots(timeSlots);
75+
}
76+
} catch (error) {
77+
console.error('Error loading availability:', error);
78+
} finally {
79+
setLoadingAvailability(false);
80+
}
81+
};
82+
loadAvailability();
83+
}
84+
}, [isOpen]);
85+
86+
const handleAvailabilityChange = (timeSlots: TimeSlot[]) => {
87+
setAvailabilityTimeSlots(timeSlots);
88+
};
89+
90+
const handleSend = () => {
91+
onSend(selectedTimeSlots, additionalInfo);
92+
setSelectedTimeSlots([]);
93+
setAdditionalInfo('');
94+
};
95+
96+
const handleEditAvailability = () => {
97+
setIsEditingAvailability(true);
98+
};
99+
100+
const handleCancelEdit = () => {
101+
setIsEditingAvailability(false);
102+
// Reload availability from backend
103+
const loadAvailability = async () => {
104+
try {
105+
const userData = await getUserData();
106+
if (userData?.availability && userData.availability.length > 0) {
107+
const timeSlots = convertTemplatesToTimeSlots(userData.availability);
108+
setAvailabilityTimeSlots(timeSlots);
109+
}
110+
} catch (error) {
111+
console.error('Error loading availability:', error);
112+
}
113+
};
114+
loadAvailability();
115+
};
116+
117+
const handleSaveAvailability = async () => {
118+
const convertToTemplates = (timeSlots: TimeSlot[]): AvailabilityTemplateResponse[] => {
119+
const dayToIndex: Record<string, number> = {
120+
'Monday': 0,
121+
'Tuesday': 1,
122+
'Wednesday': 2,
123+
'Thursday': 3,
124+
'Friday': 4,
125+
'Saturday': 5,
126+
'Sunday': 6,
127+
};
128+
129+
const slotsByDay = timeSlots.reduce((acc, slot) => {
130+
if (!acc[slot.day]) {
131+
acc[slot.day] = [];
132+
}
133+
acc[slot.day].push(slot);
134+
return acc;
135+
}, {} as Record<string, TimeSlot[]>);
136+
137+
const templates: AvailabilityTemplateResponse[] = [];
138+
139+
Object.entries(slotsByDay).forEach(([day, slots]) => {
140+
const sortedSlots = slots.sort((a, b) => {
141+
const aHour = parseInt(a.time.split(':')[0]);
142+
const bHour = parseInt(b.time.split(':')[0]);
143+
return aHour - bHour;
144+
});
145+
146+
let rangeStart: number | null = null;
147+
let lastEndHour = -1;
148+
149+
sortedSlots.forEach((slot, index) => {
150+
const [startTimeStr, endTimeStr] = slot.time.split(' - ');
151+
const startHour = parseInt(startTimeStr.split(':')[0]);
152+
const endHour = parseInt(endTimeStr.split(':')[0]);
153+
154+
if (rangeStart === null) {
155+
rangeStart = startHour;
156+
lastEndHour = endHour;
157+
} else if (startHour === lastEndHour) {
158+
lastEndHour = endHour;
159+
} else {
160+
templates.push({
161+
dayOfWeek: dayToIndex[day],
162+
startTime: `${rangeStart.toString().padStart(2, '0')}:00:00`,
163+
endTime: `${lastEndHour.toString().padStart(2, '0')}:00:00`,
164+
});
165+
rangeStart = startHour;
166+
lastEndHour = endHour;
167+
}
168+
169+
if (index === sortedSlots.length - 1) {
170+
templates.push({
171+
dayOfWeek: dayToIndex[day],
172+
startTime: `${rangeStart!.toString().padStart(2, '0')}:00:00`,
173+
endTime: `${lastEndHour.toString().padStart(2, '0')}:00:00`,
174+
});
175+
}
176+
});
177+
});
178+
179+
return templates;
180+
};
181+
182+
setSavingAvailability(true);
183+
184+
try {
185+
const templates = convertToTemplates(availabilityTimeSlots);
186+
const success = await updateMyAvailability(templates);
187+
188+
if (success) {
189+
setIsEditingAvailability(false);
190+
// Reload availability
191+
const userData = await getUserData();
192+
if (userData?.availability) {
193+
const timeSlots = convertTemplatesToTimeSlots(userData.availability);
194+
setAvailabilityTimeSlots(timeSlots);
195+
}
196+
} else {
197+
alert('Failed to save availability. Please try again.');
198+
}
199+
} catch (err) {
200+
console.error('Error updating availability:', err);
201+
alert('An error occurred while saving. Please try again.');
202+
} finally {
203+
setSavingAvailability(false);
204+
}
205+
};
206+
207+
return (
208+
<Box
209+
position="fixed"
210+
top={0}
211+
left={0}
212+
right={0}
213+
bottom={0}
214+
bg="white"
215+
zIndex={9999}
216+
overflowY="auto"
217+
>
218+
<Box minH="100vh" bg="white" p={12}>
219+
<Box w="70%" mx="auto" overflowX="hidden">
220+
<HStack gap={2} mb={4} cursor="pointer" onClick={onClose}>
221+
<Image src="/icons/chevron-left.png" alt="Back" w="20px" h="20px" />
222+
<Text
223+
fontSize="16px"
224+
color="#1D3448"
225+
fontFamily="'Open Sans', sans-serif"
226+
fontWeight={400}
227+
>
228+
Back
229+
</Text>
230+
</HStack>
231+
232+
<Heading
233+
fontSize="36px"
234+
fontWeight={600}
235+
color="#1D3448"
236+
fontFamily="'Open Sans', sans-serif"
237+
letterSpacing="-1.5%"
238+
mb="8px"
239+
>
240+
Schedule call with your availability
241+
</Heading>
242+
243+
{loadingAvailability ? (
244+
<Box h="700px" w="100%" minW={0} mt="49px" display="flex" alignItems="center" justifyContent="center">
245+
<Text fontSize="16px" color="#6B7280" fontFamily="'Open Sans', sans-serif">
246+
Loading availability...
247+
</Text>
248+
</Box>
249+
) : !isEditingAvailability ? (
250+
<>
251+
<Text
252+
color="#6B7280"
253+
fontSize="16px"
254+
fontFamily="'Open Sans', sans-serif"
255+
fontWeight={400}
256+
mb={6}
257+
>
258+
Please review your general availability before sending.
259+
</Text>
260+
261+
<Box h="700px" w="100%" minW={0} mt="49px">
262+
<TimeScheduler
263+
showAvailability={true}
264+
onTimeSlotsChange={handleAvailabilityChange}
265+
initialTimeSlots={availabilityTimeSlots}
266+
readOnly={true}
267+
/>
268+
</Box>
269+
270+
<Textarea
271+
placeholder="Additional information (optional)"
272+
value={additionalInfo}
273+
onChange={(e) => setAdditionalInfo(e.target.value)}
274+
mt={4}
275+
mb={4}
276+
minH="100px"
277+
fontFamily="'Open Sans', sans-serif"
278+
border="1.01px solid"
279+
borderColor="#D9D9D9"
280+
borderRadius="9.11px"
281+
p={4}
282+
_focus={{
283+
borderColor: "#D9D9D9",
284+
boxShadow: "none"
285+
}}
286+
_hover={{
287+
borderColor: "#D9D9D9"
288+
}}
289+
/>
290+
291+
<HStack justify="flex-end" w="100%" gap="16px">
292+
<Button
293+
onClick={handleEditAvailability}
294+
fontFamily="'Open Sans', sans-serif"
295+
fontWeight={600}
296+
fontSize="16px"
297+
borderRadius="8px"
298+
px={6}
299+
py={2}
300+
h="44px"
301+
border="1px solid"
302+
borderColor="#D5D7DA"
303+
color="#495D6C"
304+
bg="#B3CED1"
305+
boxShadow="0px 1px 2px 0px rgba(10, 13, 18, 0.05)"
306+
_hover={{ bg: "#A0BFC2" }}
307+
>
308+
Edit General Availability
309+
</Button>
310+
<Button
311+
bg="#056067"
312+
color="#fff"
313+
fontFamily="'Open Sans', sans-serif"
314+
fontWeight={600}
315+
fontSize="16px"
316+
borderRadius="8px"
317+
px={8}
318+
py={2}
319+
h="44px"
320+
_hover={{ bg: "#044d4d" }}
321+
onClick={handleSend}
322+
>
323+
Send
324+
</Button>
325+
</HStack>
326+
</>
327+
) : (
328+
<>
329+
<Text
330+
color="#1D3448"
331+
fontSize="16px"
332+
fontFamily="'Open Sans', sans-serif"
333+
fontWeight={400}
334+
letterSpacing="-1.5%"
335+
lineHeight="100%"
336+
mb={6}
337+
>
338+
Drag to select all the times you will usually be available to meet with participants.
339+
</Text>
340+
341+
<Box h="700px" w="100%" minW={0} mt="49px">
342+
<TimeScheduler
343+
showAvailability={true}
344+
onTimeSlotsChange={handleAvailabilityChange}
345+
initialTimeSlots={availabilityTimeSlots}
346+
readOnly={false}
347+
/>
348+
</Box>
349+
350+
<HStack justify="flex-end" w="100%" gap="16px" mt={4}>
351+
<Button
352+
variant="outline"
353+
onClick={handleCancelEdit}
354+
fontFamily="'Open Sans', sans-serif"
355+
fontWeight={600}
356+
borderRadius="8px"
357+
px={8}
358+
py={2}
359+
h="44px"
360+
>
361+
Cancel
362+
</Button>
363+
<Button
364+
bg="#056067"
365+
color="#fff"
366+
fontFamily="'Open Sans', sans-serif"
367+
fontWeight={600}
368+
borderRadius="8px"
369+
px={8}
370+
py={2}
371+
h="44px"
372+
_hover={{ bg: "#044d4d" }}
373+
onClick={handleSaveAvailability}
374+
disabled={savingAvailability}
375+
>
376+
{savingAvailability ? 'Saving...' : 'Save Changes'}
377+
</Button>
378+
</HStack>
379+
</>
380+
)}
381+
</Box>
382+
</Box>
383+
</Box>
384+
);
385+
};
386+
387+
export default ScheduleCallModal;

0 commit comments

Comments
 (0)