Skip to content

Commit 42ce162

Browse files
Use same component for all edits of mission schedule
1 parent 89dd24b commit 42ce162

File tree

6 files changed

+437
-525
lines changed

6 files changed

+437
-525
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
import { Button, Checkbox, Chip, TextField, Typography } from '@equinor/eds-core-react'
2+
import { tokens } from '@equinor/eds-tokens'
3+
import { BackendAPICaller } from 'api/ApiCaller'
4+
import { AlertCategory } from 'components/Alerts/AlertsBanner'
5+
import { FailedRequestAlertContent, FailedRequestAlertListContent } from 'components/Alerts/FailedRequestAlert'
6+
import { AlertType, useAlertContext } from 'components/Contexts/AlertContext'
7+
import { useLanguageContext } from 'components/Contexts/LanguageContext'
8+
import {
9+
ButtonSection,
10+
FormCard,
11+
StyledDialog as MissionStyledDialog,
12+
} from 'components/Pages/MissionDefinitionPage/MissionDefinitionStyledComponents'
13+
import { StyledDialog } from 'components/Styles/StyledComponents'
14+
import { config } from 'config'
15+
import { allDays, DaysOfWeek } from 'models/AutoScheduleFrequency'
16+
import { MissionDefinition } from 'models/MissionDefinition'
17+
import { MissionDefinitionUpdateForm } from 'models/MissionDefinitionUpdateForm'
18+
import { ChangeEvent, useState } from 'react'
19+
import { useNavigate } from 'react-router-dom'
20+
import styled from 'styled-components'
21+
import { formulateAutoScheduleFrequencyAsString } from 'utils/language'
22+
23+
const StyledSummary = styled.div`
24+
padding: 16px 8px 0px 8px;
25+
border-top: 1px solid ${tokens.colors.interactive.disabled__border.hex};
26+
`
27+
28+
const StyledFormCard = styled(FormCard)`
29+
max-width: 340px;
30+
`
31+
32+
const StyledSelectSection = styled.div`
33+
display: flex;
34+
flex-direction: column;
35+
gap: 8px;
36+
padding: 8px;
37+
`
38+
const StyledDaySelector = styled.div`
39+
display: flex;
40+
flex-direction: row;
41+
gap: 2px;
42+
`
43+
const DayButton = styled(Button)`
44+
width: 42px;
45+
`
46+
const StyledTimeSelector = styled.div`
47+
display: flex;
48+
flex-direction: row;
49+
align-items: end;
50+
gap: 8px;
51+
`
52+
const StyledTimeChips = styled.div`
53+
display: flex;
54+
flex-direction: row;
55+
gap: 8px;
56+
`
57+
58+
const useMissionUpdater = () => {
59+
const { TranslateText } = useLanguageContext()
60+
const { setAlert, setListAlert } = useAlertContext()
61+
const navigate = useNavigate()
62+
63+
const updateMission = (
64+
mission: MissionDefinition,
65+
partialForm: Partial<MissionDefinitionUpdateForm>,
66+
onSuccess: () => void
67+
) => {
68+
const defaultForm: MissionDefinitionUpdateForm = {
69+
comment: mission.comment,
70+
autoScheduleFrequency: mission.autoScheduleFrequency,
71+
name: mission.name,
72+
isDeprecated: false,
73+
}
74+
const form: MissionDefinitionUpdateForm = { ...defaultForm, ...partialForm }
75+
76+
BackendAPICaller.updateMissionDefinition(mission.id, form)
77+
.then((missionDefinition) => {
78+
onSuccess()
79+
if (missionDefinition.isDeprecated) navigate(`${config.FRONTEND_BASE_ROUTE}/front-page`)
80+
})
81+
.catch(() => {
82+
setAlert(
83+
AlertType.RequestFail,
84+
<FailedRequestAlertContent translatedMessage={TranslateText('Failed to update inspection')} />,
85+
AlertCategory.ERROR
86+
)
87+
setListAlert(
88+
AlertType.RequestFail,
89+
<FailedRequestAlertListContent translatedMessage={TranslateText('Failed to update inspection')} />,
90+
AlertCategory.ERROR
91+
)
92+
})
93+
}
94+
95+
return updateMission
96+
}
97+
98+
interface MissionEditDialogProps {
99+
mission: MissionDefinition
100+
isOpen: boolean
101+
onClose: () => void
102+
}
103+
104+
export const MissionSchedulingEditDialog = ({ mission, isOpen, onClose }: MissionEditDialogProps) => {
105+
const { TranslateText } = useLanguageContext()
106+
const updateMission = useMissionUpdater()
107+
108+
const [days, setDays] = useState<DaysOfWeek[]>(mission.autoScheduleFrequency?.daysOfWeek ?? [])
109+
const [timesOfDay, setTimesOfDay] = useState<string[]>(mission.autoScheduleFrequency?.timesOfDayCET ?? [])
110+
111+
const onSubmit = () => {
112+
updateMission(
113+
mission,
114+
{
115+
autoScheduleFrequency: {
116+
daysOfWeek: days,
117+
timesOfDayCET: timesOfDay,
118+
},
119+
},
120+
onClose
121+
)
122+
}
123+
124+
return (
125+
<>
126+
<StyledDialog open={isOpen}>
127+
<StyledDialog.Header>
128+
<StyledDialog.Title>
129+
<Typography variant="h3">
130+
{TranslateText('Edit auto scheduling of mission') + ' ' + mission.name}
131+
</Typography>
132+
</StyledDialog.Title>
133+
</StyledDialog.Header>
134+
<FormCard>
135+
<SelectDaysOfWeek currentAutoScheduleDays={days} changedAutoScheduleDays={setDays} />
136+
<SelectTimesOfDay currentAutoScheduleTimes={timesOfDay} changedAutoScheduleTimes={setTimesOfDay} />
137+
<StyledSummary>
138+
{days.length === 0 && (
139+
<Typography color="warning">
140+
{TranslateText('No days have been selected. Please select days')}
141+
</Typography>
142+
)}
143+
{timesOfDay.length === 0 && (
144+
<Typography color="warning">
145+
{TranslateText('No times have been selected. Please add time')}
146+
</Typography>
147+
)}
148+
{days.length > 0 && timesOfDay.length > 0 && (
149+
<Typography>
150+
{formulateAutoScheduleFrequencyAsString(
151+
{ daysOfWeek: days, timesOfDayCET: timesOfDay },
152+
TranslateText
153+
)}
154+
</Typography>
155+
)}
156+
</StyledSummary>
157+
<ButtonSection>
158+
<Button onClick={onClose} variant="outlined" color="primary">
159+
{TranslateText('Cancel')}
160+
</Button>
161+
<Button
162+
onClick={onSubmit}
163+
disabled={days.length === 0 || timesOfDay.length === 0}
164+
variant="contained"
165+
color="primary"
166+
>
167+
{TranslateText('Update')}
168+
</Button>
169+
</ButtonSection>
170+
</FormCard>
171+
</StyledDialog>
172+
</>
173+
)
174+
}
175+
176+
export const MissionNameEditDialog = ({ mission, isOpen, onClose }: MissionEditDialogProps) => {
177+
const { TranslateText } = useLanguageContext()
178+
const updateMission = useMissionUpdater()
179+
180+
const [name, setName] = useState<string>(mission.name)
181+
182+
const onSubmit = () => {
183+
updateMission(mission, { name: name }, onClose)
184+
}
185+
186+
return (
187+
<MissionStyledDialog open={isOpen}>
188+
<StyledFormCard>
189+
<Typography variant="h2">{TranslateText('Edit') + ' ' + TranslateText('name')}</Typography>
190+
<TextField
191+
id="nameEdit"
192+
value={name}
193+
label={TranslateText('Name')}
194+
onChange={(changes: ChangeEvent<HTMLInputElement>) => setName(changes.target.value)}
195+
/>
196+
<ButtonSection>
197+
<Button onClick={onClose} variant="outlined" color="primary">
198+
{TranslateText('Cancel')}
199+
</Button>
200+
<Button onClick={onSubmit} disabled={!name} variant="contained" color="primary">
201+
{TranslateText('Update')}
202+
</Button>
203+
</ButtonSection>
204+
</StyledFormCard>
205+
</MissionStyledDialog>
206+
)
207+
}
208+
209+
export const MissionCommentEditDialog = ({ mission, isOpen, onClose }: MissionEditDialogProps) => {
210+
const { TranslateText } = useLanguageContext()
211+
const updateMission = useMissionUpdater()
212+
213+
const [comment, setComment] = useState<string>(mission.comment ?? '')
214+
215+
const onSubmit = () => {
216+
updateMission(mission, { comment: comment }, onClose)
217+
}
218+
219+
return (
220+
<MissionStyledDialog open={isOpen}>
221+
<StyledFormCard>
222+
<Typography variant="h2">{TranslateText('Edit') + ' ' + TranslateText('comment')}</Typography>
223+
<TextField
224+
id="commentEdit"
225+
multiline
226+
rows={2}
227+
label={TranslateText('Comment')}
228+
value={comment}
229+
onChange={(changes: ChangeEvent<HTMLInputElement>) => setComment(changes.target.value)}
230+
/>
231+
<ButtonSection>
232+
<Button onClick={onClose} variant="outlined" color="primary">
233+
{TranslateText('Cancel')}
234+
</Button>
235+
<Button onClick={onSubmit} variant="contained" color="primary">
236+
{TranslateText('Update')}
237+
</Button>
238+
</ButtonSection>
239+
</StyledFormCard>
240+
</MissionStyledDialog>
241+
)
242+
}
243+
244+
interface SelectDaysOfWeekProps {
245+
currentAutoScheduleDays: DaysOfWeek[]
246+
changedAutoScheduleDays: (newAutoScheduleDays: DaysOfWeek[]) => void
247+
}
248+
249+
const SelectDaysOfWeek = ({ currentAutoScheduleDays, changedAutoScheduleDays }: SelectDaysOfWeekProps) => {
250+
const { TranslateText } = useLanguageContext()
251+
252+
const isAllSelected = currentAutoScheduleDays?.length === allDays.length
253+
254+
const selectAll = () => {
255+
changedAutoScheduleDays(allDays)
256+
}
257+
258+
const unselectAll = () => {
259+
changedAutoScheduleDays([])
260+
}
261+
262+
const addDay = (day: DaysOfWeek) => {
263+
if (currentAutoScheduleDays?.includes(day)) return
264+
changedAutoScheduleDays([...currentAutoScheduleDays, day])
265+
}
266+
267+
const removeDay = (day: DaysOfWeek) => {
268+
changedAutoScheduleDays(currentAutoScheduleDays.filter((d) => d !== day))
269+
}
270+
271+
const OneLetterDayButton = ({ day }: { day: DaysOfWeek }) => {
272+
const isDaySelected = currentAutoScheduleDays.includes(day)
273+
return (
274+
<DayButton
275+
variant={isDaySelected ? 'contained' : 'outlined'}
276+
onClick={() => (isDaySelected ? removeDay(day) : addDay(day))}
277+
>
278+
{TranslateText('short'.concat(day.toString()))}
279+
</DayButton>
280+
)
281+
}
282+
283+
return (
284+
<StyledSelectSection>
285+
<Typography variant="h6">{TranslateText('Select days of the week')}</Typography>
286+
<StyledDaySelector>
287+
{Object.entries(allDays).map(([key, value]) => (
288+
<OneLetterDayButton key={key} day={value} />
289+
))}
290+
</StyledDaySelector>
291+
<Checkbox
292+
label={TranslateText('Select all days')}
293+
checked={isAllSelected}
294+
onChange={isAllSelected ? unselectAll : selectAll}
295+
/>
296+
</StyledSelectSection>
297+
)
298+
}
299+
300+
interface SelectTimesOfDayProps {
301+
currentAutoScheduleTimes: string[]
302+
changedAutoScheduleTimes: (newAutoScheduleTimes: string[]) => void
303+
}
304+
305+
const SelectTimesOfDay = ({ currentAutoScheduleTimes, changedAutoScheduleTimes }: SelectTimesOfDayProps) => {
306+
const { TranslateText } = useLanguageContext()
307+
const [selectedTime, setSelectedTime] = useState<string>() // Format HH:MM:ss
308+
309+
const removeTime = (time: string) => {
310+
changedAutoScheduleTimes([...currentAutoScheduleTimes.filter((t) => t !== time)])
311+
}
312+
313+
const addTime = () => {
314+
if (!selectedTime) return
315+
316+
if (selectedTime && !currentAutoScheduleTimes.includes(selectedTime))
317+
changedAutoScheduleTimes([...currentAutoScheduleTimes, selectedTime])
318+
}
319+
320+
const formatTimeToDisplay = (time: string) => {
321+
return time.substring(0, 5)
322+
}
323+
324+
const formatAsTimeOnly = (time: string) => {
325+
return time.concat(':00')
326+
}
327+
328+
const SelectedTimeChips = () => (
329+
<StyledTimeChips>
330+
{currentAutoScheduleTimes
331+
.sort((a, b) => (a === b ? 0 : a > b ? 1 : -1))
332+
.map((time) => (
333+
<Chip key={time} onDelete={() => removeTime(time)}>
334+
{formatTimeToDisplay(time)}
335+
</Chip>
336+
))}
337+
</StyledTimeChips>
338+
)
339+
340+
return (
341+
<StyledSelectSection>
342+
<Typography variant="h6"> {TranslateText('Select times of the day')}</Typography>
343+
<Typography variant="meta">{TranslateText('Add start time')}</Typography>
344+
<StyledTimeSelector>
345+
<TextField
346+
id="time"
347+
type="time"
348+
onChange={(changes: ChangeEvent<HTMLInputElement>) =>
349+
setSelectedTime(formatAsTimeOnly(changes.target.value))
350+
}
351+
/>
352+
<Button onClick={addTime} disabled={selectedTime === undefined}>
353+
{TranslateText('Add time')}
354+
</Button>
355+
</StyledTimeSelector>
356+
<Typography>{TranslateText('Selected start times')}</Typography>
357+
<SelectedTimeChips />
358+
</StyledSelectSection>
359+
)
360+
}

0 commit comments

Comments
 (0)