diff --git a/backend/logics/Mailer/mailer.mjs b/backend/logics/Mailer/mailer.mjs index 64cf594..0656d71 100644 --- a/backend/logics/Mailer/mailer.mjs +++ b/backend/logics/Mailer/mailer.mjs @@ -30,7 +30,7 @@ export class Mailer { const response = { body: { name: userName, - intro: `You have an upcoming event/meeting: ${eventName}`, + intro: `This is to inform you of an upcoming event/meeting: ${eventName}`, table: { data: [ { @@ -59,7 +59,7 @@ export class Mailer { } ] }, - outro: "Kindly be present there are per the scheduled time" + outro: "Kindly be present at the aforementioned timings" } } diff --git a/backend/test.js b/backend/test.js new file mode 100644 index 0000000..d4bb537 --- /dev/null +++ b/backend/test.js @@ -0,0 +1,251 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; +import { GraphQLClient } from 'graphql-request'; +import { GET_AVAILABILITY, UPSERT_AVAILABILITY, DELETE_AVAILABILITY } from './graphqlQueries'; +import './AvailabilityForm.css'; + +const graphqlClient = new GraphQLClient('http://localhost:8080/v1/graphql', { + headers: { + 'x-hasura-admin-secret': '123', + }, +}); + +const TimeSelect = ({ disabled, onChange, value }) => { + const times = []; + for (let i = 0; i < 24; i++) { + for (let j = 0; j < 60; j += 30) { + const time = `${i.toString().padStart(2, '0')}:${j.toString().padStart(2, '0')}`; + times.push(time); + } + } + + return ( + + ); +}; + +const AvailabilityForm = () => { + const daysOfWeek = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']; + const {eventName} = useParams(); + const [availability, setAvailability] = useState( + daysOfWeek.reduce((acc, day) => ({...acc, [day]: [{selected: false, startTime: '', endTime: ''}]}), {}) + ); + const [isFormValid, setIsFormValid] = useState(true); + + useEffect(() => { + const fetchAvailability = async () => { + try { + const response = await graphqlClient.request(GET_AVAILABILITY, {eventName}); + const fetchedAvailability = response.availability.reduce((acc, slot) => { + if (!acc[slot.day]) { + acc[slot.day] = []; + } + acc[slot.day].push({selected: true, startTime: slot.start_time, endTime: slot.end_time}); + return acc; + }, {}); + const newAvailability = {...availability}; + for (const day of daysOfWeek) { + if (fetchedAvailability[day]) { + newAvailability[day] = fetchedAvailability[day]; + } + } + setAvailability(newAvailability); + } catch (error) { + console.error('Failed to fetch availability:', error); + } + }; + + fetchAvailability(); + }, [eventName]); + + useEffect(() => { + const validateForm = () => { + for (const day in availability) { + const slots = availability[day]; + for (let i = 0; i < slots.length; i++) { + const slot = slots[i]; + if (slot.selected) { + if (slot.startTime >= slot.endTime) { + setIsFormValid(false); + return; + } + for (let j = 0; j < slots.length; j++) { + if (i !== j && slots[j].selected) { + if ( + (slot.startTime < slots[j].endTime && slot.endTime > slots[j].startTime) || + (slots[j].startTime < slot.endTime && slots[j].endTime > slot.startTime) + ) { + setIsFormValid(false); + return; + } + } + } + } + } + } + setIsFormValid(true); + }; + + validateForm(); + }, [availability]); + + const handleDayChange = (day, index) => async (event) => { + const updatedDaySlots = [...availability[day]]; + if (updatedDaySlots[index]) { + updatedDaySlots[index].selected = event.target.checked; + setAvailability({...availability, [day]: updatedDaySlots}); + + if (!event.target.checked) { + // Delete the slot if it's being deselected + const slotToDelete = availability[day][index]; + try { + const response = await graphqlClient.request(DELETE_AVAILABILITY, { + day: day, + startTime: slotToDelete.startTime, + eventName: eventName, + }); + if (response.delete_availability.affected_rows > 0) { + console.log('Slot deleted successfully'); + } else { + console.error('Failed to delete slot'); + } + } catch (e) { + console.error('Error occurred while deleting slot', e); + } + updatedDaySlots.splice(index, 1); + setAvailability({...availability, [day]: updatedDaySlots}); + } + } + }; + + const handleTimeChange = (day, index, timeType) => (event) => { + const updatedDaySlots = [...availability[day]]; + if (updatedDaySlots[index]) { + updatedDaySlots[index][timeType] = event.target.value; + setAvailability({...availability, [day]: updatedDaySlots}); + } + }; + + // const addTimeSlot = (day) => { + // setAvailability({ + // ...availability, + // [day]: [...availability[day], {selected: false, startTime: '', endTime: ''}] + // }); + // }; + + const deleteTimeSlot = async (day, index) => { + const slotToDelete = availability[day][index]; + try { + const response = await graphqlClient.request(DELETE_AVAILABILITY, { + day: day, + startTime: slotToDelete.startTime, + eventName: eventName, + }); + if (response.delete_availability.affected_rows > 0) { + console.log('Slot deleted successfully'); + } else { + console.error('Failed to delete slot'); + } + } catch (e) { + console.error('Error occurred while deleting slot', e); + } + + const updatedDaySlots = [...availability[day]]; + updatedDaySlots.splice(index, 1); + setAvailability({...availability, [day]: updatedDaySlots}); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + + const filteredAvailability = Object.fromEntries( + Object.entries(availability).filter(([day, slots]) => slots.some(slot => slot.selected)) + ); + + try { + let flag=0; + for (const [day, slots] of Object.entries(filteredAvailability)) { + for (const slot of slots) { + if (slot.selected) { + const data = await graphqlClient.request(UPSERT_AVAILABILITY, { + day: day, + startTime: slot.startTime, + endTime: slot.endTime, + eventName: eventName, + }); + console.log(data.insert_availability_one.event_name); + + if(!(data.insert_availability_one.event_name==eventName)){ + alert(`Availability will not set for Timeline:\n${day} ${slot.startTime}\nas it is already present in Event Name:\n${data.insert_availability_one.event_name}`); + flag+=1; + } + + } + } + } + if(flag!=1) { + alert('Availability set successfully!'); + } + } catch (e) { + console.error('Error occurred while setting availability', e); + alert('Failed to set availability'); + } + }; + + const addTimeSlot = (day) => { + setAvailability({ + ...availability, + [day]: [...availability[day], {selected: false, startTime: '', endTime: ''}] + }); + }; + + return ( +
Thank you for booking. You will receive a confirmation email shortly.
-Start time must be before end time
} {slot.selected && ( + className="remove-button">X )} ))} diff --git a/frontend/frontend/src/pages/Availability/AvaiblityCopy.js b/frontend/frontend/src/pages/Availability/AvaiblityCopy.js new file mode 100644 index 0000000..3d1e758 --- /dev/null +++ b/frontend/frontend/src/pages/Availability/AvaiblityCopy.js @@ -0,0 +1,211 @@ +import React, { useState, useEffect } from 'react'; +import { useEvent } from '../EventDetails/EventDetailsCopy'; +import { GraphQLClient } from 'graphql-request'; +import { useParams } from 'react-router-dom'; +import { UPSERT_AVAILABILITY, DELETE_AVAILABILITY } from './graphqlQueries'; +import './AvailabilityFormCopy.css'; + +const graphqlClient = new GraphQLClient('http://localhost:8080/v1/graphql', { + headers: { + 'x-hasura-admin-secret': '123', + }, +}); + +const TimeSelect = ({ disabled, onChange, value }) => { + const times = []; + for (let i = 0; i < 24; i++) { + for (let j = 0; j < 60; j += 30) { + const time = `${i.toString().padStart(2, '0')}:${j.toString().padStart(2, '0')}`; + times.push(time); + } + } + + return ( + + ); +}; + +const AvailabilityForm = () => { + const daysOfWeek = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']; + const {eventName} = useParams(); + const { availability, setAvailability } = useEvent(); + const [isFormValid, setIsFormValid] = useState(true); + + useEffect(() => { + const validateForm = () => { + for (const day in availability) { + const slots = availability[day]; + for (let i = 0; i < slots.length; i++) { + const slot = slots[i]; + if (slot.selected) { + if (slot.startTime >= slot.endTime) { + setIsFormValid(false); + return; + } + for (let j = 0; j < slots.length; j++) { + if (i !== j && slots[j].selected) { + if ( + (slot.startTime < slots[j].endTime && slot.endTime > slots[j].startTime) || + (slots[j].startTime < slot.endTime && slots[j].endTime > slot.startTime) + ) { + setIsFormValid(false); + return; + } + } + } + } + } + } + setIsFormValid(true); + }; + + validateForm(); + }, [availability]); + + const handleDayChange = (day, index) => async (event) => { + const updatedDaySlots = [...availability[day]]; + if (updatedDaySlots[index]) { + updatedDaySlots[index].selected = event.target.checked; + setAvailability({ ...availability, [day]: updatedDaySlots }); + + if (!event.target.checked) { + // Delete the slot if it's being deselected + const slotToDelete = availability[day][index]; + try { + const response = await graphqlClient.request(DELETE_AVAILABILITY, { + day: day, + startTime: slotToDelete.startTime, + eventName: eventName, + }); + if (response.delete_availability.affected_rows > 0) { + console.log('Slot deleted successfully'); + } else { + console.error('Failed to delete slot'); + } + } catch (e) { + console.error('Error occurred while deleting slot', e); + } + updatedDaySlots.splice(index, 1); + setAvailability({ ...availability, [day]: updatedDaySlots }); + } + } + }; + + const handleTimeChange = (day, index, timeType) => (event) => { + const updatedDaySlots = [...availability[day]]; + if (updatedDaySlots[index]) { + updatedDaySlots[index][timeType] = event.target.value; + setAvailability({ ...availability, [day]: updatedDaySlots }); + } + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + + const filteredAvailability = Object.fromEntries( + Object.entries(availability).filter(([day, slots]) => slots.some(slot => slot.selected)) + ); + + try { + let flag = false; + for (const [day, slots] of Object.entries(filteredAvailability)) { + for (const slot of slots) { + if (slot.selected) { + const response = await graphqlClient.request(UPSERT_AVAILABILITY, { + eventName, + day, + startTime: slot.startTime, + endTime: slot.endTime, + }); + if(!(response.insert_availability_one.event_name===eventName)){ + alert(`Availability will not set for Timeline:\n${day} ${slot.startTime}\nas it is already present in Event Name:\n${response.insert_availability_one.event_name}`); + flag = true; + } + } + } + } + if (!flag) { + alert('Availability saved successfully.'); + } else { + alert('Failed to save some slots.'); + } + } catch (error) { + console.error('Failed to save availability:', error); + } + }; + + const addTimeSlot = (day) => { + const updatedDaySlots = [...(availability[day] || [])]; + updatedDaySlots.push({ selected: true, startTime: '00:00', endTime: '00:00' }); + setAvailability({ ...availability, [day]: updatedDaySlots }); + }; + + const deleteTimeSlot = (day, index) => async () => { + const updatedDaySlots = [...availability[day]]; + const slotToDelete = updatedDaySlots[index]; + if (slotToDelete) { + try { + const response = await graphqlClient.request(DELETE_AVAILABILITY, { + day: day, + startTime: slotToDelete.startTime, + eventName: eventName, + }); + if (response.delete_availability.affected_rows > 0) { + console.log('Slot deleted successfully'); + } else { + console.error('Failed to delete slot'); + } + } catch (e) { + console.error('Error occurred while deleting slot', e); + } + updatedDaySlots.splice(index, 1); + setAvailability({ ...availability, [day]: updatedDaySlots }); + } + }; + + return ( + + ); +}; + +export default AvailabilityForm; diff --git a/frontend/frontend/src/pages/Availability/AvailabilityForm.css b/frontend/frontend/src/pages/Availability/AvailabilityForm.css new file mode 100644 index 0000000..3e53310 --- /dev/null +++ b/frontend/frontend/src/pages/Availability/AvailabilityForm.css @@ -0,0 +1,118 @@ +/* AvailabilityForm.css */ + +.availability-form { + display: flex; + flex-direction: column; + align-items: center; + width: 80%; /* Adjust width to better fit the content */ + margin: 0 auto; /* Center the form */ + padding: 20px; /* Add padding for better spacing */ + background-color: #f9f9f9; /* Light background for contrast */ + border-radius: 20px; /* Rounded corners for better aesthetics */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ + border-color: #000000; +} + +.title { + font-size: 24px; + font-weight: bold; + margin-bottom: 20px; +} + +.form { + width: 100%; +} + +.days-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); /* Increase columns for more compact layout */ + gap: 10px; +} + +.day { + display: flex; + flex-direction: column; + align-items: center; + background-color: #fff; /* White background for each day */ + padding: 15px; /* Add padding for space */ + border-radius: 5px; /* Slight rounding for better aesthetics */ + box-shadow: 0 2px 4px rgba(7, 6, 6, 0.1); /* Light shadow for depth */ +} + +.day-name { + font-size: 18px; + font-weight: bold; + margin-bottom: 10px; + color: #333; /* Darker text color for better readability */ +} + +.time-slot { + display: flex; + align-items: center; + margin-bottom: 10px; + width: 100%; /* Full width for better alignment */ + justify-content: space-between; /* Space out elements evenly */ +} + +.time-slot select, .time-slot input[type="checkbox"] { + margin-right: 5px; /* Space out elements */ +} + +.time-slot .text-red-500 { + font-size: 12px; /* Smaller font for error message */ + margin-left: 5px; +} + +.add-slot-button { + margin-top: 10px; + padding: 5px 10px; /* Add padding for better click area */ + background-color: #4CAF50; /* Green background */ + color: white; /* White text */ + border: none; + border-radius: 5px; /* Rounded corners */ + cursor: pointer; /* Pointer cursor */ + transition: background-color 0.3s ease; /* Smooth transition */ +} + +.add-slot-button:hover { + background-color: #45a049; /* Darker green on hover */ +} + + +.remove-button button{ + background-color:#e53935; +} + +.submit-button { + margin-top: 20px; + padding: 10px 20px; /* Add padding for better click area */ + background-color: #008CBA; /* Blue background */ + color: white; /* White text */ + border: none; + border-radius: 5px; /* Rounded corners */ + cursor: pointer; /* Pointer cursor */ + transition: background-color 0.3s ease; /* Smooth transition */ +} + +.submit-button:disabled { + background-color: #ccc; /* Grey background when disabled */ + cursor: not-allowed; /* Not allowed cursor when disabled */ +} + +.submit-button:hover:not(:disabled) { + background-color: #007bb5; /* Darker blue on hover */ +} + +.time-slot button { + background-color: #f44336; /* Red background for delete button */ + color: white; /* White text */ + border: none; + border-radius: 5px; /* Rounded corners */ + padding: 5px 10px; /* Add padding for better click area */ + cursor: pointer; /* Pointer cursor */ + transition: background-color 0.3s ease; /* Smooth transition */ +} + +.time-slot button:hover { + background-color: #e53935; /* Darker red on hover */ +} diff --git a/frontend/frontend/src/pages/Availability/AvailabilityFormCopy.css b/frontend/frontend/src/pages/Availability/AvailabilityFormCopy.css new file mode 100644 index 0000000..8ecf970 --- /dev/null +++ b/frontend/frontend/src/pages/Availability/AvailabilityFormCopy.css @@ -0,0 +1,69 @@ +.availability-form { + display: flex; + flex-direction: column; + align-items: center; + width: 80%; /* Adjust width to better fit the content */ + margin: 0 auto; /* Center the form */ + padding: 20px; /* Add padding for better spacing */ + background-color: #f9f9f9; /* Light background for contrast */ + border-radius: 20px; /* Rounded corners for better aesthetics */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ + border-color: #000000; +} + +.availability-day { + margin-bottom: 20px; /* Added margin between days for spacing */ + display: flex; + flex-direction: column; + align-items: center; + background-color: #fff; /* White background for each day */ + padding: 15px; /* Add padding for space */ + border-radius: 5px; /* Slight rounding for better aesthetics */ + box-shadow: 0 2px 4px; /* Light shadow for depth */ + +} + +.availability-slot { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.availability-slot input[type="checkbox"] { + margin-right: 10px; /* Added margin to separate checkbox from time inputs */ +} + +.availability-slot button { + margin-left: 10px; /* Added margin to separate delete button from time inputs */ + padding: 5px 10px; /* Adjusted padding for the delete button */ + border: none; + border-radius: 4px; + background-color: #f44336; /* Changed delete button background color */ + color: white; + cursor: pointer; +} + +.availability-slot button:hover { + background-color: #cc0000; /* Darkened delete button color on hover */ +} + +.availability-day h3 { + font-size: 18px; + font-weight: bold; + margin-bottom: 10px; +} + +button[type="submit"] { + margin-top: 20px; + padding: 10px 20px; + border: none; + border-radius: 4px; + background-color: #4CAF50; + color: white; + cursor: pointer; +} + +button[type="submit"]:disabled { + background-color: #cccccc; /* Changed disabled button background color */ + cursor: not-allowed; +} diff --git a/frontend/frontend/src/components/Availability/graphqlQueries.js b/frontend/frontend/src/pages/Availability/graphqlQueries.js similarity index 100% rename from frontend/frontend/src/components/Availability/graphqlQueries.js rename to frontend/frontend/src/pages/Availability/graphqlQueries.js diff --git a/frontend/frontend/src/components/CreateEvent/CreateEvent.css b/frontend/frontend/src/pages/CreateEvent/CreateEvent.css similarity index 100% rename from frontend/frontend/src/components/CreateEvent/CreateEvent.css rename to frontend/frontend/src/pages/CreateEvent/CreateEvent.css diff --git a/frontend/frontend/src/components/CreateEvent/CreateEvent.js b/frontend/frontend/src/pages/CreateEvent/CreateEvent.js similarity index 100% rename from frontend/frontend/src/components/CreateEvent/CreateEvent.js rename to frontend/frontend/src/pages/CreateEvent/CreateEvent.js diff --git a/frontend/frontend/src/components/EventDetails/EventDetails.css b/frontend/frontend/src/pages/EventDetails/EventDetails.css similarity index 86% rename from frontend/frontend/src/components/EventDetails/EventDetails.css rename to frontend/frontend/src/pages/EventDetails/EventDetails.css index dbb00fd..26120b6 100644 --- a/frontend/frontend/src/components/EventDetails/EventDetails.css +++ b/frontend/frontend/src/pages/EventDetails/EventDetails.css @@ -11,6 +11,7 @@ box-sizing: border-box; border: 1px solid #ccc; border-radius: 8px; + background-color: #f9f9f9; /* Light background for better contrast */ } .event-edit form { @@ -64,5 +65,5 @@ } .edit-button:hover { - background-color: #45a049; + background-color: #2a47a6; } diff --git a/frontend/frontend/src/components/EventDetails/EventDetails.js b/frontend/frontend/src/pages/EventDetails/EventDetails.js similarity index 100% rename from frontend/frontend/src/components/EventDetails/EventDetails.js rename to frontend/frontend/src/pages/EventDetails/EventDetails.js diff --git a/frontend/frontend/src/pages/EventDetails/EventDetailsCopy.js b/frontend/frontend/src/pages/EventDetails/EventDetailsCopy.js new file mode 100644 index 0000000..0f55346 --- /dev/null +++ b/frontend/frontend/src/pages/EventDetails/EventDetailsCopy.js @@ -0,0 +1,125 @@ +import React, { useState, useEffect, createContext, useContext } from 'react'; +import { useParams } from 'react-router-dom'; +import AvailabilityForm from '../Availability/AvaiblityCopy'; +import { GraphQLClient } from 'graphql-request'; +import { GET_EVENT_AND_AVAILABILITY, UPDATE_EVENT_DETAIL } from './query'; +import './EventDetails.css'; + +// Create a context for sharing event data +const EventContext = createContext(); + +const graphqlClient = new GraphQLClient('http://localhost:8080/v1/graphql', { + headers: { + 'x-hasura-admin-secret': '123', + }, +}); + +export const useEvent = () => useContext(EventContext); + +const EventDetails = () => { + const { eventName } = useParams(); + const [event, setEvent] = useState(null); + const [editEvent, setEditEvent] = useState(null); + const [availability, setAvailability] = useState({}); + const [showEditForm, setShowEditForm] = useState(false); + const [showAvailabilityForm, setShowAvailabilityForm] = useState(false); + + useEffect(() => { + const fetchEventAndAvailability = async () => { + try { + const response = await graphqlClient.request(GET_EVENT_AND_AVAILABILITY, { eventName }); + setEvent(response.event); + setEditEvent(response.event); + const newAvailability = response.availability.reduce((acc, slot) => { + if (!acc[slot.day]) { + acc[slot.day] = []; + } + acc[slot.day].push({ selected: true, startTime: slot.start_time, endTime: slot.end_time }); + return acc; + }, {}); + setAvailability(newAvailability); + } catch (error) { + console.error('Failed to fetch event and availability:', error); + } + }; + + fetchEventAndAvailability(); + }, [eventName]); + + const handleEditChange = (e) => { + setEditEvent({ ...editEvent, [e.target.name]: e.target.value }); + }; + + const handleEditSubmit = async (e) => { + e.preventDefault(); + + try { + const response = await graphqlClient.request(UPDATE_EVENT_DETAIL, { + eventName, + data: { + event_name: editEvent.event_name, + duration: editEvent.duration, + location_type: editEvent.location_type, + location_detail: editEvent.location_detail, + }, + }); + + setEvent(response.update_kalenview_create_events_by_pk); + setShowEditForm(false); + } catch (error) { + console.error('Failed to update event:', error); + } + }; + + if (!event) { + returnDuration: {event.duration} minutes
+Location Type: {event.location_type}
+Description: {event.location_detail}
+{eventName}
{organizerName.first_name +" "+ organizerName.last_name}
diff --git a/frontend/frontend/src/components/confirmpage/query.js b/frontend/frontend/src/pages/confirmpage/query.js similarity index 100% rename from frontend/frontend/src/components/confirmpage/query.js rename to frontend/frontend/src/pages/confirmpage/query.js diff --git a/frontend/frontend/src/components/login/Login.css b/frontend/frontend/src/pages/login/Login.css similarity index 100% rename from frontend/frontend/src/components/login/Login.css rename to frontend/frontend/src/pages/login/Login.css diff --git a/frontend/frontend/src/components/login/Login.js b/frontend/frontend/src/pages/login/Login.js similarity index 100% rename from frontend/frontend/src/components/login/Login.js rename to frontend/frontend/src/pages/login/Login.js diff --git a/frontend/frontend/src/components/slotbook/query.js b/frontend/frontend/src/pages/slotbook/query.js similarity index 100% rename from frontend/frontend/src/components/slotbook/query.js rename to frontend/frontend/src/pages/slotbook/query.js diff --git a/frontend/frontend/src/components/slotbook/slotBook.css b/frontend/frontend/src/pages/slotbook/slotBook.css similarity index 63% rename from frontend/frontend/src/components/slotbook/slotBook.css rename to frontend/frontend/src/pages/slotbook/slotBook.css index eb668c0..2d4d174 100644 --- a/frontend/frontend/src/components/slotbook/slotBook.css +++ b/frontend/frontend/src/pages/slotbook/slotBook.css @@ -1,5 +1,3 @@ - - /* slotBook.css */ /* Container for the card */ @@ -14,17 +12,22 @@ /* Card styling */ .card { - background-color: white; + background-color: #ffffff; border: 1px solid #ddd; border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); padding: 20px; - max-width: 800px; + max-width: 1000px; width: 100%; display: flex; flex-direction: row; justify-content: space-between; flex-wrap: wrap; + transition: box-shadow 0.3s ease; +} + +.card:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } .card h2 { @@ -48,12 +51,32 @@ margin-right: 20px; } -/* Calendar container inside the content container */ -.calendar-container { - width: 35%; - border-right: 1px solid #ddd; - padding-right: 20px; - margin-right: 20px; +/*!* Calendar card styling *!*/ +/*.calendar-card {*/ +/* background-color: #ffffff;*/ +/* border: 1px solid #ddd;*/ +/* border-radius: 8px;*/ +/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);*/ +/* padding: 20px;*/ +/* width: 35%;*/ +/* display: flex;*/ +/* flex-direction: column;*/ +/* align-items: center;*/ +/* transition: box-shadow 0.3s ease;*/ +/*}*/ +.calendar-card { + padding: 20px; + width: 40%; /* Increased width from 35% to 40% */ + margin-right: 20px; /* Ensure there's some spacing between cards */ + background-color: #ffffff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 2px 8px; + transition: box-shadow 0.3s ease; +} + +.calendar-card:hover { + box-shadow: 0 4px 16px; } .react-calendar { @@ -70,15 +93,12 @@ padding-bottom: 20px; } -.react-calendar__navigation__label { - border: none; - background-color: white; -} - +.react-calendar__navigation__label, .react-calendar__navigation__prev-button, .react-calendar__navigation__next-button { border: none; background-color: white; + color: #ffffff; } .react-calendar__navigation__prev2-button, @@ -94,6 +114,7 @@ .react-calendar__navigation button { font-size: 1em; + color: black; } .react-calendar__month-view__weekdays__weekday { @@ -122,6 +143,7 @@ abbr[title] { display: flex; justify-content: center; align-items: center; + transition: background-color 0.3s ease, box-shadow 0.3s ease; } .react-calendar__tile--active { @@ -130,22 +152,17 @@ abbr[title] { line-height: 10px; color: #ef6234; cursor: pointer; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } -.react-calendar__tile--hover { - background: #ef6234; - border-radius: 30px; - line-height: 10px; - color: white; - cursor: pointer; -} - +.react-calendar__tile--hover, .react-calendar button:enabled:focus { background: #ef6234; border-radius: 30px; line-height: 10px; color: white; cursor: pointer; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } /* Styles for the slot booking page */ @@ -154,11 +171,18 @@ abbr[title] { margin-top: 20px; padding: 20px; border: 1px solid #ddd; - border-radius: 5px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.3s ease; +} + +.slot-booking-page:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); } .slot-booking-page h2 { margin-bottom: 10px; + color: #333; } .slot-booking-page p { @@ -167,10 +191,12 @@ abbr[title] { border: 1px solid #ddd; border-radius: 5px; cursor: pointer; + transition: background-color 0.3s ease, box-shadow 0.3s ease; } .slot-booking-page p:hover { background-color: #f5f5f5; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } /* Styles for the slot boxes */ @@ -184,7 +210,7 @@ abbr[title] { margin-bottom: 10px; border-radius: 5px; background-color: #f0f0f0; - transition: background-color 0.3s ease; /* Smooth transition for hover effect */ + transition: background-color 0.3s ease, box-shadow 0.3s ease; cursor: pointer; } @@ -195,30 +221,48 @@ abbr[title] { } .slot-box p { - margin: 0; /* Remove default margin */ - padding: 0; /* Remove default padding */ + margin: 0; + padding: 0; font-size: 16px; - color: #333; } .slot-box:hover { - background-color: #cce5ff; /* Lighter blue for hover effect */ + background-color: #cce5ff; + box-shadow: 0 2px 8px; } /* Styles for the split slot view */ .slot-half { - width: 50%; + width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; font-size: 14px; + font-weight: bold; color: #333; - background-color: #e9ecef; /* Background color for the split halves */ - border-right: 1px solid #ddd; /* Separator between halves */ + background-color: #e9ecef; + border-right: 1px solid #ddd; } .slot-half:last-child { - border-right: none; /* Remove border on the last half */ + border-right: none; + color: black; +} +.slot-half.next-half { + background-color: #007BFF; + color: white; + cursor: pointer; +} + +.slot-half.next-half:hover { + background-color: #0056b3; } +.slot-box.clicked .slot-half { + width: 50%; + cursor: default; +} +/*#half-next {*/ +/* background-color: #0d73e0;*/ +/*}*/ diff --git a/frontend/frontend/src/components/slotbook/slotbook.js b/frontend/frontend/src/pages/slotbook/slotbook.js similarity index 67% rename from frontend/frontend/src/components/slotbook/slotbook.js rename to frontend/frontend/src/pages/slotbook/slotbook.js index 98ce725..5ae96de 100644 --- a/frontend/frontend/src/components/slotbook/slotbook.js +++ b/frontend/frontend/src/pages/slotbook/slotbook.js @@ -8,7 +8,7 @@ import './slotBook.css'; const graphqlClient = new GraphQLClient('http://localhost:8080/v1/graphql', { headers: { - 'x-hasura-admin-secret': '123', // Replace with your actual admin secret or use a more secure method for authentication + 'x-hasura-admin-secret': '123', }, }); @@ -22,44 +22,27 @@ function CalendarPage() { const [selectedDayOfWeek, setSelectedDayOfWeek] = useState(''); const [availableSlots, setAvailableSlots] = useState([]); const [bookedSlots, setBookedSlots] = useState([]); + const [clickedSlots, setClickedSlots] = useState({}); - // const selectedDateSetter = (value) => { - // const options = { weekday: 'short' }; - // const dayOfWeek = value.toLocaleDateString('en-US', options).toUpperCase(); - // const formattedDate = value.toISOString().split('T')[0]; // Format date as YYYY-MM-DD - // setSelectedDate(formattedDate); - // setSelectedDayOfWeek(dayOfWeek); - // fetchSlots(dayOfWeek, formattedDate); - // }; const selectedDateSetter = (value) => { - // Normalize the date to UTC to avoid timezone issues const utcDate = new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate())); - - const options = { weekday: 'short', timeZone: 'UTC' }; // Ensure we use UTC for consistent results + const options = { weekday: 'short', timeZone: 'UTC' }; const dayOfWeek = utcDate.toLocaleDateString('en-US', options).toUpperCase(); - const formattedDate = utcDate.toISOString().split('T')[0]; // Format date as YYYY-MM-DD - + const formattedDate = utcDate.toISOString().split('T')[0]; setSelectedDate(formattedDate); setSelectedDayOfWeek(dayOfWeek); fetchSlots(dayOfWeek, formattedDate); }; - const fetchSlots = async (dayOfWeek, formattedDate) => { try { const response = await graphqlClient.request(GET_SLOTS, { day: dayOfWeek, eventName, date: formattedDate, - // day:"MON", - // eventName:"gagan", - // date:"2024-05-26" }); - // Get the available slots and booked slots const { availability, bookedslots } = response; - console.log('Availability:', availability); - console.log('Booked Slots:', bookedslots); setAvailableSlots(availability); setBookedSlots(bookedslots); } catch (error) { @@ -71,40 +54,39 @@ function CalendarPage() { useEffect(() => { const fetchEvents = async () => { - try { - const response = await graphqlClient.request(GET_Duration, {eventName}); - // console.log(typeof(response.kalenview_create_events[0].duration)); - setDuration(response.kalenview_create_events[0].duration); - } catch (error){ - console.error('Failed to fetch Duration:', error); - } + try { + const response = await graphqlClient.request(GET_Duration, { eventName }); + setDuration(response.kalenview_create_events[0].duration); + } catch (error) { + console.error('Failed to fetch Duration:', error); + } }; - + fetchEvents(); - }, []) + }, []); useEffect(() => { const fetchEvents = async () => { - try { - const response = await graphqlClient.request(OrganizerName); - // console.log(response.kalenview[0]); - setOrganizerName(response.kalenview[0]); - } catch (error){ - console.error('Failed to fetch Organizer Name:', error); - } + try { + const response = await graphqlClient.request(OrganizerName); + setOrganizerName(response.kalenview[0]); + } catch (error) { + console.error('Failed to fetch Organizer Name:', error); + } }; - - fetchEvents(); - }, []) - // const theDuration = duration; - // console.log(`Duration is ${theDuration}`); + fetchEvents(); + }, []); - const handleSlotClick = (startTime, endTime) => { + const handleSlotClick = (startTime, endTime, index) => { const [year, month, day] = selectedDate.split('-'); - const formattedDisplayDate = `${day}-${month}-${year}`; // Format for display: DD-MM-YYYY + const formattedDisplayDate = `${day}-${month}-${year}`; const slotDetails = `${startTime}-${endTime}-${selectedDayOfWeek}-${formattedDisplayDate}`; - navigate(`/book/${eventName}/${slotDetails}`); + setClickedSlots((prev) => ({ ...prev, [index]: !prev[index] })); + + if (clickedSlots[index]) { + navigate(`/book/${eventName}/${slotDetails}`); + } }; const generateTimeSlots = (slot) => { @@ -152,12 +134,11 @@ function CalendarPage() {{eventName}
{organizerName.first_name +" "+ organizerName.last_name}
+{organizerName.first_name + " " + organizerName.last_name}
{duration}
-{eventName}
-//Duration
+//{organizerName.first_name +" "+ organizerName.last_name}
+//{duration}
+// //Thank you for booking. You will receive a confirmation email shortly.
+ +