qpa-client/packages/qpa/Event/EventForm.tsx

388 lines
12 KiB
TypeScript
Raw Normal View History

2019-11-11 16:16:11 +01:00
import { format, isBefore, addHours } from "date-fns"
2019-10-11 09:20:37 +02:00
import { Field, Form, Formik } from "formik"
import {
Button,
DatePicker,
TimePicker,
PickersProvider,
TextField,
2019-11-15 15:26:50 +01:00
Checkbox,
} from "qpa-components"
2019-12-11 09:08:38 +01:00
import css from "@emotion/css"
import { useTheme } from "qpa-emotion"
2019-10-11 09:20:37 +02:00
import * as React from "react"
2019-10-22 15:40:02 +02:00
import styled from "@emotion/styled"
2019-11-03 19:30:07 +01:00
import { hot } from "react-hot-loader"
2019-12-11 09:08:38 +01:00
import { EventPublishedState, EventStatus } from "../../../@types"
2019-10-22 15:29:28 +02:00
import * as intl from "react-intl-universal"
import TagSelector from "../EventTags/TagsSelector"
2019-10-11 09:20:37 +02:00
import messages from "./EventForm.msg.json"
2019-11-14 18:06:47 +01:00
import NextOccurrencesPreview from "./NextOccurrencesPreview"
2019-11-14 10:37:37 +01:00
import RecurrencePicker from "./Recurrence/RecurrencePicker"
2019-05-24 19:59:54 +02:00
interface Props {
2019-10-11 09:20:37 +02:00
values?: EventFormData
onSubmit: (values: EventFormData) => void
loading: boolean
deleteEventLoading?: boolean
locales: string[]
onDeleteEvent?: () => void
2019-05-24 19:59:54 +02:00
}
2019-11-14 18:06:47 +01:00
export interface EventTimeFormData {
timeZone: string
start: string
end: string
recurrence?: string
exceptions?: string
}
2019-05-25 10:36:18 +02:00
export interface EventFormData {
2019-11-14 18:06:47 +01:00
time: EventTimeFormData
2019-10-10 17:50:23 +02:00
infos: Array<{
2019-10-11 09:20:37 +02:00
language: string
title: string
description: string
}>
2019-10-10 17:50:23 +02:00
location: {
2019-10-11 09:20:37 +02:00
address: string
name: string
}
status: EventStatus
2019-11-10 16:17:13 +01:00
tagNames: string[]
publishedState: EventPublishedState
2019-05-24 19:59:54 +02:00
}
2019-11-04 12:58:55 +01:00
const todayMidday = new Date()
todayMidday.setUTCHours(12, 0)
const todayOnePM = new Date()
todayOnePM.setUTCHours(13, 0)
2019-05-24 19:59:54 +02:00
2019-06-04 14:03:51 +02:00
const EventForm = (props: Props) => {
2019-10-22 15:29:28 +02:00
intl.load({
2019-10-10 17:50:23 +02:00
"es-ES": messages.es,
2019-10-11 09:20:37 +02:00
"en-GB": messages.en,
})
const isEdit = !!props.values
2019-12-11 09:08:38 +01:00
const theme = useTheme()
2019-11-15 15:26:50 +01:00
const isEventOverMultipleDays =
props.values &&
props.values.time &&
new Date(props.values.time.start).getDay() !==
new Date(props.values.time.end).getDay()
const [showEndDate, setShowEndDate] = React.useState<boolean>(
!!isEventOverMultipleDays
)
const [isRecurrentEvent, setIsRecurrentEvent] = React.useState<boolean>(
!!(
props.values &&
props.values &&
props.values.time &&
props.values.time.recurrence
)
)
2019-10-10 17:50:23 +02:00
return (
<Formik
2019-11-06 10:10:56 +01:00
onSubmit={props.onSubmit}
2019-10-10 17:50:23 +02:00
initialValues={
props.values
? props.values
: ({
time: {
timeZone: "Europe/Madrid",
2019-11-14 18:06:47 +01:00
start: format(todayMidday, "yyyy-MM-dd'T'HH:mm"),
end: format(todayOnePM, "yyyy-MM-dd'T'HH:mm"),
2019-10-10 17:50:23 +02:00
},
2019-10-25 07:42:09 +02:00
infos: props.locales.map(locale => {
const lang = locale.substring(0, 2)
return {
language: lang,
title: "",
description: "",
}
}),
2019-10-10 17:50:23 +02:00
location: {
name: "",
2019-10-11 09:20:37 +02:00
address: "",
2019-10-10 17:50:23 +02:00
},
2019-11-10 16:17:13 +01:00
tagNames: [],
2019-10-11 09:20:37 +02:00
status: "confirmed",
2019-12-11 09:08:38 +01:00
publishedState: "published",
2019-10-10 17:50:23 +02:00
} as EventFormData)
}
validate={(values: EventFormData) => {
2019-10-11 09:20:37 +02:00
const errors: any = {}
2019-10-25 07:42:09 +02:00
if (!values.location.address) {
errors.location = errors.location || {}
errors.location.address = intl.get("must-provide-location-address")
}
if (!values.location.name) {
errors.location = errors.location || {}
errors.location.name = intl.get("must-provide-location-name")
}
if (isBefore(new Date(values.time.end), new Date(values.time.start))) {
errors.time = errors.time || {}
errors.time.start = intl.get("validate-end-after-start")
errors.time.end = intl.get("validate-end-after-start")
}
2019-10-25 07:42:09 +02:00
return errors
2019-10-10 17:50:23 +02:00
}}
>
{({ isValid, setFieldValue, values, errors }) => {
2019-10-10 17:50:23 +02:00
return (
<StyledForm>
2019-11-10 16:17:13 +01:00
<TagSelector
onChange={tagNames => setFieldValue("tagNames", tagNames)}
value={values.tagNames}
/>
2019-10-10 17:50:23 +02:00
<FormTitle>
{props.locales.length > 1
2019-10-22 15:29:28 +02:00
? intl.get("EVENT_FORM_DETAILS_FOREWORD_MULTILINGUAL")
: intl.get("EVENT_FORM_DETAILS_FOREWORD")}
2019-10-10 17:50:23 +02:00
</FormTitle>
2019-10-22 16:41:32 +02:00
{props.locales.map(locale => {
const language = locale.split("-")[0]
const msg = messages[language]
2019-10-25 07:42:09 +02:00
const i = values.infos.findIndex(
info => info.language === language
)
2019-10-10 17:50:23 +02:00
return (
2019-10-22 16:41:32 +02:00
<Section key={locale}>
2019-10-11 10:36:08 +02:00
<SectionTitle>
2019-11-03 19:30:07 +01:00
{messages[language]["EVENT_FORM_INFO_IN_LANGUAGE"]}
2019-10-11 10:36:08 +02:00
</SectionTitle>
2019-10-11 09:20:37 +02:00
<p>{msg.EVENT_TITLE}</p>
2019-10-10 17:50:23 +02:00
<Field name={`infos[${i}].title`}>
{({ field }) => (
2019-10-11 09:20:37 +02:00
<TextField
{...field}
placeholder={msg.EVENT_TITLE_PLACEHOLDER}
/>
2019-10-10 17:50:23 +02:00
)}
</Field>
<p>{msg.DESCRIPTION}</p>
2019-10-10 17:50:23 +02:00
<Field name={`infos[${i}].description`}>
{({ field }) => (
<TextField
{...field}
2019-11-03 19:30:07 +01:00
variant="outlined"
2019-10-10 17:50:23 +02:00
multiline
rows={8}
2019-10-11 09:20:37 +02:00
placeholder={msg.DESCRIPTION_PLACEHOLDER}
2019-10-10 17:50:23 +02:00
/>
)}
</Field>
</Section>
2019-10-11 09:20:37 +02:00
)
2019-10-10 17:50:23 +02:00
})}
2019-11-14 18:06:47 +01:00
<div id="timesection">
2019-10-25 07:42:09 +02:00
<PickersProvider>
<SectionTitle>{intl.get("TITLE_TIME")}</SectionTitle>
<FormTitle>{intl.get("TIME_EXPLANATION")}</FormTitle>
2019-11-15 15:26:50 +01:00
<EventTimeSection>
<TimeSegment>
<Field name="time.start">
{({ field }) => {
const timeStartOnChange = newStartDate => {
const oneHourLater = addHours(newStartDate, 1)
const newTime = {
...values.time,
start: format(newStartDate, "yyyy-MM-dd'T'HH:mm"),
end: format(oneHourLater, "yyyy-MM-dd'T'HH:mm"),
}
setFieldValue("time", newTime)
2019-11-15 15:26:50 +01:00
}
return (
<>
<DatePicker
label="Start date"
{...field}
onChange={timeStartOnChange}
/>
<TimePicker
label="Start time"
{...field}
onChange={timeStartOnChange}
/>
</>
2019-11-11 16:16:11 +01:00
)
2019-11-15 15:26:50 +01:00
}}
</Field>
</TimeSegment>
<TimeSegment>
<Field name="time.end">
{({ field }) => {
const timeEndOnChange = newStartDate => {
setFieldValue(
"time.end" as any,
format(newStartDate, "yyyy-MM-dd'T'HH:mm")
)
}
return (
<>
{showEndDate ? (
<DatePicker
label="End date"
{...field}
onChange={timeEndOnChange}
/>
) : null}
<TimePicker
label="End time"
{...field}
onChange={timeEndOnChange}
/>
</>
)
2019-11-15 15:26:50 +01:00
}}
</Field>
</TimeSegment>
</EventTimeSection>
2019-10-25 07:42:09 +02:00
</PickersProvider>
2019-11-15 15:26:50 +01:00
<Checkbox
label={intl.get("event-is-multidate")}
checked={showEndDate}
onChange={() => setShowEndDate(!showEndDate)}
disabled={
new Date(values.time.start).getDay() !==
new Date(values.time.end).getDay()
}
/>
<Checkbox
label={intl.get("event-is-recurrent")}
checked={isRecurrentEvent}
onChange={() => {
const newIsRecurrentEvent = !isRecurrentEvent
if (!newIsRecurrentEvent) {
setFieldValue("time.recurrence" as any, null)
}
setIsRecurrentEvent(newIsRecurrentEvent)
}}
disabled={
new Date(values.time.start).getDay() !==
new Date(values.time.end).getDay()
}
/>
2019-11-14 18:06:47 +01:00
<RecurrencePicker
firstOccurrence={values.time}
2019-11-15 15:26:50 +01:00
disabled={!isRecurrentEvent}
2019-11-14 18:06:47 +01:00
onChange={rrule => {
setFieldValue("time.recurrence" as any, rrule)
}}
/>
2019-11-15 15:26:50 +01:00
{values.time.recurrence ? (
<>
<SectionTitle>
{intl.get("occurrences-preview-title")}
</SectionTitle>
<StyledNextOccurrencesPreview eventTime={values.time} />
</>
) : null}
2019-11-14 18:06:47 +01:00
</div>
2019-10-10 11:46:01 +02:00
2019-10-22 15:29:28 +02:00
<p>{intl.get("LOCATION")}</p>
2019-10-10 17:50:23 +02:00
<Field name="location.name">
{({ field }) => (
<TextField
{...field}
2019-10-22 15:29:28 +02:00
placeholder={intl.get("LOCATION_PLACEHOLDER")}
2019-10-10 17:50:23 +02:00
/>
)}
</Field>
2019-10-22 15:29:28 +02:00
<p>{intl.get("ADDRESS")}</p>
2019-10-10 17:50:23 +02:00
<Field name="location.address">
{({ field }) => (
<TextField
{...field}
2019-10-22 15:29:28 +02:00
placeholder={intl.get("ADDRESS_PLACEHOLDER")}
2019-10-10 17:50:23 +02:00
/>
)}
</Field>
<Footer>
2019-12-11 09:08:38 +01:00
<Button
type="submit"
loading={props.loading}
disabled={!isValid}
color="primary"
>
2019-12-11 08:56:44 +01:00
{isEdit ? intl.get("save-changes") : intl.get("CREATE")}
2019-10-10 17:50:23 +02:00
</Button>
2019-10-11 09:20:37 +02:00
{props.onDeleteEvent ? (
<DeleteButton
type="button"
2019-12-11 09:08:38 +01:00
color="inherit"
2019-10-11 09:20:37 +02:00
onClick={props.onDeleteEvent}
loading={props.deleteEventLoading}
2019-12-11 09:08:38 +01:00
css={css`
&& {
background-color: ${theme.colors.red};
}
`}
2019-10-11 09:20:37 +02:00
>
2019-10-22 15:29:28 +02:00
{intl.get("DELETE")}
2019-10-10 17:50:23 +02:00
</DeleteButton>
) : null}
</Footer>
</StyledForm>
2019-10-11 09:20:37 +02:00
)
2019-10-10 17:50:23 +02:00
}}
</Formik>
2019-10-11 09:20:37 +02:00
)
}
2019-10-07 17:06:08 +02:00
2019-10-17 16:35:45 +02:00
const FormTitle = styled.div`
2019-10-07 17:33:19 +02:00
font-size: 18px;
2019-10-11 09:20:37 +02:00
`
2019-10-17 16:35:45 +02:00
const SectionTitle = styled.div`
2019-10-07 17:33:19 +02:00
font-size: 24px;
font-weight: bold;
2019-10-11 09:20:37 +02:00
`
2019-10-17 16:35:45 +02:00
const Section = styled.section`
2019-10-07 17:33:19 +02:00
padding-top: 18px;
2019-11-03 19:30:07 +01:00
display: flex;
flex-direction: column;
2019-10-11 09:20:37 +02:00
`
2019-10-07 17:33:19 +02:00
2019-10-17 16:35:45 +02:00
const DeleteButton = styled(Button)`
2019-10-10 11:46:01 +02:00
background: red;
2019-10-11 09:20:37 +02:00
`
2019-10-10 11:46:01 +02:00
2019-11-04 07:30:00 +01:00
const StyledForm = styled(Form)`
2019-10-07 17:06:08 +02:00
display: flex;
flex-direction: column;
margin-top: 24px;
2019-10-11 09:20:37 +02:00
`
2019-10-22 15:40:02 +02:00
2019-11-15 15:26:50 +01:00
const TimeSegment = styled.div`
2019-11-03 20:01:23 +01:00
display: flex;
flex-direction: row;
2019-11-15 15:26:50 +01:00
> *:not(:last-of-type),
&:not(:last-of-type) {
2019-11-03 20:01:23 +01:00
margin-right: 8px;
}
`
2019-11-15 15:26:50 +01:00
const EventTimeSection = styled.div`
margin-top: 8px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
`
2019-10-17 16:35:45 +02:00
const Footer = styled.div`
2019-10-10 11:46:01 +02:00
display: flex;
flex-direction: row;
2019-11-03 20:01:23 +01:00
margin-top: 14px;
justify-content: center;
2019-12-11 09:08:38 +01:00
> *:not(:last-of-type) {
margin-right: 24px;
}
2019-10-11 09:20:37 +02:00
`
2019-11-14 18:06:47 +01:00
2019-11-15 15:26:50 +01:00
const StyledNextOccurrencesPreview = styled(NextOccurrencesPreview)`
height: 6em;
`
2019-11-03 19:30:07 +01:00
export default hot(module)(EventForm)