diff --git a/components/modal/SetTimesModal.tsx b/components/modal/SetTimesModal.tsx deleted file mode 100644 index 55a48b56..00000000 --- a/components/modal/SetTimesModal.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import {ClockIcon} from "@heroicons/react/outline"; -import {useRef} from "react"; - -export default function SetTimesModal(props) { - - const isNew = props.isNew || false; - const {startDate, endDate} = props.schedule; - - const startHoursRef = useRef(); - const startMinsRef = useRef(); - const endHoursRef = useRef(); - const endMinsRef = useRef(); - - function updateStartEndTimesHandler(event) { - event.preventDefault(); - - const enteredStartHours = parseInt(startHoursRef.current.value); - const enteredStartMins = parseInt(startMinsRef.current.value); - const enteredEndHours = parseInt(endHoursRef.current.value); - const enteredEndMins = parseInt(endMinsRef.current.value); - - props.onChange({ - startDate: startDate.minute(enteredStartMins).hour(enteredStartHours), - endDate: endDate.minute(enteredEndMins).hour(enteredEndHours), - }); - - props.onExit(0); - } - - return ( -
-
- - - - -
-
-
- -
-
- -
-

- Set your work schedule -

-
-
-
-
-
- -
- - -
- : -
- - -
-
-
- -
- - -
- : -
- - -
-
-
- - -
-
-
-
-
); -} \ No newline at end of file diff --git a/components/ui/Scheduler.tsx b/components/ui/Scheduler.tsx index 7e5f73a4..9b367d62 100644 --- a/components/ui/Scheduler.tsx +++ b/components/ui/Scheduler.tsx @@ -1,8 +1,8 @@ import React, {useEffect, useState} from "react"; import TimezoneSelect from "react-timezone-select"; import {PencilAltIcon, TrashIcon} from "@heroicons/react/outline"; -import {WeekdaySelect} from "./WeekdaySelect"; -import SetTimesModal from "../modal/SetTimesModal"; +import {WeekdaySelect, Weekday} from "./WeekdaySelect"; +import SetTimesModal from "./modal/SetTimesModal"; import Schedule from '../../lib/schedule.model'; import dayjs, {Dayjs} from "dayjs"; import utc from 'dayjs/plugin/utc'; @@ -12,44 +12,45 @@ dayjs.extend(timezone); export const Scheduler = (props) => { - const [ showSetTimesModal, setShowSetTimesModal ]: boolean = useState(false); - const [ schedules, setSchedules ]: Schedule[] = useState( - props.schedules.map( (schedule, idx) => ({ - startDate: dayjs(schedule.startDate), - endDate: dayjs(schedule.startDate).startOf('day').add(schedule.length, 'minutes'), - key: idx - }) ) - ); + const [ schedules, setSchedules ]: Schedule[] = useState(props.schedules.map( schedule => { + const startDate = schedule.isOverride ? dayjs(schedule.startDate) : dayjs.utc().startOf('day').add(schedule.startTime, 'minutes') + return ( + { + days: schedule.days, + startDate, + endDate: startDate.add(schedule.length, 'minutes') + } + ) + })); const [ timeZone, setTimeZone ] = useState(props.timeZone); - const [ selectedSchedule, setSelectedSchedule ]: Schedule | null = useState(null); + const [ editSchedule, setEditSchedule ] = useState(-1); - const addNewSchedule = () => { - setSelectedSchedule({ - startDate: dayjs().startOf('day').add(0, 'minutes'), - endDate: dayjs().startOf('day').add(1439, 'minutes'), - }); - setShowSetTimesModal(true); + useEffect( () => { + props.onChange(schedules); + }, [schedules]) + + const addNewSchedule = () => setEditSchedule(schedules.length); + + const applyEditSchedule = (changed: Schedule) => { + const replaceWith = { + ...schedules[editSchedule], + ...changed + }; + schedules.splice(editSchedule, 1, replaceWith); + setSchedules([].concat(schedules)); } - const upsertSchedule = (changed: Schedule) => { - if (changed.key) { - schedules.splice( - schedules.findIndex( (schedule) => changed.key === schedule.key ), 1, changed - ) - setSchedules([].concat(schedules)); // update - } - else { - console.log(changed); - setSchedules(schedules.concat([changed])); // insert - } - } - - const removeSchedule = (toRemove: Schedule) => { - schedules.splice(schedules.findIndex( (schedule) => schedule.key === toRemove.key ), 1); + const removeScheduleAt = (toRemove: number) => { + schedules.splice(toRemove, 1); setSchedules([].concat(schedules)); }; + const setWeekdays = (idx: number, days: number[]) => { + schedules[idx].days = days; + setSchedules([].concat(schedules)); + } + return (
@@ -63,15 +64,15 @@ export const Scheduler = (props) => {
    - {schedules.length > 0 && schedules.map( (schedule) => -
  • + {schedules.map( (schedule, idx) => +
  • - -
    - @@ -88,10 +89,10 @@ export const Scheduler = (props) => { */} - {showSetTimesModal && - setShowSetTimesModal(false)} /> + {editSchedule >= 0 && + setEditSchedule(-1)} /> } {/*{showDateOverrideModal && diff --git a/components/ui/WeekdaySelect.tsx b/components/ui/WeekdaySelect.tsx index 49e8d9a8..6eed8563 100644 --- a/components/ui/WeekdaySelect.tsx +++ b/components/ui/WeekdaySelect.tsx @@ -1,14 +1,17 @@ -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; export const WeekdaySelect = (props) => { - const [ activeDays, setActiveDays ] = useState([false, true, true, true, true, true, false]); + const [ activeDays, setActiveDays ] = useState([1,2,3,4,5,6,7].map( (v) => (props.defaultValue || []).indexOf(v) !== -1)); const days = [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ]; + useEffect( () => { + props.onSelect(activeDays.map( (isActive, idx) => isActive ? idx + 1 : 0).filter( (v) => 0 !== v )); + }, [activeDays]); + const toggleDay = (e, idx: number) => { e.preventDefault(); activeDays[idx] = !activeDays[idx]; - console.log(activeDays); setActiveDays([].concat(activeDays)); } diff --git a/components/modal/DateOverrideModal.tsx b/components/ui/modal/DateOverrideModal.tsx similarity index 100% rename from components/modal/DateOverrideModal.tsx rename to components/ui/modal/DateOverrideModal.tsx diff --git a/components/ui/modal/SetTimesModal.tsx b/components/ui/modal/SetTimesModal.tsx new file mode 100644 index 00000000..2a0c1b40 --- /dev/null +++ b/components/ui/modal/SetTimesModal.tsx @@ -0,0 +1,101 @@ +import {ClockIcon} from "@heroicons/react/outline"; +import {useRef} from "react"; +import dayjs from "dayjs"; + +export default function SetTimesModal(props) { + + const {startDate, endDate} = props.schedule || { + startDate: dayjs().startOf('day').add(540, 'minutes'), + endDate: dayjs().startOf('day').add(1020, 'minutes'), + }; + + const startHoursRef = useRef(); + const startMinsRef = useRef(); + const endHoursRef = useRef(); + const endMinsRef = useRef(); + + function updateStartEndTimesHandler(event) { + event.preventDefault(); + + const enteredStartHours = parseInt(startHoursRef.current.value); + const enteredStartMins = parseInt(startMinsRef.current.value); + const enteredEndHours = parseInt(endHoursRef.current.value); + const enteredEndMins = parseInt(endMinsRef.current.value); + + props.onChange({ + startDate: startDate.minute(enteredStartMins).hour(enteredStartHours), + endDate: endDate.minute(enteredEndMins).hour(enteredEndHours), + }); + + props.onExit(0); + } + + return ( +
    +
    + + + + +
    +
    +
    + +
    +
    + +
    +

    + Set your work schedule +

    +
    +
    +
    +
    + +
    + + +
    + : +
    + + +
    +
    +
    + +
    + + +
    + : +
    + + +
    +
    +
    + + +
    +
    +
    +
    ); +} \ No newline at end of file diff --git a/lib/schedule.model.tsx b/lib/schedule.model.tsx index 03b3e444..7a5f6354 100644 --- a/lib/schedule.model.tsx +++ b/lib/schedule.model.tsx @@ -1,7 +1,7 @@ import {Dayjs} from "dayjs"; export default interface Schedule { - key: number; + id: number | null; startDate: Dayjs; endDate: Dayjs; } \ No newline at end of file diff --git a/pages/api/availability/schedule.ts b/pages/api/availability/schedule.ts deleted file mode 100644 index e30b9fff..00000000 --- a/pages/api/availability/schedule.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { getSession } from 'next-auth/client'; -import prisma from '../../../lib/prisma'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const session = await getSession({req: req}); - - if (!session) { - res.status(401).json({message: "Not authenticated"}); - return; - } - - PUT /api/availability/schedule/{id}/timezone - { - "timeZone": "Europe/London" - } - - PATCH /api/availability/schedule { - "schedules": [ - { - - } - ], - "overrides": { - - } - } - - if (req.method == "PATCH") { - const startMins = req.body.start; - const endMins = req.body.end; - - const updateDay = await prisma.schedule.update({ - where: { - id: session.user.id, - }, - data: { - startTime: startMins, - endTime: endMins - }, - }); - - res.status(200).json({message: 'Start and end times updated successfully'}); - } -} \ No newline at end of file diff --git a/pages/api/availability/schedule/[eventtype].ts b/pages/api/availability/schedule/[eventtype].ts new file mode 100644 index 00000000..9d3a9eb6 --- /dev/null +++ b/pages/api/availability/schedule/[eventtype].ts @@ -0,0 +1,69 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { getSession } from 'next-auth/client'; +import prisma from '../../../../lib/prisma'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + + const session = await getSession({req}); + if (!session) { + res.status(401).json({message: "Not authenticated"}); + return; + } + + if (req.method == "PUT") { + + const openingHours = req.body.openingHours || []; + const overrides = req.body.overrides || []; + + const removeSchedule = await prisma.schedule.deleteMany({ + where: { + eventTypeId: +req.query.eventtype, + } + }) + + const updateSchedule = Promise.all(openingHours.map( (schedule) => prisma.schedule.create({ + data: { + eventTypeId: +req.query.eventtype, + days: schedule.days, + startTime: schedule.startTime, + length: schedule.endTime - schedule.startTime, + }, + }))) + .catch( (error) => { + console.log(error); + }) + } + + res.status(200).json({message: 'Created schedule'}); + + /*if (req.method == "PATCH") { + const openingHours = req.body.openingHours || []; + const overrides = req.body.overrides || []; + + openingHours.forEach( (schedule) => { + const updateSchedule = await prisma.schedule.update({ + where: { + id: req.body.id, + }, + data: { + eventTypeId: req.query.eventtype, + days: req.body.days, + startTime: 333, + endTime: 540 - req.body.startTime, + }, + }); + }); + + overrides.forEach( (schedule) => { + const updateSchedule = await prisma.schedule.update({ + where: { + id: req.body.id, + }, + data: { + eventTypeId: req.query.eventtype, + startDate: req.body.startDate, + length: 540, + }, + }); + });*/ +} diff --git a/pages/api/availability/schedule/[type].ts b/pages/api/availability/schedule/[type].ts deleted file mode 100644 index e69de29b..00000000 diff --git a/pages/api/availability/schedule/[type]/timezone.ts b/pages/api/availability/schedule/[type]/timezone.ts deleted file mode 100644 index 1274c828..00000000 --- a/pages/api/availability/schedule/[type]/timezone.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import prisma from '../../../lib/prisma'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - const { user } = req.query - - const schedules = await prisma.schedule.find({ - where: { - eventTypeId: req.query.type, - }, - select: { - credentials: true, - timeZone: true - } - }); - - return res.status(202).send(null); -} diff --git a/pages/availability/event/[type].tsx b/pages/availability/event/[type].tsx index f0881adc..acb86ef0 100644 --- a/pages/availability/event/[type].tsx +++ b/pages/availability/event/[type].tsx @@ -29,6 +29,7 @@ export default function EventType(props) { const [ showLocationModal, setShowLocationModal ] = useState(false); const [ selectedLocation, setSelectedLocation ] = useState(undefined); const [ locations, setLocations ] = useState(props.eventType.locations || []); + const [ schedule, setSchedule ] = useState(undefined); const titleRef = useRef(); const slugRef = useRef(); @@ -40,8 +41,6 @@ export default function EventType(props) { return

    Loading...

    ; } - console.log(props); - async function updateEventTypeHandler(event) { event.preventDefault(); @@ -60,6 +59,31 @@ export default function EventType(props) { } }); + if (schedule) { + + let schedulePayload = { "overrides": [], "timeZone": props.user.timeZone, "openingHours": [] }; + schedule.forEach( (item) => { + if (item.isOverride) { + delete item.isOverride; + schedulePayload.overrides.push(item); + } else { + schedulePayload.openingHours.push({ + days: item.days, + startTime: item.startDate.hour() * 60 + item.startDate.minute(), + endTime: item.endDate.hour() * 60 + item.endDate.minute() + }); + } + }); + + const response = await fetch('/api/availability/schedule/' + props.eventType.id, { + method: 'PUT', + body: JSON.stringify(schedulePayload), + headers: { + 'Content-Type': 'application/json' + } + }); + } + router.push('/availability'); } @@ -262,16 +286,16 @@ export default function EventType(props) { - -
    -
    -

    How do you want to offer your availability for this event type?

    - -
    - Cancel - +
    +
    +

    How do you want to offer your availability for this event type?

    + +
    + Cancel + +
    -
    +
    @@ -340,52 +364,63 @@ export default function EventType(props) { } export async function getServerSideProps(context) { - const session = await getSession(context); - if (!session) { - return { redirect: { permanent: false, destination: '/auth/login' } }; + const session = await getSession(context); + if (!session) { + return { redirect: { permanent: false, destination: '/auth/login' } }; + } + const user = await prisma.user.findFirst({ + where: { + email: session.user.email, + }, + select: { + username: true, + timeZone: true, + startTime: true, + endTime: true, } - const user = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - select: { - username: true, - timeZone: true, - startTime: true, - endTime: true, - } - }); + }); - const eventType = await prisma.eventType.findUnique({ - where: { - id: parseInt(context.query.type), - }, - select: { - id: true, - title: true, - slug: true, - description: true, - length: true, - hidden: true, - locations: true, - } - }); + const eventType = await prisma.eventType.findUnique({ + where: { + id: parseInt(context.query.type), + }, + select: { + id: true, + title: true, + slug: true, + description: true, + length: true, + hidden: true, + locations: true, + } + }); - const utcOffset = dayjs().tz(user.timeZone).utcOffset(); + let schedules = await prisma.schedule.findMany({ + where: { + eventTypeId: parseInt(context.query.type), + }, + }); - const schedules = [ - { - key: 0, - startDate: dayjs.utc().startOf('day').add(user.startTime - utcOffset, 'minutes').format(), - length: user.endTime, - } - ]; - - return { - props: { - user, - eventType, - schedules + if (!schedules.length) { + schedules = await prisma.schedule.findMany({ + where: { + userId: user.id, }, + }); + if (!schedules.length) { + schedules.push({ + days: [ 1, 2, 3, 4, 5, 6, 7 ], + startTime: user.startTime, + length: user.endTime >= 1440 ? 1439 : user.endTime, + }); } + } + + return { + props: { + user, + eventType, + schedules + }, + } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index da60fe97..57f4a90a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,7 +21,7 @@ model EventType { user User? @relation(fields: [userId], references: [id]) userId Int? bookings Booking[] - availability Interval[] + availability Schedule[] } model Credential { @@ -50,7 +50,7 @@ model User { credentials Credential[] teams Membership[] bookings Booking[] - availability Interval[] + availability Schedule[] @@map(name: "users") } @@ -124,14 +124,16 @@ model Booking { updatedAt DateTime? } -model Interval { - id Int @default(autoincrement()) @id +model Schedule { + id Int @default(autoincrement()) @id label String? - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id]) userId Int? - eventType EventType? @relation(fields: [eventTypeId], references: [id]) + eventType EventType? @relation(fields: [eventTypeId], references: [id]) eventTypeId Int? - startTime DateTime + days Int[] + startTime Int? + startDate DateTime? @db.Timestamptz(3) length Int - isOverride Boolean @default(false) + isOverride Boolean @default(false) }