From b9cff754a33e3e95dd29b3a0aba27a3f51291eab Mon Sep 17 00:00:00 2001 From: aevinj Date: Sat, 15 Mar 2025 13:53:25 +0000 Subject: [PATCH 01/12] send reschedule requests on events rescheduling in progress update shared pointer attempting to use hashmap approach --- src/components/calendar-overview.tsx | 22 +++++-- src/components/reschedule-requests.tsx | 80 +++++++++++++------------- src/types/types.ts | 1 + 3 files changed, 58 insertions(+), 45 deletions(-) diff --git a/src/components/calendar-overview.tsx b/src/components/calendar-overview.tsx index 14b928e..c6c3816 100644 --- a/src/components/calendar-overview.tsx +++ b/src/components/calendar-overview.tsx @@ -155,6 +155,22 @@ export function CalendarOverview() { return (doc.body.textContent || '').trim() } + const handleReschedule = async (selectedEventID: string) => { + if (!selectedEventID) return + try { + await slotifyClient.PostAPIRescheduleRequestSingle({ + msftMeetingID: selectedEventID, + }) + toast({ + title: 'Reschedule sent', + description: 'Sent reschedule request to the organizer', + }) + } catch (error) { + console.error(error) + errorToast(error) + } + } + return (
@@ -492,10 +508,8 @@ export function CalendarOverview() { -
+ */} ))} diff --git a/src/types/types.ts b/src/types/types.ts index 516c3c0..8ea3d87 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -15,3 +15,4 @@ export type SchedulingSlotsSuccessResponse = z.infer< export type MeetingTimeSuggestion = z.infer< typeof schemas.MeetingTimeSuggestion > +export type RescheduleRequest = z.infer From e32a2d6b6e6bd17819a5d6605e5ca81ef229c11f Mon Sep 17 00:00:00 2001 From: aevinj Date: Sun, 16 Mar 2025 22:29:35 +0000 Subject: [PATCH 02/12] implement new reschuling check on dashboard --- src/components/reschedule-requests.tsx | 67 ++++++++++++++++++++------ 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 71081ac..32d1489 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -9,13 +9,26 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { toast } from '@/hooks/use-toast' import slotifyClient from '@/hooks/fetch' -import { RescheduleRequest } from '@/types/types' +import { CalendarEvent, RescheduleRequest, User } from '@/types/types' +import { set } from 'date-fns' export function RescheduleRequests() { const [myRequests, setmyRequests] = useState(null) - // hashmap of user id to user name - const [userMap, setUserMap] = useState<{ [key: number]: string }>({}) + const [fullRequests, setFullRequests] = useState<{ + [key: number]: { + requestedAt: string + requestedBy: string + oldEvent: CalendarEvent + newEvent: { + startTime: string + endTime: string + title: string + duration: string + location: string + } | null + } + }>({}) const handleAction = (id: number, action: 'accept' | 'ignore') => { if (action === 'accept') { @@ -34,20 +47,38 @@ export function RescheduleRequests() { const getRescheduleRequests = async () => { const response = await slotifyClient.GetAPIRescheduleRequestsMe() setmyRequests(response) - console.log('myRequests', myRequests) - // getUserForEachRequest() - // console.log('userMap', userMap) } - const getUserForEachRequest = async () => { + const getFullRequests = async () => { myRequests?.forEach(async request => { - const response = await slotifyClient.GetAPIUsersUserID({ params: { userID: request.requested_by } }) - setUserMap((prev) => ({ ...prev, [request.requested_by]: response.firstName + ' ' + response.lastName })) + const requestedBy = await getUserByID(request.requested_by) + const oldEvent = await slotifyClient.GetAPICalendarEvent({ + queries: { msftID: request.oldMeeting.msftMeetingID }, + }) + const newEvent = request.newMeeting?.startTime === "0001-01-01T00:00:00Z" ? null :{ + startTime: request.newMeeting!.startTime, + endTime: request.newMeeting!.endTime, + title: request.newMeeting!.title, + duration: request.newMeeting!.meetingDuration, + location: request.newMeeting!.location, + } + + setFullRequests({ + ...fullRequests, + [request.request_id]: { + requestedAt: request.requested_at, + requestedBy: requestedBy, + oldEvent: oldEvent, + newEvent: newEvent, + }, + }) }) } - async function getUserByID(id: number) { - const response = await slotifyClient.GetAPIUsersUserID({ params: { userID: id } }) + const getUserByID = async (id: number) => { + const response = await slotifyClient.GetAPIUsersUserID({ + params: { userID: id }, + }) return response.firstName + ' ' + response.lastName } @@ -55,6 +86,9 @@ export function RescheduleRequests() { getRescheduleRequests() }, []) + useEffect(() => { + getFullRequests() + }, [myRequests]) return ( @@ -71,10 +105,15 @@ export function RescheduleRequests() {

- {request.newMeeting?.title ? request.newMeeting.title : 'Untitled'} + {request.newMeeting?.title + ? request.newMeeting.title + : 'Untitled'}

- Requested by {getUserByID(request.requested_by)} + Requested by{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.requestedBy + : ''}

@@ -82,7 +121,7 @@ export function RescheduleRequests() {
Current:
- {(request.oldMeeting)} + {request.oldMeeting}
From 3d65d04ef17f7454ce0e1d73f976eb2ff8f151c2 Mon Sep 17 00:00:00 2001 From: aevinj Date: Sun, 16 Mar 2025 23:28:44 +0000 Subject: [PATCH 03/12] change confidence value to int --- src/components/calendar/weekly-calendar.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/calendar/weekly-calendar.tsx b/src/components/calendar/weekly-calendar.tsx index e3f7015..7dab0a2 100644 --- a/src/components/calendar/weekly-calendar.tsx +++ b/src/components/calendar/weekly-calendar.tsx @@ -406,9 +406,7 @@ export function WeeklyCalendar({ >
Rating:{' '} - {suggestion.confidence - ? `${suggestion.confidence}%` - : ''} + {suggestion.confidence ? `${parseInt(suggestion.confidence.toString(), 10)}%` : ''}
) From 274a7f0483110d0bf248f17097f32d79578970b3 Mon Sep 17 00:00:00 2001 From: aevinj Date: Mon, 17 Mar 2025 00:53:30 +0000 Subject: [PATCH 04/12] fetch live rescheduling requests linting update shared accept button in progress --- src/components/calendar/weekly-calendar.tsx | 4 +- src/components/reschedule-requests.tsx | 187 ++++++++++++++------ 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/src/components/calendar/weekly-calendar.tsx b/src/components/calendar/weekly-calendar.tsx index 7dab0a2..503c049 100644 --- a/src/components/calendar/weekly-calendar.tsx +++ b/src/components/calendar/weekly-calendar.tsx @@ -406,7 +406,9 @@ export function WeeklyCalendar({ >
Rating:{' '} - {suggestion.confidence ? `${parseInt(suggestion.confidence.toString(), 10)}%` : ''} + {suggestion.confidence + ? `${parseInt(suggestion.confidence.toString(), 10)}%` + : ''}
) diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 32d1489..7181ffe 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -1,24 +1,26 @@ 'use client' import { Check, X } from 'lucide-react' -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback } from 'react' import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' -import { toast } from '@/hooks/use-toast' +import { errorToast, toast } from '@/hooks/use-toast' import slotifyClient from '@/hooks/fetch' -import { CalendarEvent, RescheduleRequest, User } from '@/types/types' -import { set } from 'date-fns' +import { CalendarEvent, RescheduleRequest } from '@/types/types' +import { format, parseISO } from 'date-fns' export function RescheduleRequests() { + // TODO - this can be removed since we are using full requests instead const [myRequests, setmyRequests] = useState(null) const [fullRequests, setFullRequests] = useState<{ [key: number]: { requestedAt: string requestedBy: string + status: string oldEvent: CalendarEvent newEvent: { startTime: string @@ -30,50 +32,40 @@ export function RescheduleRequests() { } }>({}) - const handleAction = (id: number, action: 'accept' | 'ignore') => { - if (action === 'accept') { - toast({ - title: 'Request accepted', - description: `Request has been accepted`, - }) - } else { - toast({ - title: 'Request ignored', - description: `Request has been ignored`, - }) - } - } - const getRescheduleRequests = async () => { const response = await slotifyClient.GetAPIRescheduleRequestsMe() setmyRequests(response) } - const getFullRequests = async () => { + const getFullRequests = useCallback(async () => { myRequests?.forEach(async request => { const requestedBy = await getUserByID(request.requested_by) const oldEvent = await slotifyClient.GetAPICalendarEvent({ queries: { msftID: request.oldMeeting.msftMeetingID }, }) - const newEvent = request.newMeeting?.startTime === "0001-01-01T00:00:00Z" ? null :{ - startTime: request.newMeeting!.startTime, - endTime: request.newMeeting!.endTime, - title: request.newMeeting!.title, - duration: request.newMeeting!.meetingDuration, - location: request.newMeeting!.location, - } - - setFullRequests({ - ...fullRequests, + const newEvent = + request.newMeeting?.startTime === '0001-01-01T00:00:00Z' + ? null + : { + startTime: request.newMeeting!.startTime, + endTime: request.newMeeting!.endTime, + title: request.newMeeting!.title, + duration: request.newMeeting!.meetingDuration, + location: request.newMeeting!.location, + } + + setFullRequests(prevFullRequests => ({ + ...prevFullRequests, [request.request_id]: { requestedAt: request.requested_at, requestedBy: requestedBy, + status: request.status, oldEvent: oldEvent, newEvent: newEvent, }, - }) + })) }) - } + }, [myRequests]) const getUserByID = async (id: number) => { const response = await slotifyClient.GetAPIUsersUserID({ @@ -82,13 +74,34 @@ export function RescheduleRequests() { return response.firstName + ' ' + response.lastName } + const handleRequestReject = async (requestID: number) => { + const response = + await slotifyClient.PatchAPIRescheduleRequestRequestIDReject(undefined, { + params: { requestID }, + }) + + // TODO - 2 api calls are made here for some reason + if (response) { + toast({ + title: 'Request rejected', + description: 'The request has been rejected', + }) + } else { + errorToast('Failed to reject request') + } + } + + const handleRequestAccept = async (requestID: number) => {} + + // TODO - this can be removed since we are using full requests instead useEffect(() => { getRescheduleRequests() }, []) useEffect(() => { getFullRequests() - }, [myRequests]) + }, [getFullRequests]) + return ( @@ -107,8 +120,27 @@ export function RescheduleRequests() {

{request.newMeeting?.title ? request.newMeeting.title - : 'Untitled'} + : '(No name)'}

+

+ Current event:{' '} + {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[ + request.request_id + ]!.oldEvent.startTime?.toString() ?? '', + ), + 'EEEE do HH:mm', + ) + : ''} +

+

+ Status:{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.status + : ''} +

Requested by{' '} {fullRequests[request.request_id] @@ -117,51 +149,88 @@ export function RescheduleRequests() {

- {/*
+
-
Current:
+
+ Requested on: +
- {request.oldMeeting} + {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[request.request_id]!.requestedAt, + ), + 'EEEE do HH:mm', + ) + : ''}
-
+ {/*
Requested:
{request.requestedDateTime}
-
+
*/}
-
+
Attendees:
- {request.participants.join(', ')} + {fullRequests[request.request_id] + ? fullRequests[ + request.request_id + ]!.oldEvent.attendees.map( + attendee => attendee.email, + ).join(', ') + : ''}
- - -
*/} + {request.status === 'pending' && ( + <> + {request.newMeeting!.startTime === + '0001-01-01T00:00:00Z' ? ( + + ) : ( + + )} + + )} + + {request.status === 'pending' && ( + + )} +
))} From d4dd3c7a39a128492a0cdc3fcca23a45f116e54f Mon Sep 17 00:00:00 2001 From: aevinj Date: Tue, 18 Mar 2025 18:21:43 +0000 Subject: [PATCH 05/12] update dashboard layout --- src/app/dashboard/page.tsx | 8 +-- src/components/events-today.tsx | 69 ++++++++++++++++++-------- src/components/reschedule-requests.tsx | 4 +- src/components/user.tsx | 46 ++++++++++------- 4 files changed, 83 insertions(+), 44 deletions(-) diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 1522188..74055f1 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -122,10 +122,10 @@ export default function Dashboard() { } return ( -
+
-
-
+
+
@@ -222,7 +222,7 @@ export default function Dashboard() { )}
-
+
diff --git a/src/components/events-today.tsx b/src/components/events-today.tsx index 1170519..8d4321f 100644 --- a/src/components/events-today.tsx +++ b/src/components/events-today.tsx @@ -1,30 +1,28 @@ +'use client' + import { CalendarDays } from 'lucide-react' import { CalendarEvent } from '@/types/types' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { ScrollArea } from '@/components/ui/scroll-area' interface EventsForTodayProps { events: CalendarEvent[] } export function EventsForToday({ events }: EventsForTodayProps) { - const today = new Date() - const formattedDate = today.toLocaleDateString('en-US', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }) - return ( -
-
-
-
- Events for Today -
-

- {formattedDate} -

-
+ + + + Today's Events + {events.length} + + + + + +
{events.map(event => (
@@ -40,8 +38,37 @@ export function EventsForToday({ events }: EventsForTodayProps) {
))}
-
-
-
+ + + + + //
+ //
+ //
+ //
+ // Events for Today + //
+ //

+ // {formattedDate} + //

+ //
+ // {events.map(event => ( + //
+ //
+ // + //

{event?.subject}

+ //
+ //

+ // {event?.startTime} -{' '} + // {event?.locations && + // event.locations.length > 0 && + // event.locations[0]?.name} + //

+ //
+ // ))} + //
+ //
+ //
+ //
) } diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 7181ffe..807a58f 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -110,8 +110,8 @@ export function RescheduleRequests() { {myRequests?.length} - - + +
{myRequests?.map(request => (
diff --git a/src/components/user.tsx b/src/components/user.tsx index b5fd811..0736207 100644 --- a/src/components/user.tsx +++ b/src/components/user.tsx @@ -2,34 +2,46 @@ import { useEffect, useState } from 'react' import { Member } from '@/components/slotify-group-members' -import { User as LucideUserIcon } from 'lucide-react' +import { User as LucideUserIcon, CalendarDays } from 'lucide-react' import slotifyClient from '@/hooks/fetch' import { errorToast } from '@/hooks/use-toast' export default function User() { const [user, setUser] = useState(null) + const today = new Date() + const formattedDate = today.toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }) - useEffect(() => { - const fetchUser = async () => { - try { - console.log('Making fetch call to /users') - const data = await slotifyClient.GetAPIUsersMe() - setUser(data) - } catch (error) { - console.error(error) - errorToast(error) - } + const fetchUser = async () => { + try { + console.log('Making fetch call to /users') + const data = await slotifyClient.GetAPIUsersMe() + setUser(data) + } catch (error) { + console.error(error) + errorToast(error) } + } + + useEffect(() => { fetchUser() }, []) - if (!user) return 'No user' - return ( -
- -
- Welcome {user.firstName} +
+
+
+ + Welcome {user ? user.firstName: 'User'} +
+
+
+ {formattedDate} +
) From 82a44793ca7fae47619c8930e4dccb8d5c13bf98 Mon Sep 17 00:00:00 2001 From: aevinj Date: Tue, 18 Mar 2025 18:47:06 +0000 Subject: [PATCH 06/12] split reschedule requests into 2 columns create event dialog --- src/components/reschedule-requests.tsx | 394 +++++++++++++++++-------- 1 file changed, 274 insertions(+), 120 deletions(-) diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 807a58f..5c0df9c 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -11,10 +11,12 @@ import { errorToast, toast } from '@/hooks/use-toast' import slotifyClient from '@/hooks/fetch' import { CalendarEvent, RescheduleRequest } from '@/types/types' import { format, parseISO } from 'date-fns' +import { CreateEvent } from './calendar/create-event' export function RescheduleRequests() { // TODO - this can be removed since we are using full requests instead const [myRequests, setmyRequests] = useState(null) + const [createEventOpen, setCreateEventOpen] = useState(false) const [fullRequests, setFullRequests] = useState<{ [key: number]: { @@ -103,69 +105,211 @@ export function RescheduleRequests() { }, [getFullRequests]) return ( - - - - Reschedule Requests - {myRequests?.length} - - - - -
- {myRequests?.map(request => ( -
-
-
-

- {request.newMeeting?.title - ? request.newMeeting.title - : '(No name)'} -

-

- Current event:{' '} - {fullRequests[request.request_id] - ? format( - parseISO( - fullRequests[ - request.request_id - ]!.oldEvent.startTime?.toString() ?? '', - ), - 'EEEE do HH:mm', - ) - : ''} -

-

- Status:{' '} - {fullRequests[request.request_id] - ? fullRequests[request.request_id]!.status - : ''} -

-

- Requested by{' '} - {fullRequests[request.request_id] - ? fullRequests[request.request_id]!.requestedBy - : ''} -

-
- -
-
-
- Requested on: + <> + + + + Reschedule Requests + {myRequests?.length} + + + +
+ +
+ {myRequests + ?.filter(request => request.status === 'pending') + .map(request => ( +
+
+
+

+ {request.newMeeting?.title + ? request.newMeeting.title + : '(No name)'} +

+

+ Current event:{' '} + {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[ + request.request_id + ]!.oldEvent.startTime?.toString() ?? '', + ), + 'EEEE do HH:mm', + ) + : ''} +

+

+ Status:{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.status + : ''} +

+

+ Requested by{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.requestedBy + : ''} +

+
+ +
+
+
+ Requested on: +
+
+ {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[request.request_id]! + .requestedAt, + ), + 'EEEE do HH:mm', + ) + : ''} +
+
+ {/*
+
+ Requested:
- {fullRequests[request.request_id] - ? format( - parseISO( - fullRequests[request.request_id]!.requestedAt, - ), - 'EEEE do HH:mm', - ) - : ''} + {request.requestedDateTime} +
+
*/} +
+
+ Attendees: +
+
+ {fullRequests[request.request_id] + ? fullRequests[ + request.request_id + ]!.oldEvent.attendees.map( + attendee => attendee.email, + ).join(', ') + : ''} +
+
+
+ +
+ {request.status === 'pending' && ( + <> + {request.newMeeting!.startTime === + '0001-01-01T00:00:00Z' ? ( + + ) : ( + + )} + + )} + + {request.status === 'pending' && ( + + )} +
- {/*
+ ))} +
+ + + +
+ {myRequests + ?.filter(request => request.status !== 'pending') + .map(request => ( +
+ setCreateEventOpen(true) + } + > +
+
+

+ {request.newMeeting?.title + ? request.newMeeting.title + : '(No name)'} +

+

+ Current event:{' '} + {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[ + request.request_id + ]!.oldEvent.startTime?.toString() ?? '', + ), + 'EEEE do HH:mm', + ) + : ''} +

+

+ Status:{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.status + : ''} +

+

+ Requested by{' '} + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.requestedBy + : ''} +

+
+ +
+
+
+ Requested on: +
+
+ {fullRequests[request.request_id] + ? format( + parseISO( + fullRequests[request.request_id]! + .requestedAt, + ), + 'EEEE do HH:mm', + ) + : ''} +
+
+ {/*
Requested:
@@ -173,70 +317,80 @@ export function RescheduleRequests() { {request.requestedDateTime}
*/} -
-
- Attendees: -
-
- {fullRequests[request.request_id] - ? fullRequests[ - request.request_id - ]!.oldEvent.attendees.map( - attendee => attendee.email, - ).join(', ') - : ''} +
+
+ Attendees: +
+
+ {fullRequests[request.request_id] + ? fullRequests[ + request.request_id + ]!.oldEvent.attendees.map( + attendee => attendee.email, + ).join(', ') + : ''} +
+
+
+ +
+ {request.status === 'pending' && ( + <> + {request.newMeeting!.startTime === + '0001-01-01T00:00:00Z' ? ( + + ) : ( + + )} + + )} + + {request.status === 'pending' && ( + + )} +
-
- -
- {request.status === 'pending' && ( - <> - {request.newMeeting!.startTime === - '0001-01-01T00:00:00Z' ? ( - - ) : ( - - )} - - )} - - {request.status === 'pending' && ( - - )} -
-
+ ))}
- ))} +
- -
-
+ + + + setCreateEventOpen(false)} + /> + ) } From c8e50f62925a386b79f4a49ec4d9a9803bc168a2 Mon Sep 17 00:00:00 2001 From: aevinj Date: Wed, 19 Mar 2025 01:03:40 +0000 Subject: [PATCH 07/12] working accepted/declined requests remove console.logs --- src/components/calendar/create-event.tsx | 113 +++++++++++------- .../calendar/event-range-picker.tsx | 60 ++++++---- src/components/events-today.tsx | 2 +- src/components/reschedule-requests.tsx | 78 +++++++++++- src/components/user.tsx | 4 +- 5 files changed, 183 insertions(+), 74 deletions(-) diff --git a/src/components/calendar/create-event.tsx b/src/components/calendar/create-event.tsx index 5c791eb..9519ad9 100644 --- a/src/components/calendar/create-event.tsx +++ b/src/components/calendar/create-event.tsx @@ -27,7 +27,11 @@ import { UserSearch } from '@/components/user-search-bar' interface CreateEventProps { open: boolean onOpenChangeAction: (open: boolean) => void - closeCreateEventDialogOpenAction: () => void + closeCreateEventDialogOpen: () => void + initialTitle?: string + initialDuration?: string + initialParticipants?: User[] + initialSelectedRange?: { start: Date; end: Date } | null } // Mapping from dropdown option to minutes @@ -40,22 +44,25 @@ const durationMapping: { [key: string]: number } = { export function CreateEvent({ open, onOpenChangeAction, - closeCreateEventDialogOpenAction, + closeCreateEventDialogOpen, + initialTitle = '', + initialDuration = '1hr', + initialParticipants = [], + initialSelectedRange = null, }: CreateEventProps) { + const [myEvents, setMyEvents] = useState([]) - const [title, setTitle] = useState('') + const [title, setTitle] = useState(initialTitle) const [location, setLocation] = useState('') - const [duration, setDuration] = useState('1hr') + const [duration, setDuration] = useState(initialDuration) - // const [userSearchQuery, setUserSearchQuery] = useState('') - // const [searchResults, setSearchResults] = useState([]) - const [selectedParticipants, setSelectedParticipants] = useState([]) + const [userSearchQuery, setUserSearchQuery] = useState('') + const [searchResults, setSearchResults] = useState([]) + const [selectedParticipants, setSelectedParticipants] = + useState(initialParticipants) const [selectedDate, setSelectedDate] = useState(null) - const [selectedRange, setSelectedRange] = useState<{ - start: Date - end: Date - } | null>(null) + const [selectedRange, setSelectedRange] = useState(initialSelectedRange) const [availabilityData, setAvailabilityData] = useState(null) @@ -68,7 +75,6 @@ export function CreateEvent({ const fetchCurrentUser = async () => { try { const user = await slotifyClient.GetAPIUsersMe() - console.log('Current user:', user) setCurrentUser(user) } catch (error) { console.error('Error fetching current user:', error) @@ -77,6 +83,21 @@ export function CreateEvent({ fetchCurrentUser() }, []) + useEffect(() => { + if (open) { + setTitle(initialTitle) + setDuration(initialDuration) + setSelectedParticipants(initialParticipants) + setSelectedRange(initialSelectedRange) + } + }, [ + open, + initialTitle, + initialDuration, + initialParticipants, + initialSelectedRange, + ]) + // Ref to store the last fetched range (optional) const lastFetchedRangeRef = useRef<{ start: number; end: number } | null>( null, @@ -94,15 +115,44 @@ export function CreateEvent({ selectedRange.start.getTime() !== range.start.getTime() || selectedRange.end.getTime() !== range.end.getTime()) ) { - console.log('Range selected (updating):', range) setSelectedRange(range) - } else { - console.log('Range selected is identical or null; no update.') } }, [selectedRange], ) + useEffect(() => { + const searchUsers = async () => { + if (!userSearchQuery) { + setSearchResults([]) + return + } + try { + let response + if (userSearchQuery.includes('@')) { + // Search by email + response = await slotifyClient.GetAPIUsers({ + queries: { email: userSearchQuery }, + }) + } else { + // Search by name + response = await slotifyClient.GetAPIUsers({ + queries: { name: userSearchQuery }, + }) + } + setSearchResults(response) + } catch (error) { + console.error('Error searching users:', error) + toast({ + title: 'Error', + description: 'Failed to search users.', + variant: 'destructive', + }) + } + } + searchUsers() + }, [userSearchQuery]) + // Add a user from search results to selected participants const handleAddParticipant = (user: User) => { if ( @@ -135,11 +185,8 @@ export function CreateEvent({ end: memoizedRange?.end?.toISOString() || new Date().toISOString(), }, }) - console.log("User's own events:", myEventsResponse) setMyEvents(myEventsResponse) - console.log('Check Availability button clicked.') - // Validate required fields if (selectedParticipants.length === 0 || !memoizedRange) { toast({ @@ -156,11 +203,7 @@ export function CreateEvent({ memoizedRange && lastFetchedRangeRef.current.start === memoizedRange.start.getTime() && lastFetchedRangeRef.current.end === memoizedRange.end.getTime() - ) { - console.log( - 'Range unchanged from last fetch; re-fetching due to manual trigger.', - ) - } + ) lastFetchedRangeRef.current = { start: memoizedRange.start.getTime(), @@ -183,20 +226,8 @@ export function CreateEvent({ }) } - console.log('Attendees:', attendees) - const durationInMinutes = durationMapping[duration] || 60 const meetingDuration = `PT${durationInMinutes}M` - console.log('Meeting duration:', meetingDuration) - - console.log('Sending scheduling API call with details:', { - meetingName: title || 'New Meeting', - meetingDuration, - timeSlot: { - start: memoizedRange.start.toISOString(), - end: memoizedRange.end.toISOString(), - }, - }) // Fetch scheduling suggestions const response = await slotifyClient.PostAPISchedulingSlots({ @@ -218,7 +249,6 @@ export function CreateEvent({ }, }) - console.log('Scheduling API response received:', response) setAvailabilityData(response) // Now fetch each selected participant's calendar events for conflicts @@ -233,7 +263,7 @@ export function CreateEvent({ ) const conflictResults = await Promise.all(conflictPromises) const allConflictEvents = conflictResults.flat() - console.log('Conflict events fetched:', allConflictEvents) + setConflictEvents(allConflictEvents) } catch (error) { console.error('Error fetching availability:', error) @@ -244,12 +274,10 @@ export function CreateEvent({ }) } finally { setIsLoading(false) - console.log('Finished API call for availability.') } } const handleCreateManually = () => { - console.log('Manual event creation triggered') onOpenChangeAction(false) } @@ -275,7 +303,6 @@ export function CreateEvent({ placeholder='My New Meeting' value={title} onChange={e => { - console.log('Title changed:', e.target.value) setTitle(e.target.value) }} /> @@ -288,7 +315,6 @@ export function CreateEvent({ placeholder='None' value={location} onChange={e => { - console.log('Location changed:', e.target.value) setLocation(e.target.value) }} /> @@ -300,7 +326,6 @@ export function CreateEvent({ id='duration' value={duration} onChange={e => { - console.log('Duration changed:', e.target.value) setDuration(e.target.value) }} className='block w-full rounded-md border border-gray-300 p-2' @@ -321,8 +346,8 @@ export function CreateEvent({ { - console.log('Date selected:', date) setSelectedDate(date) }} onRangeSelect={handleRangeSelect} @@ -368,7 +393,7 @@ export function CreateEvent({ participants={selectedParticipants} location={''} //TODO fix this eventTitle={title} - closeCreateEventDialogOpen={closeCreateEventDialogOpenAction} + closeCreateEventDialogOpen={closeCreateEventDialogOpen} />
diff --git a/src/components/calendar/event-range-picker.tsx b/src/components/calendar/event-range-picker.tsx index 0d16eb1..65f8481 100644 --- a/src/components/calendar/event-range-picker.tsx +++ b/src/components/calendar/event-range-picker.tsx @@ -9,26 +9,39 @@ interface EventRangePickerProps { selectedDate: Date | null onDateSelect: (date: Date) => void onRangeSelect: (range: { start: Date; end: Date } | null) => void + selectedRange?: { start: Date; end: Date } | null // new optional prop } export function EventRangePicker({ selectedDate, onDateSelect, onRangeSelect, + selectedRange, // destructure the new prop }: EventRangePickerProps) { const [currentMonth, setCurrentMonth] = useState(new Date()) const [dragStart, setDragStart] = useState(null) const [dragEnd, setDragEnd] = useState(null) const [isSelecting, setIsSelecting] = useState(false) - // Update range when drag is complete + // Sync external selectedRange prop with internal state. + useEffect(() => { + if (selectedRange) { + setDragStart(selectedRange.start) + setDragEnd(selectedRange.end) + } else { + setDragStart(null) + setDragEnd(null) + } + }, [selectedRange]) + + // When drag is complete, update the range. useEffect(() => { if (dragStart && dragEnd && !isSelecting) { - // Ensure start date is before end date + // Ensure start is before end. const start = dragStart < dragEnd ? dragStart : dragEnd const end = dragStart < dragEnd ? dragEnd : dragStart - // Set the end of day for the end date + // Set the end of day for the end date. const endDate = new Date(end) endDate.setHours(23, 59, 59, 999) @@ -36,7 +49,20 @@ export function EventRangePicker({ } }, [dragStart, dragEnd, isSelecting, onRangeSelect]) - // Get days in month + // Helper to normalize a date (zero out time). + const normalize = (d: Date) => + new Date(d.getFullYear(), d.getMonth(), d.getDate()) + + // Check if a date is in the selected range (comparing only year, month, day). + const isInRange = (date: Date) => { + if (!dragStart || !dragEnd) return false + const normalizedDate = normalize(date) + const normalizedStart = normalize(dragStart) + const normalizedEnd = normalize(dragEnd) + return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd + } + + // Get days in month. const getDaysInMonth = (date: Date) => { const year = date.getFullYear() const month = date.getMonth() @@ -45,7 +71,7 @@ export function EventRangePicker({ const days = [] const firstDayOfMonth = new Date(year, month, 1).getDay() - // Add days from previous month to fill the first week + // Add days from previous month to fill the first week. const prevMonthDays = new Date(year, month, 0).getDate() for (let i = firstDayOfMonth - 1; i >= 0; i--) { const day = prevMonthDays - i @@ -53,13 +79,13 @@ export function EventRangePicker({ days.push({ date, isCurrentMonth: false }) } - // Add days of current month + // Add days of current month. for (let i = 1; i <= daysInMonth; i++) { const date = new Date(year, month, i) days.push({ date, isCurrentMonth: true }) } - // Add days from next month to complete the last week + // Add days from next month to complete the last week. const lastDayOfMonth = new Date(year, month, daysInMonth).getDay() for (let i = 1; i < 7 - lastDayOfMonth; i++) { const date = new Date(year, month + 1, i) @@ -102,23 +128,11 @@ export function EventRangePicker({ setIsSelecting(false) } - // Check if a date is in the selected range - const isInRange = (date: Date) => { - if (!dragStart || !dragEnd) return false - - const start = dragStart < dragEnd ? dragStart : dragEnd - const end = dragStart < dragEnd ? dragEnd : dragStart - - return date >= start && date <= end - } - - // Format date for display - const formatDate = (date: Date) => { - return date.getDate() - } + // Format date for display. + const formatDate = (date: Date) => date.getDate() useEffect(() => { - // Add global mouse up handler to stop selection even if mouse is released outside calendar + // Global mouseup handler to stop selection if mouse is released outside. document.addEventListener('mouseup', handleMouseUp) return () => { document.removeEventListener('mouseup', handleMouseUp) @@ -150,13 +164,13 @@ export function EventRangePicker({
+
Su
Mo
Tu
We
Th
Fr
Sa
-
Su
- Today's Events + Today's Events {events.length} diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 5c0df9c..18e71d5 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -2,22 +2,35 @@ import { Check, X } from 'lucide-react' import { useEffect, useState, useCallback } from 'react' - import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { errorToast, toast } from '@/hooks/use-toast' import slotifyClient from '@/hooks/fetch' -import { CalendarEvent, RescheduleRequest } from '@/types/types' +import { CalendarEvent, RescheduleRequest, Attendee, User } from '@/types/types' import { format, parseISO } from 'date-fns' import { CreateEvent } from './calendar/create-event' +interface CreateEventData { + title: string + duration: string + participants: User[] + selectedRange: { start: Date; end: Date } | null +} + export function RescheduleRequests() { // TODO - this can be removed since we are using full requests instead const [myRequests, setmyRequests] = useState(null) const [createEventOpen, setCreateEventOpen] = useState(false) + const [createEventData, setCreateEventData] = useState({ + title: '', + duration: '1hr', + participants: [], + selectedRange: null, + }) + const [fullRequests, setFullRequests] = useState<{ [key: number]: { requestedAt: string @@ -34,6 +47,20 @@ export function RescheduleRequests() { } }>({}) + const convertAttendeesToUsers = async (attendees: Attendee[]) => { + try { + const usersPromises = attendees.map(attendee => + slotifyClient.GetAPIUsers({ queries: { email: attendee.email } }), + ) + const usersArrays = await Promise.all(usersPromises) + const users = usersArrays.flat() + return users + } catch (error) { + console.error('Error converting attendees:', error) + return [] + } + } + const getRescheduleRequests = async () => { const response = await slotifyClient.GetAPIRescheduleRequestsMe() setmyRequests(response) @@ -254,9 +281,48 @@ export function RescheduleRequests() {
+ onClick={async () => { + const currentFullRequest = + fullRequests[request.request_id] + if (!currentFullRequest) { + return + } + + // If newMeeting exists and is valid, update createEventData. + if ( + currentFullRequest.newEvent && + currentFullRequest.newEvent.startTime !== + '0001-01-01T00:00:00Z' + ) { + const newParticipants = await convertAttendeesToUsers( + currentFullRequest.oldEvent.attendees, + ) + setCreateEventData({ + title: currentFullRequest.newEvent.title, + duration: currentFullRequest.newEvent.duration, + participants: newParticipants, + selectedRange: { + start: new Date( + currentFullRequest.newEvent.startTime, + ), + end: new Date( + currentFullRequest.newEvent.endTime, + ), + }, + }) + } else { + const newParticipants = await convertAttendeesToUsers( + currentFullRequest.oldEvent.attendees, + ) + setCreateEventData({ + title: '', + duration: '1hr', + participants: newParticipants, + selectedRange: null, + }) + } setCreateEventOpen(true) - } + }} >
@@ -390,6 +456,10 @@ export function RescheduleRequests() { open={createEventOpen} onOpenChangeAction={setCreateEventOpen} closeCreateEventDialogOpen={() => setCreateEventOpen(false)} + initialTitle={createEventData.title} + initialDuration={createEventData.duration} + initialParticipants={createEventData.participants} + initialSelectedRange={createEventData.selectedRange} /> ) diff --git a/src/components/user.tsx b/src/components/user.tsx index 0736207..a7ec07c 100644 --- a/src/components/user.tsx +++ b/src/components/user.tsx @@ -35,8 +35,8 @@ export default function User() {
- - Welcome {user ? user.firstName: 'User'} + + Welcome {user ? user.firstName : 'User'}
From c0f5b2e12d9a7ce88e2310ce6fbac1011c88077a Mon Sep 17 00:00:00 2001 From: aevinj Date: Wed, 19 Mar 2025 01:58:29 +0000 Subject: [PATCH 08/12] pending accept -> opens disabled create event update shared pointer --- src/components/calendar/create-event.tsx | 12 +- .../calendar/event-range-picker.tsx | 13 +- src/components/reschedule-requests.tsx | 148 +++++++----------- 3 files changed, 73 insertions(+), 100 deletions(-) diff --git a/src/components/calendar/create-event.tsx b/src/components/calendar/create-event.tsx index 9519ad9..21a7f83 100644 --- a/src/components/calendar/create-event.tsx +++ b/src/components/calendar/create-event.tsx @@ -32,6 +32,7 @@ interface CreateEventProps { initialDuration?: string initialParticipants?: User[] initialSelectedRange?: { start: Date; end: Date } | null + inputsDisabled?: boolean } // Mapping from dropdown option to minutes @@ -49,6 +50,7 @@ export function CreateEvent({ initialDuration = '1hr', initialParticipants = [], initialSelectedRange = null, + inputsDisabled = false, }: CreateEventProps) { const [myEvents, setMyEvents] = useState([]) @@ -305,6 +307,7 @@ export function CreateEvent({ onChange={e => { setTitle(e.target.value) }} + disabled={inputsDisabled} />
@@ -317,6 +320,7 @@ export function CreateEvent({ onChange={e => { setLocation(e.target.value) }} + disabled={inputsDisabled} />
@@ -329,6 +333,7 @@ export function CreateEvent({ setDuration(e.target.value) }} className='block w-full rounded-md border border-gray-300 p-2' + disabled={inputsDisabled} > @@ -348,9 +353,12 @@ export function CreateEvent({ selectedDate={selectedDate} selectedRange={selectedRange} onDateSelect={date => { - setSelectedDate(date) + if (!inputsDisabled) setSelectedDate(date) }} - onRangeSelect={handleRangeSelect} + onRangeSelect={range => { + if (!inputsDisabled) handleRangeSelect(range) + }} + disabled={inputsDisabled} />
diff --git a/src/components/calendar/event-range-picker.tsx b/src/components/calendar/event-range-picker.tsx index 65f8481..914da6b 100644 --- a/src/components/calendar/event-range-picker.tsx +++ b/src/components/calendar/event-range-picker.tsx @@ -9,14 +9,16 @@ interface EventRangePickerProps { selectedDate: Date | null onDateSelect: (date: Date) => void onRangeSelect: (range: { start: Date; end: Date } | null) => void - selectedRange?: { start: Date; end: Date } | null // new optional prop + selectedRange?: { start: Date; end: Date } | null + disabled?: boolean // new optional prop to disable input changes } export function EventRangePicker({ selectedDate, onDateSelect, onRangeSelect, - selectedRange, // destructure the new prop + selectedRange, + disabled = false, }: EventRangePickerProps) { const [currentMonth, setCurrentMonth] = useState(new Date()) const [dragStart, setDragStart] = useState(null) @@ -112,6 +114,7 @@ export function EventRangePicker({ } const handleMouseDown = (date: Date) => { + if (disabled) return setIsSelecting(true) setDragStart(date) setDragEnd(date) @@ -119,12 +122,14 @@ export function EventRangePicker({ } const handleMouseEnter = (date: Date) => { + if (disabled) return if (isSelecting) { setDragEnd(date) } } const handleMouseUp = () => { + if (disabled) return setIsSelecting(false) } @@ -147,6 +152,7 @@ export function EventRangePicker({ size='icon' onClick={prevMonth} className='h-7 w-7' + disabled={disabled} > @@ -158,6 +164,7 @@ export function EventRangePicker({ size='icon' onClick={nextMonth} className='h-7 w-7' + disabled={disabled} > @@ -175,7 +182,7 @@ export function EventRangePicker({
isSelecting && setIsSelecting(false)} + onMouseLeave={() => !disabled && isSelecting && setIsSelecting(false)} > {days.map(({ date, isCurrentMonth }, index) => (
{} + const handleRequestAccept = async (oldEvent: CalendarEvent) => { + const start = parseISO(oldEvent.startTime!) + const end = parseISO(oldEvent.endTime!) + const diffInMinutes = Math.round((end.getTime() - start.getTime()) / 60000) + const candidates = [30, 60, 120]; + const closest = candidates.reduce((prev, curr) => + Math.abs(curr - diffInMinutes) < Math.abs(prev - diffInMinutes) ? curr : prev + ); + const durationString = + closest === 30 ? '30 minutes' : closest === 60 ? '1hr' : '2hrs'; + + const newParticipants = await convertAttendeesToUsers(oldEvent.attendees) + + setCreateEventData({ + title: oldEvent.subject ?? '', + duration: durationString, + participants: newParticipants, + selectedRange: { + start: new Date(oldEvent.startTime!), + end: new Date(oldEvent.endTime!), + }, + disabled: true, + }) + setCreateEventOpen(true) + } // TODO - this can be removed since we are using full requests instead useEffect(() => { @@ -224,48 +250,29 @@ export function RescheduleRequests() {
- {request.status === 'pending' && ( - <> - {request.newMeeting!.startTime === - '0001-01-01T00:00:00Z' ? ( - - ) : ( - - )} - - )} + - {request.status === 'pending' && ( - - )} +
@@ -309,6 +316,7 @@ export function RescheduleRequests() { currentFullRequest.newEvent.endTime, ), }, + disabled: false, }) } else { const newParticipants = await convertAttendeesToUsers( @@ -319,6 +327,7 @@ export function RescheduleRequests() { duration: '1hr', participants: newParticipants, selectedRange: null, + disabled: false, }) } setCreateEventOpen(true) @@ -375,14 +384,7 @@ export function RescheduleRequests() { : ''}
- {/*
-
- Requested: -
-
- {request.requestedDateTime} -
-
*/} +
Attendees: @@ -398,51 +400,6 @@ export function RescheduleRequests() {
- -
- {request.status === 'pending' && ( - <> - {request.newMeeting!.startTime === - '0001-01-01T00:00:00Z' ? ( - - ) : ( - - )} - - )} - - {request.status === 'pending' && ( - - )} -
))} @@ -460,6 +417,7 @@ export function RescheduleRequests() { initialDuration={createEventData.duration} initialParticipants={createEventData.participants} initialSelectedRange={createEventData.selectedRange} + inputsDisabled={createEventData.disabled} /> ) From a09ef666d767f62d272b948fc9f6c6a18d1b2d97 Mon Sep 17 00:00:00 2001 From: aevinj Date: Wed, 19 Mar 2025 16:55:04 +0000 Subject: [PATCH 09/12] rebase alterations --- src/components/calendar-overview.tsx | 2 +- src/components/calendar/create-event.tsx | 76 +++++++++---------- .../calendar/event-range-picker.tsx | 2 +- src/components/reschedule-requests.tsx | 14 ++-- 4 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/components/calendar-overview.tsx b/src/components/calendar-overview.tsx index c6c3816..25aace1 100644 --- a/src/components/calendar-overview.tsx +++ b/src/components/calendar-overview.tsx @@ -407,7 +407,7 @@ export function CalendarOverview() { ([]) const [title, setTitle] = useState(initialTitle) const [location, setLocation] = useState('') const [duration, setDuration] = useState(initialDuration) - const [userSearchQuery, setUserSearchQuery] = useState('') - const [searchResults, setSearchResults] = useState([]) + // const [userSearchQuery, setUserSearchQuery] = useState('') + // const [searchResults, setSearchResults] = useState([]) const [selectedParticipants, setSelectedParticipants] = useState(initialParticipants) @@ -123,37 +122,37 @@ export function CreateEvent({ [selectedRange], ) - useEffect(() => { - const searchUsers = async () => { - if (!userSearchQuery) { - setSearchResults([]) - return - } - try { - let response - if (userSearchQuery.includes('@')) { - // Search by email - response = await slotifyClient.GetAPIUsers({ - queries: { email: userSearchQuery }, - }) - } else { - // Search by name - response = await slotifyClient.GetAPIUsers({ - queries: { name: userSearchQuery }, - }) - } - setSearchResults(response) - } catch (error) { - console.error('Error searching users:', error) - toast({ - title: 'Error', - description: 'Failed to search users.', - variant: 'destructive', - }) - } - } - searchUsers() - }, [userSearchQuery]) + // useEffect(() => { + // const searchUsers = async () => { + // if (!userSearchQuery) { + // setSearchResults([]) + // return + // } + // try { + // let response + // if (userSearchQuery.includes('@')) { + // // Search by email + // response = await slotifyClient.GetAPIUsers({ + // queries: { email: userSearchQuery }, + // }) + // } else { + // // Search by name + // response = await slotifyClient.GetAPIUsers({ + // queries: { name: userSearchQuery }, + // }) + // } + // setSearchResults(response) + // } catch (error) { + // console.error('Error searching users:', error) + // toast({ + // title: 'Error', + // description: 'Failed to search users.', + // variant: 'destructive', + // }) + // } + // } + // searchUsers() + // }, [userSearchQuery]) // Add a user from search results to selected participants const handleAddParticipant = (user: User) => { @@ -206,11 +205,10 @@ export function CreateEvent({ lastFetchedRangeRef.current.start === memoizedRange.start.getTime() && lastFetchedRangeRef.current.end === memoizedRange.end.getTime() ) - - lastFetchedRangeRef.current = { - start: memoizedRange.start.getTime(), - end: memoizedRange.end.getTime(), - } + lastFetchedRangeRef.current = { + start: memoizedRange.start.getTime(), + end: memoizedRange.end.getTime(), + } setIsLoading(true) try { diff --git a/src/components/calendar/event-range-picker.tsx b/src/components/calendar/event-range-picker.tsx index 914da6b..2f966aa 100644 --- a/src/components/calendar/event-range-picker.tsx +++ b/src/components/calendar/event-range-picker.tsx @@ -10,7 +10,7 @@ interface EventRangePickerProps { onDateSelect: (date: Date) => void onRangeSelect: (range: { start: Date; end: Date } | null) => void selectedRange?: { start: Date; end: Date } | null - disabled?: boolean // new optional prop to disable input changes + disabled?: boolean // new optional prop to disable input changes } export function EventRangePicker({ diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 9ddce41..ac3c2df 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -126,12 +126,14 @@ export function RescheduleRequests() { const start = parseISO(oldEvent.startTime!) const end = parseISO(oldEvent.endTime!) const diffInMinutes = Math.round((end.getTime() - start.getTime()) / 60000) - const candidates = [30, 60, 120]; + const candidates = [30, 60, 120] const closest = candidates.reduce((prev, curr) => - Math.abs(curr - diffInMinutes) < Math.abs(prev - diffInMinutes) ? curr : prev - ); + Math.abs(curr - diffInMinutes) < Math.abs(prev - diffInMinutes) + ? curr + : prev, + ) const durationString = - closest === 30 ? '30 minutes' : closest === 60 ? '1hr' : '2hrs'; + closest === 30 ? '30 minutes' : closest === 60 ? '1hr' : '2hrs' const newParticipants = await convertAttendeesToUsers(oldEvent.attendees) @@ -255,7 +257,9 @@ export function RescheduleRequests() { size='sm' className='w-full text-green-500 hover:text-green-600 hover:bg-green-50' onClick={() => - handleRequestAccept(fullRequests[request.request_id]!.oldEvent) + handleRequestAccept( + fullRequests[request.request_id]!.oldEvent, + ) } > From a1dce9a59a96f1d9173838715f9f75c41b0b0c94 Mon Sep 17 00:00:00 2001 From: aevinj Date: Wed, 19 Mar 2025 21:45:07 +0000 Subject: [PATCH 10/12] fix GetAPIRescheduleRequestsMe request --- public/swagger/swagger.json | 29 ++---- src/components/reschedule-requests.tsx | 10 ++- src/types/client.ts | 118 +++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 33 deletions(-) diff --git a/public/swagger/swagger.json b/public/swagger/swagger.json index 2eb47e7..de3a712 100644 --- a/public/swagger/swagger.json +++ b/public/swagger/swagger.json @@ -971,7 +971,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InvitesGroupsAndPagination" + "$ref": "#/components/schemas/UsersAndPagination" } } } @@ -3257,27 +3257,12 @@ "name" ] }, - "InvitesGroupsAndPagination": { + "UsersAndPagination": { "type": "object", - "properties": { - "invites": { - "type": "array", - "items": { - "$ref": "#/components/schemas/InvitesGroup" - } - }, - "nextPageToken": { - "type": "integer", - "format": "uint32" - } - }, "required": [ - "invites", + "users", "nextPageToken" - ] - }, - "UsersAndPagination": { - "type": "object", + ], "properties": { "users": { "type": "array", @@ -3289,11 +3274,7 @@ "type": "integer", "format": "uint32" } - }, - "required": [ - "users", - "nextPageToken" - ] + } } } } diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index ac3c2df..1f5bc5d 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -65,7 +65,15 @@ export function RescheduleRequests() { const getRescheduleRequests = async () => { const response = await slotifyClient.GetAPIRescheduleRequestsMe() - setmyRequests(response) + const requests : RescheduleRequest[] = [] + response.pending.forEach((request: RescheduleRequest) => { + requests.push(request) + }) + response.responses.forEach((request: RescheduleRequest) => { + requests.push(request) + }) + + setmyRequests(requests) } const getFullRequests = useCallback(async () => { diff --git a/src/types/client.ts b/src/types/client.ts index a482cfa..fc35ff4 100644 --- a/src/types/client.ts +++ b/src/types/client.ts @@ -23,6 +23,9 @@ type ReschedulingRequestOldMeeting = { meetingStartTime: string; timeRangeStart: string; timeRangeEnd: string; + meetingStartTime: string; + timeRangeStart: string; + timeRangeEnd: string; }; type ReschedulingRequestNewMeeting = { title: string; @@ -31,6 +34,7 @@ type ReschedulingRequestNewMeeting = { endTime: string; location: string; attendees: Array; + attendees: Array; }; type ReschedulingCheckBodySchema = { newMeeting: { @@ -181,10 +185,6 @@ type InvitesMe = { fromUserFirstName: string; fromUserLastName: string; }; -type InvitesGroupsAndPagination = { - invites: Array; - nextPageToken: number; -}; type UsersAndPagination = { users: Array; nextPageToken: number; @@ -258,6 +258,7 @@ const CalendarEvent: z.ZodType = z webLink: z.string().optional(), }) .passthrough(); +const User: z.ZodType = z const User: z.ZodType = z .object({ id: z.number().int(), @@ -311,8 +312,8 @@ const InvitesGroup: z.ZodType = z createdAt: z.string().datetime({ offset: true }), }) .passthrough(); -const InvitesGroupsAndPagination: z.ZodType = z - .object({ invites: z.array(InvitesGroup), nextPageToken: z.number().int() }) +const UsersAndPagination: z.ZodType = z + .object({ users: z.array(User), nextPageToken: z.number().int() }) .passthrough(); const SlotifyGroup = z .object({ id: z.number().int(), name: z.string() }) @@ -434,6 +435,13 @@ const ReschedulingCheckBodySchema: z.ZodType = z .passthrough(); const ReschedulingRequestOldMeeting: z.ZodType = z + .object({ + msftMeetingID: z.string(), + meetingId: z.number().int(), + meetingStartTime: z.string().datetime({ offset: true }), + timeRangeStart: z.string().datetime({ offset: true }), + timeRangeEnd: z.string().datetime({ offset: true }), + }) .object({ msftMeetingID: z.string(), meetingId: z.number().int(), @@ -451,6 +459,7 @@ const ReschedulingRequestNewMeeting: z.ZodType = endTime: z.string().datetime({ offset: true }), location: z.string(), attendees: z.array(z.number().int()), + attendees: z.array(z.number().int()), }) .passthrough(); const RescheduleRequest: z.ZodType = z @@ -513,7 +522,7 @@ export const schemas = { InvitesMe, InviteCreate, InvitesGroup, - InvitesGroupsAndPagination, + UsersAndPagination, SlotifyGroup, SlotifyGroupCreate, UsersAndPagination, @@ -1422,6 +1431,83 @@ const endpoints = makeApi([ }, ], }, + { + method: "get", + path: "/api/reschedule/request/:requestID/close", + alias: "GetAPIRescheduleRequestRequestIDClose", + requestFormat: "json", + parameters: [ + { + name: "requestID", + type: "Path", + schema: z.number().int(), + }, + ], + response: z.string(), + errors: [ + { + status: 400, + description: `Bad request`, + schema: z.void(), + }, + { + status: 401, + description: `Access token is missing or invalid`, + schema: z.void(), + }, + { + status: 500, + description: `Something went wrong internally`, + schema: z.string(), + }, + { + status: 502, + description: `Something went wrong with an external API`, + schema: z.string(), + }, + ], + }, + { + method: "post", + path: "/api/reschedule/request/:requestID/complete", + alias: "PostAPIRescheduleRequestRequestIDComplete", + requestFormat: "json", + parameters: [ + { + name: "body", + type: "Body", + schema: CalendarEvent, + }, + { + name: "requestID", + type: "Path", + schema: z.number().int(), + }, + ], + response: CalendarEvent, + errors: [ + { + status: 400, + description: `Bad request`, + schema: z.void(), + }, + { + status: 401, + description: `Access token is missing or invalid`, + schema: z.void(), + }, + { + status: 500, + description: `Something went wrong internally`, + schema: z.string(), + }, + { + status: 502, + description: `Something went wrong with an external API`, + schema: z.string(), + }, + ], + }, { method: "patch", path: "/api/reschedule/request/:requestID/reject", @@ -1525,6 +1611,12 @@ const endpoints = makeApi([ path: "/api/reschedule/requests/me", alias: "GetAPIRescheduleRequestsMe", requestFormat: "json", + response: z + .object({ + pending: z.array(RescheduleRequest), + responses: z.array(RescheduleRequest), + }) + .passthrough(), response: z .object({ pending: z.array(RescheduleRequest), @@ -1726,7 +1818,7 @@ const endpoints = makeApi([ schema: z.number().int(), }, ], - response: InvitesGroupsAndPagination, + response: UsersAndPagination, errors: [ { status: 400, @@ -1807,6 +1899,16 @@ const endpoints = makeApi([ type: "Query", schema: z.string().optional(), }, + { + name: "name", + type: "Query", + schema: z.string().optional(), + }, + { + name: "email", + type: "Query", + schema: z.string().optional(), + }, ], response: UsersAndPagination, errors: [ From 4f262dfc9712de96122d6d962213d2437d7c27f9 Mon Sep 17 00:00:00 2001 From: aevinj Date: Thu, 20 Mar 2025 00:01:36 +0000 Subject: [PATCH 11/12] fix create event button --- public/swagger/swagger.json | 30 +++++++++++++++++++++++----- shared | 2 +- src/app/dashboard/page.tsx | 2 +- src/components/calendar-overview.tsx | 7 ++++++- src/types/client.ts | 18 +++++++++-------- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/public/swagger/swagger.json b/public/swagger/swagger.json index de3a712..7f553b3 100644 --- a/public/swagger/swagger.json +++ b/public/swagger/swagger.json @@ -971,7 +971,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UsersAndPagination" + "$ref": "#/components/schemas/InvitesGroupsAndPagination" } } } @@ -2427,6 +2427,7 @@ }, "attendees": { "type": "array", + "nullable": true, "items": { "type": "integer", "format": "uint32", @@ -3257,12 +3258,27 @@ "name" ] }, - "UsersAndPagination": { + "InvitesGroupsAndPagination": { "type": "object", + "properties": { + "invites": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvitesGroup" + } + }, + "nextPageToken": { + "type": "integer", + "format": "uint32" + } + }, "required": [ - "users", + "invites", "nextPageToken" - ], + ] + }, + "UsersAndPagination": { + "type": "object", "properties": { "users": { "type": "array", @@ -3274,7 +3290,11 @@ "type": "integer", "format": "uint32" } - } + }, + "required": [ + "users", + "nextPageToken" + ] } } } diff --git a/shared b/shared index ea26ce8..5c0ec52 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit ea26ce89b3aa0577932f30309ebb4def7c69cb2e +Subproject commit 5c0ec528ba82be4638ea6dfbf00b3251f961ee71 diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 74055f1..16187fe 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -122,7 +122,7 @@ export default function Dashboard() { } return ( -
+
diff --git a/src/components/calendar-overview.tsx b/src/components/calendar-overview.tsx index 25aace1..7cb3fb4 100644 --- a/src/components/calendar-overview.tsx +++ b/src/components/calendar-overview.tsx @@ -407,7 +407,12 @@ export function CalendarOverview() { setIsCreateEventOpen(false)} + initialTitle={''} + initialDuration={'1hr'} + initialParticipants={[]} + initialSelectedRange={null} + inputsDisabled={false} /> ; - attendees: Array; + attendees: Array | null; }; type ReschedulingCheckBodySchema = { newMeeting: { @@ -185,6 +184,10 @@ type InvitesMe = { fromUserFirstName: string; fromUserLastName: string; }; +type InvitesGroupsAndPagination = { + invites: Array; + nextPageToken: number; +}; type UsersAndPagination = { users: Array; nextPageToken: number; @@ -312,8 +315,8 @@ const InvitesGroup: z.ZodType = z createdAt: z.string().datetime({ offset: true }), }) .passthrough(); -const UsersAndPagination: z.ZodType = z - .object({ users: z.array(User), nextPageToken: z.number().int() }) +const InvitesGroupsAndPagination: z.ZodType = z + .object({ invites: z.array(InvitesGroup), nextPageToken: z.number().int() }) .passthrough(); const SlotifyGroup = z .object({ id: z.number().int(), name: z.string() }) @@ -458,8 +461,7 @@ const ReschedulingRequestNewMeeting: z.ZodType = startTime: z.string().datetime({ offset: true }), endTime: z.string().datetime({ offset: true }), location: z.string(), - attendees: z.array(z.number().int()), - attendees: z.array(z.number().int()), + attendees: z.array(z.number().int()).nullable(), }) .passthrough(); const RescheduleRequest: z.ZodType = z @@ -522,7 +524,7 @@ export const schemas = { InvitesMe, InviteCreate, InvitesGroup, - UsersAndPagination, + InvitesGroupsAndPagination, SlotifyGroup, SlotifyGroupCreate, UsersAndPagination, @@ -1818,7 +1820,7 @@ const endpoints = makeApi([ schema: z.number().int(), }, ], - response: UsersAndPagination, + response: InvitesGroupsAndPagination, errors: [ { status: 400, From 64151587777b6054de24a345277602599dee097d Mon Sep 17 00:00:00 2001 From: aevinj Date: Thu, 20 Mar 2025 00:10:27 +0000 Subject: [PATCH 12/12] updated generated file linting changes fix reschedule names prettier changes --- src/components/calendar-overview.tsx | 6 +- src/components/reschedule-requests.tsx | 9 ++- src/types/client.ts | 104 ------------------------- 3 files changed, 6 insertions(+), 113 deletions(-) diff --git a/src/components/calendar-overview.tsx b/src/components/calendar-overview.tsx index 7cb3fb4..07f5375 100644 --- a/src/components/calendar-overview.tsx +++ b/src/components/calendar-overview.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { format, startOfWeek, @@ -70,10 +70,6 @@ export function CalendarOverview() { const viewportRef = useRef(null) - const closeCreateEventDialogOpen = useCallback(() => { - setIsCreateEventOpen(false) - }, [setIsCreateEventOpen]) - useEffect(() => { if (viewportRef.current) { const el = viewportRef.current diff --git a/src/components/reschedule-requests.tsx b/src/components/reschedule-requests.tsx index 1f5bc5d..67a609f 100644 --- a/src/components/reschedule-requests.tsx +++ b/src/components/reschedule-requests.tsx @@ -65,14 +65,14 @@ export function RescheduleRequests() { const getRescheduleRequests = async () => { const response = await slotifyClient.GetAPIRescheduleRequestsMe() - const requests : RescheduleRequest[] = [] + const requests: RescheduleRequest[] = [] response.pending.forEach((request: RescheduleRequest) => { requests.push(request) }) response.responses.forEach((request: RescheduleRequest) => { requests.push(request) }) - + setmyRequests(requests) } @@ -348,8 +348,9 @@ export function RescheduleRequests() {

- {request.newMeeting?.title - ? request.newMeeting.title + {fullRequests[request.request_id] + ? fullRequests[request.request_id]!.oldEvent + .subject : '(No name)'}

diff --git a/src/types/client.ts b/src/types/client.ts index bb39566..878b49d 100644 --- a/src/types/client.ts +++ b/src/types/client.ts @@ -23,9 +23,6 @@ type ReschedulingRequestOldMeeting = { meetingStartTime: string; timeRangeStart: string; timeRangeEnd: string; - meetingStartTime: string; - timeRangeStart: string; - timeRangeEnd: string; }; type ReschedulingRequestNewMeeting = { title: string; @@ -261,7 +258,6 @@ const CalendarEvent: z.ZodType = z webLink: z.string().optional(), }) .passthrough(); -const User: z.ZodType = z const User: z.ZodType = z .object({ id: z.number().int(), @@ -438,13 +434,6 @@ const ReschedulingCheckBodySchema: z.ZodType = z .passthrough(); const ReschedulingRequestOldMeeting: z.ZodType = z - .object({ - msftMeetingID: z.string(), - meetingId: z.number().int(), - meetingStartTime: z.string().datetime({ offset: true }), - timeRangeStart: z.string().datetime({ offset: true }), - timeRangeEnd: z.string().datetime({ offset: true }), - }) .object({ msftMeetingID: z.string(), meetingId: z.number().int(), @@ -1433,83 +1422,6 @@ const endpoints = makeApi([ }, ], }, - { - method: "get", - path: "/api/reschedule/request/:requestID/close", - alias: "GetAPIRescheduleRequestRequestIDClose", - requestFormat: "json", - parameters: [ - { - name: "requestID", - type: "Path", - schema: z.number().int(), - }, - ], - response: z.string(), - errors: [ - { - status: 400, - description: `Bad request`, - schema: z.void(), - }, - { - status: 401, - description: `Access token is missing or invalid`, - schema: z.void(), - }, - { - status: 500, - description: `Something went wrong internally`, - schema: z.string(), - }, - { - status: 502, - description: `Something went wrong with an external API`, - schema: z.string(), - }, - ], - }, - { - method: "post", - path: "/api/reschedule/request/:requestID/complete", - alias: "PostAPIRescheduleRequestRequestIDComplete", - requestFormat: "json", - parameters: [ - { - name: "body", - type: "Body", - schema: CalendarEvent, - }, - { - name: "requestID", - type: "Path", - schema: z.number().int(), - }, - ], - response: CalendarEvent, - errors: [ - { - status: 400, - description: `Bad request`, - schema: z.void(), - }, - { - status: 401, - description: `Access token is missing or invalid`, - schema: z.void(), - }, - { - status: 500, - description: `Something went wrong internally`, - schema: z.string(), - }, - { - status: 502, - description: `Something went wrong with an external API`, - schema: z.string(), - }, - ], - }, { method: "patch", path: "/api/reschedule/request/:requestID/reject", @@ -1613,12 +1525,6 @@ const endpoints = makeApi([ path: "/api/reschedule/requests/me", alias: "GetAPIRescheduleRequestsMe", requestFormat: "json", - response: z - .object({ - pending: z.array(RescheduleRequest), - responses: z.array(RescheduleRequest), - }) - .passthrough(), response: z .object({ pending: z.array(RescheduleRequest), @@ -1901,16 +1807,6 @@ const endpoints = makeApi([ type: "Query", schema: z.string().optional(), }, - { - name: "name", - type: "Query", - schema: z.string().optional(), - }, - { - name: "email", - type: "Query", - schema: z.string().optional(), - }, ], response: UsersAndPagination, errors: [