Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui/schedules: temp sched calendar override button #1857

Merged
merged 25 commits into from
Sep 9, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
113fad7
Added choose dialog before other overrides
spencerpauly Aug 11, 2021
2b8be77
test
spencerpauly Aug 11, 2021
a9f2238
Stubbed out basic UI
spencerpauly Aug 12, 2021
7fd6858
Add schedule calendar context & proper wizard form handling
spencerpauly Aug 16, 2021
18aeae1
Added default selected variant
spencerpauly Aug 16, 2021
8826430
Cleaned up code for override feature
spencerpauly Aug 17, 2021
d464a58
Removed duplicate copy text
spencerpauly Aug 17, 2021
79688d3
Updated cypress tests, fixed submit bug, needs rebase w master
spencerpauly Aug 19, 2021
37f652c
Merge branch 'master' into temp-sched-calendar-override
spencerpauly Aug 19, 2021
8610a24
Fix some merge errors
spencerpauly Aug 19, 2021
3423762
Cleaned up before PR
spencerpauly Aug 20, 2021
5e40348
Update web/src/app/schedules/ScheduleCalendarOverrideDialog.js
spencerpauly Aug 25, 2021
3a2622c
Fix temp schedule text
spencerpauly Aug 26, 2021
f2f49fc
Merge branch 'master' into temp-sched-calendar-override
spencerpauly Aug 26, 2021
dacc123
Move override files to '/schedules/calendar' directory
spencerpauly Sep 3, 2021
4471f29
Move copyText to new file
spencerpauly Sep 3, 2021
73ddbe7
Update override dialog copy
spencerpauly Sep 3, 2021
a6e30b2
Make remove user read-only from override shift but not from toolbar
spencerpauly Sep 7, 2021
4e2f4c5
Merge branch 'master' into temp-sched-calendar-override
spencerpauly Sep 7, 2021
04fd13c
Refactor step form into scheduleCalendarOverrideDialog + add function…
spencerpauly Sep 7, 2021
1d91061
Add spacing to variant radio buttons
spencerpauly Sep 7, 2021
26070e5
Change copy text for variant dialogs
spencerpauly Sep 8, 2021
3d75984
Update ScheduleOverrideCreateDialog.js
spencerpauly Sep 8, 2021
22a12d2
Update cypress tests
spencerpauly Sep 8, 2021
ac62d06
Updated timepicker test
spencerpauly Sep 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions web/src/app/schedules/ScheduleDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ const query = gql`
}
`

export const ScheduleCalendarContext = React.createContext({
onNewTempSched: () => {},
onEditTempSched: () => {},
onDeleteTempSched: () => {},
// ts files infer function signature, need parameter list
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setOverrideDialog: (overrideVal) => {},
overrideDialog: null,
})

export default function ScheduleDetails({ scheduleID }) {
const [showEdit, setShowEdit] = useState(false)
const [showDelete, setShowDelete] = useState(false)
Expand All @@ -43,6 +53,7 @@ export default function ScheduleDetails({ scheduleID }) {
const onNewTempSched = useCallback(() => setConfigTempSchedule(true), [])
const onEditTempSched = useCallback(setConfigTempSchedule, [])
const onDeleteTempSched = useCallback(setDeleteTempSchedule, [])
const [overrideDialog, setOverrideDialog] = useState(null)

const {
data: _data,
Expand Down Expand Up @@ -96,12 +107,17 @@ export default function ScheduleDetails({ scheduleID }) {
subheader={`Time Zone: ${data.timeZone || 'Loading...'}`}
details={data.description}
pageContent={
<ScheduleCalendarQuery
scheduleID={scheduleID}
onNewTempSched={onNewTempSched}
onEditTempSched={onEditTempSched}
onDeleteTempSched={onDeleteTempSched}
/>
<ScheduleCalendarContext.Provider
value={{
onNewTempSched,
onEditTempSched,
onDeleteTempSched,
setOverrideDialog,
overrideDialog,
}}
>
<ScheduleCalendarQuery scheduleID={scheduleID} />
</ScheduleCalendarContext.Provider>
}
primaryActions={[
<CalendarSubscribeButton
Expand Down
40 changes: 29 additions & 11 deletions web/src/app/schedules/ScheduleOverrideCreateDialog.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import React, { useState } from 'react'
import { gql, useMutation } from '@apollo/client'
import p from 'prop-types'

import FormDialog from '../dialogs/FormDialog'
import { DateTime } from 'luxon'
import ScheduleOverrideForm from './ScheduleOverrideForm'
import { fieldErrors, nonFieldErrors } from '../util/errutil'
import useOverrideNotices from './useOverrideNotices'

const copyText = {
export const variantDetails = {
add: {
title: 'Temporarily Add a User',
desc: 'This will add a new shift for the selected user, while the override is active. Existing shifts will remain unaffected.',
title: 'Temporarily Add User',
desc: 'Create a new shift for the selected user. Existing shifts will remain unaffected.',
name: 'Additional Coverage',
helperText: 'Add an additional on-call user for a given time frame.',
},
remove: {
title: 'Temporarily Remove a User',
desc: 'This will remove (or split/shorten) shifts belonging to the selected user, while the override is active.',
title: 'Temporarily Remove User',
desc: 'Remove (or split/shorten) shifts belonging to the selected user during the specified time frame.',
name: 'Remove Coverage',
helperText: "Remove one user's shifts during a given time frame.",
},
replace: {
title: 'Temporarily Replace a User',
desc: 'This will replace the selected user with another during any existing shifts, while the override is active. No new shifts will be created, only who is on-call will be changed.',
title: 'Temporarily Replace User',
desc: 'Select one user to take over the shifts of another user during the specified time frame.',
name: "Cover Someone's Shifts",
helperText:
'Have one user take over the shifts of another user during a given time frame.',
},
temp: {
title: 'Create Temporary Schedule',
desc: 'Replace the entire schedule for a given period of time.',
name: 'Temporary Schedule',
helperText:
'Define a fixed shift-by-shift schedule to use for a given time frame.',
},
choose: {
title: 'Choose Override Action',
desc: 'Select the type of override you would like to apply to this schedule.',
name: 'Choose',
helperText: 'Determine which override action you want to take.',
},
}

Expand All @@ -30,7 +49,6 @@ const mutation = gql`
}
}
`

export default function ScheduleOverrideCreateDialog(props) {
const [value, setValue] = useState({
addUserID: '',
Expand All @@ -55,8 +73,8 @@ export default function ScheduleOverrideCreateDialog(props) {
return (
<FormDialog
onClose={props.onClose}
title={copyText[props.variant].title}
subTitle={copyText[props.variant].desc}
title={variantDetails[props.variant].title}
subTitle={variantDetails[props.variant].desc}
errors={nonFieldErrors(error)}
notices={notices} // create and edit dialogue
onSubmit={() => mutate()}
Expand Down
88 changes: 41 additions & 47 deletions web/src/app/schedules/calendar/ScheduleCalendar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useContext } from 'react'
import { PropTypes as p } from 'prop-types'
import { Card, Button, makeStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid'
Expand All @@ -7,11 +7,8 @@ import Switch from '@material-ui/core/Switch'
import Typography from '@material-ui/core/Typography'
import { Calendar } from 'react-big-calendar'
import 'react-big-calendar/lib/css/react-big-calendar.css'
import ScheduleCalendarEventWrapper, {
EventHandlerContext,
} from './ScheduleCalendarEventWrapper'
import ScheduleCalendarEventWrapper from './ScheduleCalendarEventWrapper'
import ScheduleCalendarToolbar from './ScheduleCalendarToolbar'
import ScheduleOverrideCreateDialog from '../ScheduleOverrideCreateDialog'
import { useResetURLParams, useURLParam } from '../../actions'
import { DateTime, Interval } from 'luxon'
import { theme } from '../../mui'
Expand All @@ -23,6 +20,8 @@ import FilterContainer from '../../util/FilterContainer'
import { UserSelect } from '../../selection'
import SpinContainer from '../../loading/components/SpinContainer'
import { useCalendarNavigation } from './hooks'
import { ScheduleCalendarContext } from '../ScheduleDetails'
import ScheduleCalendarOverrideDialog from '../calendar/ScheduleCalendarOverrideDialog'

const localizer = LuxonLocalizer(DateTime, { firstDayOfWeek: 0 })

Expand All @@ -40,20 +39,18 @@ const useStyles = makeStyles((theme) => ({

function ScheduleCalendar(props) {
const classes = useStyles()

const { overrideDialog, setOverrideDialog } = useContext(
ScheduleCalendarContext,
)

const { weekly, start } = useCalendarNavigation()

const [overrideDialog, setOverrideDialog] = useState(null)
const [activeOnly, setActiveOnly] = useURLParam('activeOnly', false)
const [userFilter, setUserFilter] = useURLParam('userFilter', [])
const resetFilter = useResetURLParams('userFilter', 'activeOnly')

const {
shifts,
temporarySchedules,
onNewTempSched,
onEditTempSched,
onDeleteTempSched,
} = props
const { shifts, temporarySchedules } = props

const eventStyleGetter = (event, start, end, isSelected) => {
if (event.fixed) {
Expand Down Expand Up @@ -170,55 +167,52 @@ function ScheduleCalendar(props) {
<Button
variant='contained'
color='primary'
data-cy='new-temp-sched'
onClick={onNewTempSched}
data-cy='new-override'
onClick={() =>
setOverrideDialog({
variantOptions: ['replace', 'remove', 'add', 'temp'],
removeUserReadOnly: false,
})
}
className={classes.tempSchedBtn}
startIcon={<GroupAdd />}
title='Make temporary change to schedule'
>
Temp Sched
Override
</Button>
}
/>
<SpinContainer loading={props.loading}>
<EventHandlerContext.Provider
value={{
onEditTempSched,
onDeleteTempSched,
onOverrideClick: setOverrideDialog,
<Calendar
date={DateTime.fromISO(start).toJSDate()}
localizer={localizer}
events={getCalEvents(shifts, temporarySchedules)}
style={{
height: weekly ? '100%' : '45rem',
fontFamily: theme.typography.body2.fontFamily,
fontSize: theme.typography.body2.fontSize,
}}
>
<Calendar
date={DateTime.fromISO(start).toJSDate()}
localizer={localizer}
events={getCalEvents(shifts, temporarySchedules)}
style={{
height: weekly ? '100%' : '45rem',
fontFamily: theme.typography.body2.fontFamily,
fontSize: theme.typography.body2.fontSize,
}}
tooltipAccessor={() => null}
views={['month', 'week']}
view={weekly ? 'week' : 'month'}
showAllEvents
eventPropGetter={eventStyleGetter}
onNavigate={() => {}} // stub to hide false console err
onView={() => {}} // stub to hide false console err
components={{
eventWrapper: ScheduleCalendarEventWrapper,
toolbar: () => null,
}}
/>
</EventHandlerContext.Provider>
tooltipAccessor={() => null}
views={['month', 'week']}
view={weekly ? 'week' : 'month'}
showAllEvents
eventPropGetter={eventStyleGetter}
onNavigate={() => {}} // stub to hide false console err
onView={() => {}} // stub to hide false console err
components={{
eventWrapper: ScheduleCalendarEventWrapper,
toolbar: () => null,
}}
/>
</SpinContainer>
</Card>
{Boolean(overrideDialog) && (
<ScheduleOverrideCreateDialog
<ScheduleCalendarOverrideDialog
defaultValue={overrideDialog.defaultValue}
variant={overrideDialog.variant}
variantOptions={overrideDialog.variantOptions}
scheduleID={props.scheduleID}
onClose={() => setOverrideDialog(null)}
removeUserReadOnly
removeUserReadOnly={overrideDialog.removeUserReadOnly}
/>
)}
</React.Fragment>
Expand Down
37 changes: 11 additions & 26 deletions web/src/app/schedules/calendar/ScheduleCalendarEventWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Popover from '@material-ui/core/Popover'
import Typography from '@material-ui/core/Typography'
import { makeStyles } from '@material-ui/core'
import { DateTime } from 'luxon'
import { ScheduleCalendarContext } from '../ScheduleDetails'

const useStyles = makeStyles({
button: {
Expand All @@ -26,17 +27,12 @@ const useStyles = makeStyles({
},
})

export const EventHandlerContext = React.createContext({
onOverrideClick: () => {},
onEditTempSched: () => {},
onDeleteTempSched: () => {},
})

export default function ScheduleCalendarEventWrapper({ children, event }) {
const classes = useStyles()
const [anchorEl, setAnchorEl] = useState(null)
const { onOverrideClick, onEditTempSched, onDeleteTempSched } =
useContext(EventHandlerContext)
const { setOverrideDialog, onEditTempSched, onDeleteTempSched } = useContext(
ScheduleCalendarContext,
)
const open = Boolean(anchorEl)
const id = open ? 'shift-popover' : undefined

Expand All @@ -55,11 +51,12 @@ export default function ScheduleCalendarEventWrapper({ children, event }) {
}
}

function handleShowOverrideForm(type) {
function handleShowOverrideForm() {
handleCloseShiftInfo()

onOverrideClick({
variant: type,
setOverrideDialog({
variantOptions: ['replace', 'remove'],
removeUserReadOnly: true,
defaultValue: {
start: event.start.toISOString(),
end: event.end.toISOString(),
Expand Down Expand Up @@ -111,29 +108,17 @@ export default function ScheduleCalendarEventWrapper({ children, event }) {
function renderOverrideButtons() {
return (
<React.Fragment>
<Grid item>
<Button
data-cy='replace-override'
size='small'
onClick={() => handleShowOverrideForm('replace')}
variant='contained'
color='primary'
title={`Temporarily replace ${event.title} from this schedule`}
>
Replace
</Button>
</Grid>
<Grid item className={classes.flexGrow} />
<Grid item>
<Button
data-cy='remove-override'
data-cy='override'
size='small'
onClick={() => handleShowOverrideForm('remove')}
onClick={handleShowOverrideForm}
variant='contained'
color='primary'
title={`Temporarily remove ${event.title} from this schedule`}
>
Remove
Override Shift
</Button>
</Grid>
</React.Fragment>
Expand Down
Loading