Save WIP
This commit is contained in:
parent
49cb191254
commit
03f583b021
15 changed files with 441 additions and 11 deletions
|
@ -28,6 +28,8 @@ tags:
|
||||||
description: Manage integrations
|
description: Manage integrations
|
||||||
- name: User
|
- name: User
|
||||||
description: Manage the user's profile and settings
|
description: Manage the user's profile and settings
|
||||||
|
- name: Team
|
||||||
|
description: Group users into teams
|
||||||
paths:
|
paths:
|
||||||
/api/auth/signin:
|
/api/auth/signin:
|
||||||
get:
|
get:
|
||||||
|
@ -144,4 +146,9 @@ paths:
|
||||||
description: Updates a user's profile.
|
description: Updates a user's profile.
|
||||||
summary: Updates a user's profile
|
summary: Updates a user's profile
|
||||||
tags:
|
tags:
|
||||||
- User
|
- User
|
||||||
|
/api/availability/schedule:
|
||||||
|
path:
|
||||||
|
description: "Updates a schedule"
|
||||||
|
tags:
|
||||||
|
- Availability
|
6
components/Schedule.model.tsx
Normal file
6
components/Schedule.model.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import {Dayjs} from "dayjs";
|
||||||
|
|
||||||
|
interface Schedule {
|
||||||
|
startDate: Dayjs;
|
||||||
|
endDate: Dayjs;
|
||||||
|
}
|
7
components/modal/DateOverrideModal.tsx
Normal file
7
components/modal/DateOverrideModal.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
/*export default function DateOverrideModal(props) {
|
||||||
|
return (
|
||||||
|
|
||||||
|
);
|
||||||
|
}*/
|
100
components/modal/SetTimesModal.tsx
Normal file
100
components/modal/SetTimesModal.tsx
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
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<HTMLInputElement>();
|
||||||
|
const startMinsRef = useRef<HTMLInputElement>();
|
||||||
|
const endHoursRef = useRef<HTMLInputElement>();
|
||||||
|
const endMinsRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||||
|
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
|
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
||||||
|
<div className="sm:flex sm:items-start mb-4">
|
||||||
|
<div
|
||||||
|
className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
|
<ClockIcon className="h-6 w-6 text-blue-600"/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||||
|
Change when you are available for bookings
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Set your work schedule
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={updateStartEndTimesHandler} noValidate>
|
||||||
|
<div className="flex mb-4">
|
||||||
|
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="startHours" className="sr-only">Hours</label>
|
||||||
|
<input ref={startHoursRef} type="number" min="0" max="23" maxLength="2" name="hours" id="startHours"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
|
placeholder="9" defaultValue={startDate.format('H')} />
|
||||||
|
</div>
|
||||||
|
<span className="mx-2 pt-1">:</span>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="startMinutes" className="sr-only">Minutes</label>
|
||||||
|
<input ref={startMinsRef} type="number" min="0" max="59" step="15" maxLength="2" name="minutes" id="startMinutes"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
|
placeholder="30" defaultValue={startDate.format('m')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="endHours" className="sr-only">Hours</label>
|
||||||
|
<input ref={endHoursRef} type="number" min="0" max="23" maxLength="2" name="hours" id="endHours"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
|
placeholder="17" defaultValue={endDate.format('H')} />
|
||||||
|
</div>
|
||||||
|
<span className="mx-2 pt-1">:</span>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="endMinutes" className="sr-only">Minutes</label>
|
||||||
|
<input ref={endMinsRef} type="number" min="0" max="59" maxLength="2" step="15" name="minutes" id="endMinutes"
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
|
placeholder="30" defaultValue={endDate.format('m')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||||
|
<button type="submit" className="btn btn-primary">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button onClick={props.onExit} type="button" className="btn btn-white mr-2">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
101
components/ui/Scheduler.tsx
Normal file
101
components/ui/Scheduler.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
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 Schedule from '../../lib/schedule.model';
|
||||||
|
import dayjs, {Dayjs} from "dayjs";
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
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 [ timeZone, setTimeZone ] = useState(props.timeZone);
|
||||||
|
const [ selectedSchedule, setSelectedSchedule ]: Schedule | null = useState(null);
|
||||||
|
|
||||||
|
const addNewSchedule = () => {
|
||||||
|
setSelectedSchedule({
|
||||||
|
startDate: dayjs().startOf('day').add(0, 'minutes'),
|
||||||
|
endDate: dayjs().startOf('day').add(1439, 'minutes'),
|
||||||
|
});
|
||||||
|
setShowSetTimesModal(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
setSchedules([].concat(schedules));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="rounded border flex">
|
||||||
|
<div className="w-3/5">
|
||||||
|
<div className="w-3/4 p-2">
|
||||||
|
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||||
|
Timezone
|
||||||
|
</label>
|
||||||
|
<div className="mt-1">
|
||||||
|
<TimezoneSelect id="timeZone" value={timeZone} onChange={setTimeZone} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{schedules.length > 0 && schedules.map( (schedule) =>
|
||||||
|
<li key={schedule.key} className="py-2 flex justify-between border-t">
|
||||||
|
<div className="inline-flex ml-2">
|
||||||
|
<WeekdaySelect />
|
||||||
|
<button className="ml-2 text-sm px-2" type="button" onClick={() => { setSelectedSchedule(schedule); setShowSetTimesModal(true) }}>
|
||||||
|
{schedule.startDate.format(schedule.startDate.minute() === 0 ? 'ha' : 'h:mma')} until {schedule.endDate.format(schedule.endDate.minute() === 0 ? 'ha' : 'h:mma')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={() => removeSchedule(schedule)}
|
||||||
|
className="btn-sm bg-transparent px-2 py-1 ml-1">
|
||||||
|
<TrashIcon className="h-6 w-6 inline text-gray-400 -mt-1" />
|
||||||
|
</button>
|
||||||
|
</li>)}
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm m-2">Add another</button>
|
||||||
|
</div>
|
||||||
|
<div className="border-l p-2 w-2/5 text-sm bg-gray-50">
|
||||||
|
{/*<p className="font-bold mb-2">Add date overrides</p>
|
||||||
|
<p className="mb-2">
|
||||||
|
Add dates when your availability changes from your weekly hours
|
||||||
|
</p>
|
||||||
|
<button className="btn-sm btn-white">Add a date override</button>*/}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{showSetTimesModal &&
|
||||||
|
<SetTimesModal schedule={selectedSchedule}
|
||||||
|
onChange={upsertSchedule}
|
||||||
|
onExit={() => setShowSetTimesModal(false)} />
|
||||||
|
}
|
||||||
|
{/*{showDateOverrideModal &&
|
||||||
|
<DateOverrideModal />
|
||||||
|
}*/}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
39
components/ui/WeekdaySelect.tsx
Normal file
39
components/ui/WeekdaySelect.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import React, {useState} from "react";
|
||||||
|
|
||||||
|
export const WeekdaySelect = (props) => {
|
||||||
|
|
||||||
|
const [ activeDays, setActiveDays ] = useState([false, true, true, true, true, true, false]);
|
||||||
|
const days = [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ];
|
||||||
|
|
||||||
|
const toggleDay = (e, idx: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
activeDays[idx] = !activeDays[idx];
|
||||||
|
console.log(activeDays);
|
||||||
|
setActiveDays([].concat(activeDays));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="weekdaySelect">
|
||||||
|
<div className="inline-flex">
|
||||||
|
{days.map( (day, idx) => activeDays[idx] ?
|
||||||
|
<button key={idx} onClick={(e) => toggleDay(e, idx)}
|
||||||
|
style={ {"marginLeft": "-2px"} }
|
||||||
|
className={`
|
||||||
|
active focus:outline-none border-2 border-blue-500 px-2 py-1 rounded
|
||||||
|
${activeDays[idx+1] ? 'rounded-r-none': ''}
|
||||||
|
${activeDays[idx-1] ? 'rounded-l-none': ''}
|
||||||
|
${idx === 0 ? 'rounded-l' : ''}
|
||||||
|
${idx === days.length-1 ? 'rounded-r' : ''}
|
||||||
|
`}>
|
||||||
|
{day}
|
||||||
|
</button>
|
||||||
|
:
|
||||||
|
<button key={idx} onClick={(e) => toggleDay(e, idx)}
|
||||||
|
style={ {"marginTop": "1px", "marginBottom": "1px"} }
|
||||||
|
className={`border focus:outline-none px-2 py-1 rounded-none ${idx === 0 ? 'rounded-l' : 'border-l-0'} ${idx === days.length-1 ? 'rounded-r' : ''}`}>
|
||||||
|
{day}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
7
lib/schedule.model.tsx
Normal file
7
lib/schedule.model.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import {Dayjs} from "dayjs";
|
||||||
|
|
||||||
|
export default interface Schedule {
|
||||||
|
key: number;
|
||||||
|
startDate: Dayjs;
|
||||||
|
endDate: Dayjs;
|
||||||
|
}
|
45
pages/api/availability/schedule.ts
Normal file
45
pages/api/availability/schedule.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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'});
|
||||||
|
}
|
||||||
|
}
|
0
pages/api/availability/schedule/[type].ts
Normal file
0
pages/api/availability/schedule/[type].ts
Normal file
18
pages/api/availability/schedule/[type]/timezone.ts
Normal file
18
pages/api/availability/schedule/[type]/timezone.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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);
|
||||||
|
}
|
30
pages/api/availability/week.ts
Normal file
30
pages/api/availability/week.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method == "PATCH") {
|
||||||
|
|
||||||
|
const startMins = req.body.start;
|
||||||
|
const endMins = req.body.end;
|
||||||
|
|
||||||
|
const updateWeek = 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'});
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import prisma from '../../../lib/prisma';
|
||||||
import { LocationType } from '../../../lib/location';
|
import { LocationType } from '../../../lib/location';
|
||||||
import Shell from '../../../components/Shell';
|
import Shell from '../../../components/Shell';
|
||||||
import { useSession, getSession } from 'next-auth/client';
|
import { useSession, getSession } from 'next-auth/client';
|
||||||
|
import {Scheduler} from "../../../components/ui/Scheduler";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LocationMarkerIcon,
|
LocationMarkerIcon,
|
||||||
PlusCircleIcon,
|
PlusCircleIcon,
|
||||||
|
@ -14,6 +16,12 @@ import {
|
||||||
PhoneIcon,
|
PhoneIcon,
|
||||||
} from '@heroicons/react/outline';
|
} from '@heroicons/react/outline';
|
||||||
|
|
||||||
|
import dayjs, {Dayjs} from "dayjs";
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
export default function EventType(props) {
|
export default function EventType(props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -32,6 +40,8 @@ export default function EventType(props) {
|
||||||
return <p className="text-gray-400">Loading...</p>;
|
return <p className="text-gray-400">Loading...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(props);
|
||||||
|
|
||||||
async function updateEventTypeHandler(event) {
|
async function updateEventTypeHandler(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -142,7 +152,7 @@ export default function EventType(props) {
|
||||||
<Shell heading={'Event Type - ' + props.eventType.title}>
|
<Shell heading={'Event Type - ' + props.eventType.title}>
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div className="col-span-3 sm:col-span-2">
|
<div className="col-span-3 sm:col-span-2">
|
||||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
<div className="bg-white overflow-hidden shadow rounded-lg mb-4">
|
||||||
<div className="px-4 py-5 sm:p-6">
|
<div className="px-4 py-5 sm:p-6">
|
||||||
<form onSubmit={updateEventTypeHandler}>
|
<form onSubmit={updateEventTypeHandler}>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
|
@ -232,7 +242,7 @@ export default function EventType(props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="my-8">
|
<div className="my-6 mb-4">
|
||||||
<div className="relative flex items-start">
|
<div className="relative flex items-start">
|
||||||
<div className="flex items-center h-5">
|
<div className="flex items-center h-5">
|
||||||
<input
|
<input
|
||||||
|
@ -252,9 +262,16 @@ export default function EventType(props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn btn-primary">Update</button>
|
|
||||||
<Link href="/availability"><a className="ml-2 btn btn-white">Cancel</a></Link>
|
|
||||||
</form>
|
</form>
|
||||||
|
<hr className="my-4"/>
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-2">How do you want to offer your availability for this event type?</h3>
|
||||||
|
<Scheduler timeZone={props.user.timeZone} schedules={props.schedules} />
|
||||||
|
<div className="py-4 flex justify-end">
|
||||||
|
<Link href="/availability"><a className="mr-2 btn btn-white">Cancel</a></Link>
|
||||||
|
<button type="submit" className="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -332,7 +349,10 @@ export async function getServerSideProps(context) {
|
||||||
email: session.user.email,
|
email: session.user.email,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
username: true
|
username: true,
|
||||||
|
timeZone: true,
|
||||||
|
startTime: true,
|
||||||
|
endTime: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -351,10 +371,21 @@ export async function getServerSideProps(context) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const utcOffset = dayjs().tz(user.timeZone).utcOffset();
|
||||||
|
|
||||||
|
const schedules = [
|
||||||
|
{
|
||||||
|
key: 0,
|
||||||
|
startDate: dayjs.utc().startOf('day').add(user.startTime - utcOffset, 'minutes').format(),
|
||||||
|
length: user.endTime,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
user,
|
user,
|
||||||
eventType
|
eventType,
|
||||||
},
|
schedules
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ model EventType {
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
userId Int?
|
userId Int?
|
||||||
bookings Booking[]
|
bookings Booking[]
|
||||||
|
availability Interval[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Credential {
|
model Credential {
|
||||||
|
@ -49,6 +50,8 @@ model User {
|
||||||
credentials Credential[]
|
credentials Credential[]
|
||||||
teams Membership[]
|
teams Membership[]
|
||||||
bookings Booking[]
|
bookings Booking[]
|
||||||
|
availability Interval[]
|
||||||
|
|
||||||
@@map(name: "users")
|
@@map(name: "users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,4 +122,16 @@ model Booking {
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime?
|
updatedAt DateTime?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Interval {
|
||||||
|
id Int @default(autoincrement()) @id
|
||||||
|
label String?
|
||||||
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
userId Int?
|
||||||
|
eventType EventType? @relation(fields: [eventTypeId], references: [id])
|
||||||
|
eventTypeId Int?
|
||||||
|
startTime DateTime
|
||||||
|
length Int
|
||||||
|
isOverride Boolean @default(false)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
table tbody tr:nth-child(odd) {
|
table tbody tr:nth-child(odd) {
|
||||||
@apply bg-gray-50;
|
@apply bg-gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-odd > *:nth-child(odd) {
|
||||||
|
@apply bg-gray-50;
|
||||||
}
|
}
|
|
@ -24,4 +24,24 @@ body {
|
||||||
|
|
||||||
#timeZone input:focus {
|
#timeZone input:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdaySelect {
|
||||||
|
font-family: "Courier New", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdaySelect button.active:first-child {
|
||||||
|
margin-left: -1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdaySelect button:not(.active) {
|
||||||
|
padding-left: calc(0.5rem + 0px);
|
||||||
|
margin-right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekdaySelect button.active + button.active {
|
||||||
|
border-color: rgba(3, 169, 244, var(--tw-border-opacity))
|
||||||
|
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||||
|
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||||
|
white;
|
||||||
}
|
}
|
Loading…
Reference in a new issue