mirror of
https://github.com/mvvasilev/findtheti.me.git
synced 2025-04-18 21:19:52 +03:00
First try at availability picker
This commit is contained in:
parent
a665c5e59d
commit
0dcf39b48a
18 changed files with 1038 additions and 58 deletions
|
@ -13,8 +13,11 @@
|
|||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/material": "^5.15.3",
|
||||
"@mui/x-date-pickers": "^6.18.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-github-corner": "^2.5.0",
|
||||
"react-router-dom": "^6.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Bungee+Spice&display=swap');
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Old versions of Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently
|
||||
supported by Chrome, Edge, Opera and Firefox */
|
||||
}
|
||||
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
|
|
308
frontend/src/components/AvailabilityPicker.tsx
Normal file
308
frontend/src/components/AvailabilityPicker.tsx
Normal file
|
@ -0,0 +1,308 @@
|
|||
import { Box, Card, Divider, Stack, Typography } from "@mui/material";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as utc from 'dayjs/plugin/utc';
|
||||
import * as timezone from 'dayjs/plugin/timezone';
|
||||
import * as localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import utils from "../utils";
|
||||
import { EventTypes } from "../types/Event";
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
type AvailabilityTime = {
|
||||
fromTime: Dayjs,
|
||||
toTime: Dayjs
|
||||
}
|
||||
|
||||
type AvailabilityDay = {
|
||||
forDate: Dayjs,
|
||||
availableTimes: AvailabilityTime[]
|
||||
}
|
||||
|
||||
const HALFHOUR_DISPLAY_HEIGHT: number = 15;
|
||||
|
||||
const DAY_DISPLAY_WIDTH: String = "150px";
|
||||
|
||||
export default function AvailabilityPicker(props: {
|
||||
fromDate: Dayjs,
|
||||
toDate: Dayjs,
|
||||
eventType: String,
|
||||
availabilityDurationInMinutes: number
|
||||
}) {
|
||||
const [days, setDays] = useState<AvailabilityDay[]>([]);
|
||||
const [selectingAvailabilityForDay, setAvailabilityDayBeingSelectedFor] = useState<AvailabilityDay | null>(null);
|
||||
const [currentAvailabilityTime, setAvailabilityTime] = useState<AvailabilityTime | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let localTimezone = dayjs.tz.guess();
|
||||
|
||||
let localFromDate = props.fromDate.tz(localTimezone);
|
||||
let localToDate = props.toDate.tz(localTimezone);
|
||||
|
||||
switch (props.eventType) {
|
||||
case EventTypes.SPECIFIC_DATE: {
|
||||
createAvailabilitiesBasedOnInitialDate(localFromDate, 1);
|
||||
break;
|
||||
}
|
||||
case EventTypes.DATE_RANGE: {
|
||||
createAvailabilitiesBasedOnInitialDate(localFromDate, Math.abs(localFromDate.diff(localToDate, "day", false)));
|
||||
break;
|
||||
}
|
||||
case EventTypes.DAY: {
|
||||
createAvailabilitiesBasedOnUnspecifiedInitialDate(1, localTimezone);
|
||||
break;
|
||||
}
|
||||
case EventTypes.WEEK: {
|
||||
createAvailabilitiesBasedOnUnspecifiedInitialDate(7, localTimezone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(days)
|
||||
}, [days])
|
||||
|
||||
function createAvailabilitiesBasedOnUnspecifiedInitialDate(numberOfDays: number, tz: string) {
|
||||
createAvailabilitiesBasedOnInitialDate(dayjs.tz("1970-01-05 00:00:00", tz), numberOfDays);
|
||||
}
|
||||
|
||||
function createAvailabilitiesBasedOnInitialDate(date: Dayjs, numberOfDays: number) {
|
||||
let availabilities: AvailabilityDay[] = [];
|
||||
|
||||
for (var i: number = 0; i < numberOfDays; i++) {
|
||||
let availability: AvailabilityDay = {
|
||||
forDate: date.add(i, "day").startOf("day"),
|
||||
availableTimes: []
|
||||
}
|
||||
|
||||
availabilities.push(availability);
|
||||
}
|
||||
|
||||
setDays(availabilities);
|
||||
}
|
||||
|
||||
function clearAvailabilityTimeSelection() {
|
||||
setAvailabilityDayBeingSelectedFor(null);
|
||||
setAvailabilityTime(null);
|
||||
}
|
||||
|
||||
function beginAvailabilityTimeSelection(e: React.MouseEvent<HTMLDivElement, MouseEvent>, day: AvailabilityDay, startTime: Dayjs) {
|
||||
setAvailabilityDayBeingSelectedFor(day);
|
||||
setAvailabilityTime({
|
||||
fromTime: startTime,
|
||||
toTime: startTime
|
||||
});
|
||||
}
|
||||
|
||||
function finishAvailabilityTimeSelection(e: React.MouseEvent<HTMLDivElement, MouseEvent>, day: AvailabilityDay) {
|
||||
if (currentAvailabilityTime === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
day.availableTimes.push(currentAvailabilityTime);
|
||||
setDays([...days])
|
||||
|
||||
clearAvailabilityTimeSelection();
|
||||
}
|
||||
|
||||
function addTimeToAvailabilityTimeSelection(e: React.MouseEvent<HTMLDivElement, MouseEvent>, day: AvailabilityDay, time: Dayjs) {
|
||||
if (e.buttons !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentAvailabilityTime === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentAvailabilityTime !== null && selectingAvailabilityForDay !== null && Math.abs(selectingAvailabilityForDay.forDate.diff(time, "day")) >= 1) {
|
||||
clearAvailabilityTimeSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
let currentFrom = currentAvailabilityTime.fromTime;
|
||||
let currentTo = currentAvailabilityTime.toTime;
|
||||
|
||||
if (time.isBefore(currentFrom)) {
|
||||
setAvailabilityTime({
|
||||
fromTime: time,
|
||||
toTime: currentTo
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (time.isAfter(currentTo)) {
|
||||
setAvailabilityTime({
|
||||
fromTime: currentFrom,
|
||||
toTime: time
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function currentAvailabilityTimeSelectionIncludes(time: Dayjs): boolean {
|
||||
if (currentAvailabilityTime === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((time.isAfter(currentAvailabilityTime.fromTime) && time.isBefore(currentAvailabilityTime.toTime)) || (time.isSame(currentAvailabilityTime.toTime) || time.isSame(currentAvailabilityTime.fromTime))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isTimeIncludedInAnyAvailabilityPeriod(day: AvailabilityDay, time: Dayjs): boolean {
|
||||
return day.availableTimes.some(t => t.fromTime.isBefore(time) && t.toTime.isAfter(time));
|
||||
}
|
||||
|
||||
function isTimeBeginningOfAnyAvailabilityPeriod(day: AvailabilityDay, time: Dayjs): boolean {
|
||||
return day.availableTimes.some(t => t.fromTime.isSame(time));
|
||||
}
|
||||
|
||||
function isTimeEndingOfAnyAvailabilityPeriod(day: AvailabilityDay, time: Dayjs): boolean {
|
||||
return day.availableTimes.some(t => t.toTime.isSame(time));
|
||||
}
|
||||
|
||||
function generateDay(day: AvailabilityDay) {
|
||||
|
||||
const HOVER_COLOR: String = "#004455";
|
||||
const HOUR_LIGHT_COLOR: String = "#002233";
|
||||
const HOUR_DARK_COLOR: String = "#003344";
|
||||
const HOUR_BORDER_COLOR: String = "#777";
|
||||
const ACTIVE_COLOR: String = "#223300";
|
||||
const CURRENTLY_SELECTED_COLOR: String = "#112200";
|
||||
const HOUR_TEXT_COLOR: String = "#ddd";
|
||||
const HALFHOUR_BORDER_COLOR: String = "#333";
|
||||
|
||||
let hours = [...Array<String>(24)].map((_, i) => {
|
||||
let time = day.forDate.set("hour", i).set("minute", 0).set("second", 0);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={`${i}`}
|
||||
sx={{
|
||||
width: "100%",
|
||||
borderBottom: 1,
|
||||
borderColor: HOUR_BORDER_COLOR,
|
||||
bgcolor: (i % 2 == 0) ? HOUR_LIGHT_COLOR : HOUR_DARK_COLOR,
|
||||
":hover": {
|
||||
bgcolor: HOVER_COLOR
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: HALFHOUR_DISPLAY_HEIGHT,
|
||||
borderBottom: 1,
|
||||
borderColor: HALFHOUR_BORDER_COLOR,
|
||||
":active": {
|
||||
bgcolor: ACTIVE_COLOR
|
||||
},
|
||||
bgcolor: currentAvailabilityTimeSelectionIncludes(time) ? CURRENTLY_SELECTED_COLOR : "inherit"
|
||||
}}
|
||||
onMouseDown={(e) => beginAvailabilityTimeSelection(e, day, time)}
|
||||
onMouseUp={(e) => finishAvailabilityTimeSelection(e, day)}
|
||||
onMouseOver={(e) => addTimeToAvailabilityTimeSelection(e, day, time)}
|
||||
>
|
||||
<Typography
|
||||
className={"noselect"}
|
||||
textAlign={"left"}
|
||||
fontSize={"0.65em"}
|
||||
color={HOUR_TEXT_COLOR}
|
||||
>
|
||||
{ utils.formatTimeFromHourOfDay(i, 0) }
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: HALFHOUR_DISPLAY_HEIGHT,
|
||||
":active": {
|
||||
bgcolor: ACTIVE_COLOR
|
||||
},
|
||||
bgcolor: currentAvailabilityTimeSelectionIncludes(time.set("minute", 30)) ? CURRENTLY_SELECTED_COLOR : "inherit"
|
||||
}}
|
||||
onMouseDown={(e) => beginAvailabilityTimeSelection(e, day, time.set("minute", 30))}
|
||||
onMouseUp={(e) => finishAvailabilityTimeSelection(e, day)}
|
||||
onMouseOver={(e) => addTimeToAvailabilityTimeSelection(e, day, time.set("minute", 30))}
|
||||
>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
);
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack
|
||||
key={day.forDate.format()}
|
||||
direction="column"
|
||||
sx={{
|
||||
minWidth: DAY_DISPLAY_WIDTH,
|
||||
width: DAY_DISPLAY_WIDTH
|
||||
}}
|
||||
overflow={"visible"}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "fit-content",
|
||||
overflow: "visible"
|
||||
}}
|
||||
variant="outlined"
|
||||
onMouseLeave={(e) => clearAvailabilityTimeSelection()}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%" }}
|
||||
padding={1}
|
||||
>
|
||||
{
|
||||
(props.eventType === EventTypes.WEEK) &&
|
||||
<Typography>
|
||||
{ day.forDate.format("dddd") }
|
||||
</Typography>
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DAY) &&
|
||||
<Typography>
|
||||
Any Day
|
||||
</Typography>
|
||||
}
|
||||
{
|
||||
(props.eventType === EventTypes.DATE_RANGE || props.eventType === EventTypes.SPECIFIC_DATE) &&
|
||||
<Typography>
|
||||
{ day.forDate.format("LL") }
|
||||
</Typography>
|
||||
}
|
||||
</Box>
|
||||
<Divider></Divider>
|
||||
{hours}
|
||||
</Card>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
justifyContent={"safe center"}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
maxHeight: "500px",
|
||||
overflowY: "scroll",
|
||||
overflowX: "scroll"
|
||||
}}
|
||||
>
|
||||
{
|
||||
days.map(a => generateDay(a))
|
||||
}
|
||||
</Stack>
|
||||
);
|
||||
}
|
15
frontend/src/components/Footer.tsx
Normal file
15
frontend/src/components/Footer.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Stack, Typography } from '@mui/material';
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<Stack
|
||||
sx={{ height: "50px" }}
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Typography align="center">
|
||||
Created by <a href="https://mvvasilev.dev">mvvasilev</a> | <a href="https://github.com/mvvasilev/findtheti.me">Github</a>
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
30
frontend/src/components/Header.tsx
Normal file
30
frontend/src/components/Header.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Stack, Typography, useTheme } from '@mui/material';
|
||||
|
||||
export default function Header() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{ height: "100px" }}
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
>
|
||||
<a href={window.location.origin}>
|
||||
<Typography
|
||||
align="center"
|
||||
sx={{
|
||||
fontFamily: "'Bungee Spice', sans-serif",
|
||||
[theme.breakpoints.up("xs")]: {
|
||||
fontSize: "2em"
|
||||
},
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
fontSize: "4em"
|
||||
}
|
||||
}}
|
||||
>
|
||||
findtheti.me
|
||||
</Typography>
|
||||
</a>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -1,11 +1,82 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Event, createEvent } from '../types/Event';
|
||||
import Grid from '@mui/material/Unstable_Grid2'
|
||||
import { Button, TextField, Typography } from "@mui/material";
|
||||
import AvailabilityPicker from "../components/AvailabilityPicker";
|
||||
import dayjs from "dayjs";
|
||||
import utils from "../utils";
|
||||
|
||||
export default function ExistingEventPage() {
|
||||
let { pasteId } = useParams();
|
||||
let { eventId } = useParams();
|
||||
|
||||
console.log(pasteId);
|
||||
const [event, setEvent] = useState<Event>(createEvent());
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/events/${eventId}`)
|
||||
.then(resp => resp.json())
|
||||
.then(resp => setEvent({
|
||||
name: resp.result?.name,
|
||||
description: resp.result?.description,
|
||||
fromDate: dayjs.utc(resp.result?.from_date),
|
||||
toDate: dayjs.utc(resp.result?.to_date),
|
||||
eventType: resp.result?.event_type,
|
||||
snowflakeId: resp.result?.snowflake_id,
|
||||
duration: resp.result?.duration
|
||||
}));
|
||||
}, [eventId]);
|
||||
|
||||
return (
|
||||
<div />
|
||||
<Grid container sx={{ p: 2 }} spacing={1}>
|
||||
<Grid xs={12}>
|
||||
<Typography>You've been invited to...</Typography>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Typography variant="h4">{ event.name }</Typography>
|
||||
</Grid>
|
||||
{
|
||||
(event.description !== null) &&
|
||||
<Grid xs={12}>
|
||||
<Typography>{ event.description }</Typography>
|
||||
</Grid>
|
||||
}
|
||||
<Grid xs={12}>
|
||||
<Typography>
|
||||
This event lasts for { utils.formatMinutesAsHoursMinutes(event.duration) }. When will you be available to attend?
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
{
|
||||
(event.fromDate !== null && event.toDate !== null && event.eventType !== null) &&
|
||||
<AvailabilityPicker
|
||||
fromDate={event.fromDate}
|
||||
toDate={event.toDate}
|
||||
eventType={event.eventType}
|
||||
availabilityDurationInMinutes={event.duration}
|
||||
/>
|
||||
}
|
||||
</Grid>
|
||||
<Grid xs={0} md={3}></Grid>
|
||||
<Grid xs={12} md={6} container spacing={1}>
|
||||
<Grid xs={12} sm={9}>
|
||||
<TextField
|
||||
sx={{ width: "100%" }}
|
||||
// TODO
|
||||
// value={event.description}
|
||||
// onChange={(e) => {
|
||||
// event.description = e.target.value;
|
||||
// setEvent({...event});
|
||||
// }}
|
||||
label="Your Name"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid xs={12} sm={3}>
|
||||
<Button sx={{ width: "100%", height: "100%" }} variant="contained">
|
||||
<Typography>Submit</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid xs={0} md={3}></Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,210 @@
|
|||
import { Alert, Button, MenuItem, Select, Slider, TextField, Typography } from '@mui/material';
|
||||
import Grid from '@mui/material/Unstable_Grid2'
|
||||
import { DateTimePicker } from '@mui/x-date-pickers';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { Event, EventTypes, createEvent } from '../types/Event';
|
||||
import utils from '../utils';
|
||||
|
||||
export default function NewEventPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [event, setEvent] = useState<Event>(createEvent());
|
||||
const [isEventValid, setEventValid] = useState<Boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
validateEvent();
|
||||
}, [event])
|
||||
|
||||
function validateEvent(): void {
|
||||
console.log(event);
|
||||
var valid: boolean = true;
|
||||
|
||||
valid &&= event.name && event.name !== "";
|
||||
valid &&= event.eventType !== EventTypes.UNKNOWN || event.eventType !== null;
|
||||
|
||||
if (event.eventType === EventTypes.DATE_RANGE) {
|
||||
valid &&= event.fromDate !== null;
|
||||
valid &&= event.toDate !== null;
|
||||
}
|
||||
|
||||
if (event.eventType === EventTypes.SPECIFIC_DATE) {
|
||||
valid &&= event.fromDate !== null;
|
||||
}
|
||||
|
||||
setEventValid(valid);
|
||||
}
|
||||
|
||||
function saveEvent() {
|
||||
fetch("/api/events", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from_date: event.fromDate?.utc().format(),
|
||||
to_date: event.toDate?.utc().format(),
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
event_type: event.eventType,
|
||||
duration: event.duration
|
||||
})
|
||||
})
|
||||
.then(resp => resp.json())
|
||||
.then(resp => {
|
||||
navigate(resp.result.snowflake_id)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div />
|
||||
<Grid container>
|
||||
<Grid xs={12} spacing={1}>
|
||||
<h2>Create New Event</h2>
|
||||
</Grid>
|
||||
<Grid xs={0} sm={2} md={4}></Grid>
|
||||
<Grid sx={{ p: 2 }} container spacing={1} xs={12} sm={8} md={4}>
|
||||
<Grid xs={12}>
|
||||
<TextField
|
||||
sx={{ width: "100%" }}
|
||||
value={event.name}
|
||||
onChange={(e) => {
|
||||
event.name = e.target.value;
|
||||
setEvent({...event});
|
||||
}}
|
||||
label="I'm organizing a(n)..."
|
||||
/>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<TextField
|
||||
sx={{ width: "100%" }}
|
||||
value={event.description}
|
||||
onChange={(e) => {
|
||||
event.description = e.target.value;
|
||||
setEvent({...event});
|
||||
}}
|
||||
label="More details... ( Optional )"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Typography>
|
||||
Duration
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={{ width: "90%" }}
|
||||
step={30}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(val) => utils.formatMinutesAsHoursMinutes(val)}
|
||||
marks={
|
||||
[
|
||||
{
|
||||
value: 30,
|
||||
label: "30m"
|
||||
},
|
||||
{
|
||||
value: 120,
|
||||
label: "2h"
|
||||
},
|
||||
{
|
||||
value: 240,
|
||||
label: "4h"
|
||||
},
|
||||
{
|
||||
value: 360,
|
||||
label: "6h"
|
||||
},
|
||||
{
|
||||
value: 480,
|
||||
label: "8h"
|
||||
}
|
||||
]
|
||||
}
|
||||
min={30}
|
||||
max={480}
|
||||
value={event.duration}
|
||||
onChange={(_, val) => {
|
||||
event.duration = val as number;
|
||||
setEvent({...event});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Select
|
||||
sx={{ width: "100%" }}
|
||||
value={event.eventType}
|
||||
onChange={(e) => {
|
||||
event.eventType = e.target.value;
|
||||
setEvent({...event});
|
||||
}}
|
||||
>
|
||||
<MenuItem value={EventTypes.UNKNOWN} disabled>Event Type</MenuItem>
|
||||
<MenuItem value={EventTypes.SPECIFIC_DATE}>Exact Date</MenuItem>
|
||||
<MenuItem value={EventTypes.DATE_RANGE}>Between</MenuItem>
|
||||
<MenuItem value={EventTypes.DAY}>Daily</MenuItem>
|
||||
<MenuItem value={EventTypes.WEEK}>Weekly</MenuItem>
|
||||
</Select>
|
||||
</Grid>
|
||||
{
|
||||
event.eventType == EventTypes.SPECIFIC_DATE &&
|
||||
<Grid xs={12}>
|
||||
<DateTimePicker
|
||||
sx={{ width: "100%" }}
|
||||
value={event.fromDate}
|
||||
onChange={(value) => {
|
||||
event.fromDate = value ?? null;
|
||||
setEvent({...event});
|
||||
}}
|
||||
label="When"
|
||||
/>
|
||||
</Grid>
|
||||
}
|
||||
{
|
||||
event.eventType == EventTypes.DATE_RANGE &&
|
||||
<Grid xs={12} sm={6}>
|
||||
<DateTimePicker
|
||||
sx={{ width: "100%" }}
|
||||
value={event.fromDate}
|
||||
onChange={(value) => {
|
||||
event.fromDate = value ?? null;
|
||||
setEvent({...event});
|
||||
}}
|
||||
label="From"
|
||||
/>
|
||||
</Grid>
|
||||
}
|
||||
{
|
||||
event.eventType == EventTypes.DATE_RANGE &&
|
||||
<Grid xs={12} sm={6}>
|
||||
<DateTimePicker
|
||||
sx={{ width: "100%" }}
|
||||
value={event.toDate}
|
||||
onChange={(value) => {
|
||||
event.toDate = value ?? null;
|
||||
setEvent({...event});
|
||||
}}
|
||||
label="To"
|
||||
/>
|
||||
</Grid>
|
||||
}
|
||||
{
|
||||
(event.eventType == EventTypes.DAY || event.eventType == EventTypes.WEEK || event.eventType == EventTypes.MONTH) &&
|
||||
<Grid xs={12}>
|
||||
<Alert severity={"info"}>
|
||||
<Typography>Selecting the Day type will allow attendees to select their availability during an unspecified {event.eventType}</Typography>
|
||||
</Alert>
|
||||
</Grid>
|
||||
}
|
||||
<Grid xs={12}>
|
||||
<Button
|
||||
disabled={!isEventValid}
|
||||
sx={{ width: "100%" }}
|
||||
variant={"contained"}
|
||||
onClick={saveEvent}
|
||||
>
|
||||
<Typography>Create</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid xs={0} sm={2} md={4}></Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
import { ThemeProvider } from "@emotion/react";
|
||||
import { Box, CssBaseline, createTheme } from "@mui/material";
|
||||
import { CssBaseline, Divider, Paper, createTheme } from "@mui/material";
|
||||
import Grid from '@mui/material/Unstable_Grid2'
|
||||
import GithubCorner from "react-github-corner";
|
||||
import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
||||
import dayjs from "dayjs";
|
||||
import * as utc from 'dayjs/plugin/utc';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
|
@ -12,21 +22,51 @@ export default function RootLayout(props: { children: React.ReactNode }) {
|
|||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline/>
|
||||
|
||||
<Box
|
||||
<GithubCorner
|
||||
href={"https://github.com/mvvasilev/findtheti.me"}
|
||||
bannerColor="#FD6C6C"
|
||||
octoColor="inherit"
|
||||
size={80}
|
||||
direction="right"
|
||||
/>
|
||||
|
||||
<Paper
|
||||
component="main"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
top: 0,
|
||||
left: "50%",
|
||||
transform: "translate(-50%, 0)",
|
||||
width: {
|
||||
xs: "100%",
|
||||
lg: "1000px"
|
||||
},
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
top: "25px",
|
||||
width: "1000px"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||
<Grid container>
|
||||
<Grid xs={12}>
|
||||
<Header />
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Divider></Divider>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
{props.children}
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Divider></Divider>
|
||||
</Grid>
|
||||
<Grid xs={12}>
|
||||
<Footer />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</LocalizationProvider>
|
||||
</Paper>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
32
frontend/src/types/Event.tsx
Normal file
32
frontend/src/types/Event.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Dayjs } from "dayjs";
|
||||
|
||||
export type Event = {
|
||||
snowflakeId: String,
|
||||
name: String,
|
||||
description: String,
|
||||
fromDate: null | Dayjs,
|
||||
toDate: null | Dayjs,
|
||||
eventType: String,
|
||||
duration: number
|
||||
};
|
||||
|
||||
export const EventTypes = {
|
||||
UNKNOWN: "Unknown",
|
||||
SPECIFIC_DATE: "SpecificDate",
|
||||
DATE_RANGE: "DateRange",
|
||||
DAY: "Day",
|
||||
WEEK: "Week",
|
||||
MONTH: "Month" // Unsupported atm
|
||||
};
|
||||
|
||||
export function createEvent(): Event {
|
||||
return {
|
||||
snowflakeId: "",
|
||||
name: "",
|
||||
description: "",
|
||||
fromDate: null,
|
||||
toDate: null,
|
||||
eventType: EventTypes.UNKNOWN,
|
||||
duration: 30
|
||||
};
|
||||
}
|
30
frontend/src/utils.ts
Normal file
30
frontend/src/utils.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import dayjs from "dayjs";
|
||||
import * as duration from 'dayjs/plugin/duration';
|
||||
|
||||
dayjs.extend(duration)
|
||||
|
||||
const utils = {
|
||||
toHoursAndMinutes: (totalMinutes: number): { hours: number, minutes: number } => {
|
||||
const hours = Math.floor(totalMinutes / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
|
||||
return { hours, minutes };
|
||||
},
|
||||
formatMinutesAsHoursMinutes: (val: number): String => {
|
||||
let { hours, minutes } = utils.toHoursAndMinutes(val);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
},
|
||||
zeroPad: (num: number, places: number): String => {
|
||||
return String(num).padStart(places, '0');
|
||||
},
|
||||
formatTimeFromHourOfDay: (hourOfDay: number, minutes: number): String => {
|
||||
return dayjs.duration({ hours: hourOfDay, minutes: minutes }).format('HH:mm');
|
||||
}
|
||||
}
|
||||
|
||||
export default utils;
|
|
@ -6,6 +6,15 @@ import { fileURLToPath, URL } from 'node:url'
|
|||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz"
|
||||
integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.23.5":
|
||||
"@babel/core@^7.23.5":
|
||||
version "7.23.7"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz"
|
||||
integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==
|
||||
|
@ -186,6 +186,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.23.2":
|
||||
version "7.23.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz"
|
||||
|
@ -265,7 +272,7 @@
|
|||
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz"
|
||||
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
|
||||
|
||||
"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.11.3", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0":
|
||||
"@emotion/react@^11.11.3":
|
||||
version "11.11.3"
|
||||
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.3.tgz"
|
||||
integrity sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==
|
||||
|
@ -295,7 +302,7 @@
|
|||
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz"
|
||||
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
|
||||
|
||||
"@emotion/styled@^11.11.0", "@emotion/styled@^11.3.0":
|
||||
"@emotion/styled@^11.11.0":
|
||||
version "11.11.0"
|
||||
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz"
|
||||
integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==
|
||||
|
@ -327,11 +334,121 @@
|
|||
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz"
|
||||
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
|
||||
|
||||
"@esbuild/aix-ppc64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3"
|
||||
integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==
|
||||
|
||||
"@esbuild/android-arm64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220"
|
||||
integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==
|
||||
|
||||
"@esbuild/android-arm@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c"
|
||||
integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==
|
||||
|
||||
"@esbuild/android-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2"
|
||||
integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==
|
||||
|
||||
"@esbuild/darwin-arm64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf"
|
||||
integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==
|
||||
|
||||
"@esbuild/darwin-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e"
|
||||
integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a"
|
||||
integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==
|
||||
|
||||
"@esbuild/freebsd-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2"
|
||||
integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==
|
||||
|
||||
"@esbuild/linux-arm64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545"
|
||||
integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==
|
||||
|
||||
"@esbuild/linux-arm@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3"
|
||||
integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==
|
||||
|
||||
"@esbuild/linux-ia32@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4"
|
||||
integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==
|
||||
|
||||
"@esbuild/linux-loong64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121"
|
||||
integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==
|
||||
|
||||
"@esbuild/linux-mips64el@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9"
|
||||
integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==
|
||||
|
||||
"@esbuild/linux-ppc64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912"
|
||||
integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==
|
||||
|
||||
"@esbuild/linux-riscv64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916"
|
||||
integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==
|
||||
|
||||
"@esbuild/linux-s390x@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8"
|
||||
integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==
|
||||
|
||||
"@esbuild/linux-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz"
|
||||
integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==
|
||||
|
||||
"@esbuild/netbsd-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d"
|
||||
integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==
|
||||
|
||||
"@esbuild/openbsd-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2"
|
||||
integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==
|
||||
|
||||
"@esbuild/sunos-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767"
|
||||
integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==
|
||||
|
||||
"@esbuild/win32-arm64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee"
|
||||
integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==
|
||||
|
||||
"@esbuild/win32-ia32@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c"
|
||||
integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==
|
||||
|
||||
"@esbuild/win32-x64@0.19.11":
|
||||
version "0.19.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04"
|
||||
integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
|
||||
|
@ -442,7 +559,7 @@
|
|||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mui/base@5.0.0-beta.30":
|
||||
"@mui/base@5.0.0-beta.30", "@mui/base@^5.0.0-beta.22":
|
||||
version "5.0.0-beta.30"
|
||||
resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.30.tgz"
|
||||
integrity sha512-dc38W4W3K42atE9nSaOeoJ7/x9wGIfawdwC/UmMxMLlZ1iSsITQ8dQJaTATCbn98YvYPINK/EH541YA5enQIPQ==
|
||||
|
@ -516,7 +633,7 @@
|
|||
resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.12.tgz"
|
||||
integrity sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw==
|
||||
|
||||
"@mui/utils@^5.15.3":
|
||||
"@mui/utils@^5.14.16", "@mui/utils@^5.15.3":
|
||||
version "5.15.3"
|
||||
resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.15.3.tgz"
|
||||
integrity sha512-mT3LiSt9tZWCdx1pl7q4Q5tNo6gdZbvJel286ZHGuj6LQQXjWNAh8qiF9d+LogvNUI+D7eLkTnj605d1zoazfg==
|
||||
|
@ -526,6 +643,19 @@
|
|||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@mui/x-date-pickers@^6.18.7":
|
||||
version "6.18.7"
|
||||
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.18.7.tgz#6b00163c77dc450c11b44a479baf62541e6f8b36"
|
||||
integrity sha512-4NoapaCT3jvEk2cuAUjG0ReZvTEk1i4dGDz94Gt1Oc08GuC1AuzYRwCR1/1tdmbDynwkR8ilkKL6AyS3NL1H4A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
"@mui/base" "^5.0.0-beta.22"
|
||||
"@mui/utils" "^5.14.16"
|
||||
"@types/react-transition-group" "^4.4.8"
|
||||
clsx "^2.0.0"
|
||||
prop-types "^15.8.1"
|
||||
react-transition-group "^4.4.5"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
||||
|
@ -534,7 +664,7 @@
|
|||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
@ -557,6 +687,46 @@
|
|||
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.14.1.tgz"
|
||||
integrity sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz#b1094962742c1a0349587040bc06185e2a667c9b"
|
||||
integrity sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz#96eb86fb549e05b187f2ad06f51d191a23cb385a"
|
||||
integrity sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz#2456630c007cc5905cb368acb9ff9fc04b2d37be"
|
||||
integrity sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz#97742214fc7dfd47a0f74efba6f5ae264e29c70c"
|
||||
integrity sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz#cd933e61d6f689c9cdefde424beafbd92cfe58e2"
|
||||
integrity sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz#33b09bf462f1837afc1e02a1b352af6b510c78a6"
|
||||
integrity sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz#50257fb248832c2308064e3764a16273b6ee4615"
|
||||
integrity sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz#09589e4e1a073cf56f6249b77eb6c9a8e9b613a8"
|
||||
integrity sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz"
|
||||
|
@ -567,6 +737,21 @@
|
|||
resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz"
|
||||
integrity sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz#95957067eb107f571da1d81939f017d37b4958d3"
|
||||
integrity sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz#71b6facad976db527863f698692c6964c0b6e10e"
|
||||
integrity sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.9.4":
|
||||
version "4.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz#16295ccae354707c9bc6842906bdeaad4f3ba7a5"
|
||||
integrity sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==
|
||||
|
||||
"@types/babel__core@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
|
||||
|
@ -610,7 +795,7 @@
|
|||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
|
||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||
|
||||
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.10.7":
|
||||
"@types/node@^20.10.7":
|
||||
version "20.10.7"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz"
|
||||
integrity sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==
|
||||
|
@ -634,14 +819,14 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@^4.4.10":
|
||||
"@types/react-transition-group@^4.4.10", "@types/react-transition-group@^4.4.8":
|
||||
version "4.4.10"
|
||||
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz"
|
||||
integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^17.0.0 || ^18.0.0", "@types/react@^18.2.43":
|
||||
"@types/react@*", "@types/react@^18.2.43":
|
||||
version "18.2.47"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-18.2.47.tgz"
|
||||
integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==
|
||||
|
@ -677,7 +862,7 @@
|
|||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha", "@typescript-eslint/parser@^6.14.0":
|
||||
"@typescript-eslint/parser@^6.14.0":
|
||||
version "6.18.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.0.tgz"
|
||||
integrity sha512-v6uR68SFvqhNQT41frCMCQpsP+5vySy6IdgjlzUWoo7ALCnpaWYcz/Ij2k4L8cEsL0wkvOviCMpjmtRtHNOKzA==
|
||||
|
@ -767,7 +952,7 @@ acorn-jsx@^5.3.2:
|
|||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0:
|
||||
acorn@^8.9.0:
|
||||
version "8.11.3"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz"
|
||||
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
|
||||
|
@ -847,7 +1032,7 @@ braces@^3.0.2:
|
|||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
browserslist@^4.22.2, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.22.2:
|
||||
version "4.22.2"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz"
|
||||
integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==
|
||||
|
@ -903,16 +1088,16 @@ color-convert@^2.0.1:
|
|||
dependencies:
|
||||
color-name "~1.1.4"
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
|
@ -953,6 +1138,11 @@ csstype@^3.0.2, csstype@^3.1.2:
|
|||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
||||
dayjs@^1.11.10:
|
||||
version "1.11.10"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
|
||||
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
|
@ -1066,7 +1256,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
|
|||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
|
||||
"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.55.0, eslint@>=7:
|
||||
eslint@^8.55.0:
|
||||
version "8.56.0"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz"
|
||||
integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==
|
||||
|
@ -1222,6 +1412,11 @@ fs.realpath@^1.0.0:
|
|||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
|
@ -1489,13 +1684,6 @@ micromatch@^4.0.4:
|
|||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@9.0.3:
|
||||
version "9.0.3"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz"
|
||||
|
@ -1503,6 +1691,13 @@ minimatch@9.0.3:
|
|||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
||||
|
@ -1646,7 +1841,7 @@ queue-microtask@^1.2.2:
|
|||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
"react-dom@^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0:
|
||||
react-dom@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
|
@ -1654,6 +1849,11 @@ queue-microtask@^1.2.2:
|
|||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-github-corner@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-github-corner/-/react-github-corner-2.5.0.tgz#e350d0c69f69c075bc0f1d2a6f1df6ee91da31f2"
|
||||
integrity sha512-ofds9l6n61LJc6ML+jSE6W9ZSQvATcMR9evnHPXua1oDYj289HKODnVqFUB/g2a4ieBjDHw416iHP3MjqnU76Q==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
|
@ -1694,7 +1894,7 @@ react-transition-group@^4.4.5:
|
|||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"react@^17.0.0 || ^18.0.0", react@^18.2.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
|
@ -1877,7 +2077,7 @@ type-fest@^0.20.2:
|
|||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
|
||||
typescript@^5.2.2, typescript@>=4.2.0:
|
||||
typescript@^5.2.2:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz"
|
||||
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
||||
|
@ -1902,7 +2102,7 @@ uri-js@^4.2.2:
|
|||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
"vite@^4.2.0 || ^5.0.0", vite@^5.0.8:
|
||||
vite@^5.0.8:
|
||||
version "5.0.11"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz"
|
||||
integrity sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==
|
||||
|
|
11
migrations/20240108212119_EventSetDatesOptional.sql
Normal file
11
migrations/20240108212119_EventSetDatesOptional.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
ALTER TABLE events.event
|
||||
DROP COLUMN IF EXISTS from_date;
|
||||
|
||||
ALTER TABLE events.event
|
||||
DROP COLUMN IF EXISTS to_date;
|
||||
|
||||
ALTER TABLE events.event
|
||||
ADD COLUMN IF NOT EXISTS from_date TIMESTAMP NULL;
|
||||
|
||||
ALTER TABLE events.event
|
||||
ADD COLUMN IF NOT EXISTS to_date TIMESTAMP NULL;
|
2
migrations/20240109082813_EventAddDuration.sql
Normal file
2
migrations/20240109082813_EventAddDuration.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE events.event
|
||||
ADD COLUMN IF NOT EXISTS duration INTEGER NOT NULL DEFAULT (60);
|
|
@ -40,8 +40,8 @@ pub(crate) async fn insert_event_and_fetch_id(
|
|||
) -> Result<i64, sqlx::Error> {
|
||||
sqlx::query_scalar!(
|
||||
r#"
|
||||
INSERT INTO events.event (snowflake_id, name, description, from_date, to_date, event_type)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
INSERT INTO events.event (snowflake_id, name, description, from_date, to_date, event_type, duration)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id
|
||||
"#,
|
||||
event.snowflake_id,
|
||||
|
@ -49,7 +49,8 @@ pub(crate) async fn insert_event_and_fetch_id(
|
|||
event.description,
|
||||
event.from_date,
|
||||
event.to_date,
|
||||
event.event_type.to_string()
|
||||
event.event_type.to_string(),
|
||||
event.duration
|
||||
)
|
||||
.fetch_one(&mut **txn)
|
||||
.await
|
||||
|
|
|
@ -18,21 +18,23 @@ use crate::{
|
|||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateEventDto {
|
||||
from_date: DateTime<Utc>,
|
||||
to_date: DateTime<Utc>,
|
||||
from_date: Option<DateTime<Utc>>,
|
||||
to_date: Option<DateTime<Utc>>,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
event_type: String,
|
||||
duration: i32
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EventDto {
|
||||
snowflake_id: String,
|
||||
from_date: DateTime<Utc>,
|
||||
to_date: DateTime<Utc>,
|
||||
from_date: Option<DateTime<Utc>>,
|
||||
to_date: Option<DateTime<Utc>>,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
event_type: String,
|
||||
duration: i32
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -92,9 +94,10 @@ pub async fn create_event(
|
|||
snowflake_id: uid,
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
from_date: dto.from_date.naive_utc(),
|
||||
to_date: dto.to_date.naive_utc(),
|
||||
from_date: dto.from_date.map(|d| d.naive_utc()),
|
||||
to_date: dto.to_date.map(|d| d.naive_utc()),
|
||||
event_type,
|
||||
duration: dto.duration
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
@ -103,11 +106,12 @@ pub async fn create_event(
|
|||
|
||||
Ok(EventDto {
|
||||
snowflake_id: event.snowflake_id,
|
||||
from_date: Utc.from_utc_datetime(&event.from_date),
|
||||
to_date: Utc.from_utc_datetime(&event.to_date),
|
||||
from_date: event.from_date.map(|d| Utc.from_utc_datetime(&d)),
|
||||
to_date: event.to_date.map(|d| Utc.from_utc_datetime(&d)),
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
event_type: event.event_type.to_string(),
|
||||
duration: event.duration
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -132,11 +136,12 @@ pub async fn fetch_event(
|
|||
|
||||
Ok(EventDto {
|
||||
snowflake_id: event.snowflake_id,
|
||||
from_date: Utc.from_utc_datetime(&event.from_date),
|
||||
to_date: Utc.from_utc_datetime(&event.to_date),
|
||||
from_date: event.from_date.map(|d| Utc.from_utc_datetime(&d)),
|
||||
to_date: event.to_date.map(|d| Utc.from_utc_datetime(&d)),
|
||||
name: event.name,
|
||||
description: event.description,
|
||||
event_type: event.event_type.to_string(),
|
||||
duration: event.duration
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -7,9 +7,10 @@ pub(crate) struct Event {
|
|||
pub snowflake_id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub from_date: NaiveDateTime,
|
||||
pub to_date: NaiveDateTime,
|
||||
pub from_date: Option<NaiveDateTime>,
|
||||
pub to_date: Option<NaiveDateTime>,
|
||||
pub event_type: EventType,
|
||||
pub duration: i32
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -16,10 +16,15 @@ async fn main() {
|
|||
|
||||
let api_routes = api::routes().await.expect("Unable to create api routes");
|
||||
|
||||
let routes = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.nest_service("/", ServeDir::new("./frontend/dist"))
|
||||
.fallback_service(ServeDir::new("./frontend/dist"));
|
||||
let mut routes = Router::new()
|
||||
.nest("/api", api_routes);
|
||||
|
||||
|
||||
// If in release mod, serve static files
|
||||
if !cfg!(debug_assertions) {
|
||||
routes = routes.nest_service("/", ServeDir::new("./frontend/dist"))
|
||||
.fallback_service(ServeDir::new("./frontend/dist"));
|
||||
}
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue