parent
0950f264e8
commit
4f548d529f
13 changed files with 543 additions and 160 deletions
@ -0,0 +1,237 @@ |
|||||||
|
import { utc, duration, ISO_8601 } from "moment"; |
||||||
|
import React, { Component } from "react"; |
||||||
|
import { connect } from "react-redux"; |
||||||
|
import { Redirect } from "react-router-dom"; |
||||||
|
import { Header, Label, Loader } from "semantic-ui-react"; |
||||||
|
|
||||||
|
import { |
||||||
|
setFormEmployeeUUID, |
||||||
|
setFormPriceUUID, |
||||||
|
setFormShiftStartTime, |
||||||
|
setFormShiftDuration, |
||||||
|
setFormShiftNote, |
||||||
|
setClearCShiftState, |
||||||
|
setFormShiftDates, |
||||||
|
setCShiftUUID |
||||||
|
} from "../../../actions/cShift/reducer.actions"; |
||||||
|
import { |
||||||
|
getCShiftRequest, |
||||||
|
editCShiftRequest |
||||||
|
} from "../../../actions/cShift/saga.actions"; |
||||||
|
import { ClientShiftFormView } from "./ClientShiftFormView"; |
||||||
|
import { getEmployeeFromPrice } from "./ClientShiftShared"; |
||||||
|
|
||||||
|
class ClientEditShiftForm extends Component { |
||||||
|
constructor(props) { |
||||||
|
super(props); |
||||||
|
|
||||||
|
const { cShiftRequestSuccess, selfUser } = this.props; |
||||||
|
if (selfUser.client && cShiftRequestSuccess.uuid) { |
||||||
|
const employee = getEmployeeFromPrice( |
||||||
|
cShiftRequestSuccess.price, |
||||||
|
selfUser |
||||||
|
); |
||||||
|
const startTime = utc(cShiftRequestSuccess.set_start, ISO_8601).local(); |
||||||
|
const endTime = utc(cShiftRequestSuccess.set_end, ISO_8601).local(); |
||||||
|
this.props.dispatch(setCShiftUUID(cShiftRequestSuccess.uuid)); |
||||||
|
this.props.dispatch(setFormShiftDates([startTime])); |
||||||
|
this.props.dispatch(setFormShiftStartTime(startTime)); |
||||||
|
this.props.dispatch( |
||||||
|
setFormShiftDuration( |
||||||
|
duration(endTime - startTime, "milliseconds").as("minutes") |
||||||
|
) |
||||||
|
); |
||||||
|
this.props.dispatch(setFormEmployeeUUID(employee.uuid)); |
||||||
|
this.props.dispatch(setFormShiftNote(cShiftRequestSuccess.description)); |
||||||
|
this.props.dispatch(setFormPriceUUID(cShiftRequestSuccess.price)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
changeSelectedEmployee = (e, { value }) => { |
||||||
|
if (value !== this.props.employeeUUID) { |
||||||
|
this.props.dispatch(setFormPriceUUID("")); |
||||||
|
} |
||||||
|
this.props.dispatch(setFormEmployeeUUID(value)); |
||||||
|
}; |
||||||
|
|
||||||
|
changeSelectedPrice = (e, { value }) => { |
||||||
|
this.props.dispatch(setFormPriceUUID(value)); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* change handler for shift start time selector. |
||||||
|
* @param momentTime - instance of moment (but we only care about the time) |
||||||
|
*/ |
||||||
|
changeShiftStartTime = momentTime => { |
||||||
|
this.props.dispatch(setFormShiftStartTime(momentTime)); |
||||||
|
}; |
||||||
|
|
||||||
|
changeShiftDuration = (e, { value }) => { |
||||||
|
this.props.dispatch(setFormShiftDuration(value)); |
||||||
|
}; |
||||||
|
|
||||||
|
changeShiftNote = event => { |
||||||
|
this.props.dispatch(setFormShiftNote(event.target.value)); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* change handler for shift date selector |
||||||
|
* @param momentDate - instance of moment (but we only care about the day) |
||||||
|
*/ |
||||||
|
handleSelectDate = momentDate => { |
||||||
|
this.props.dispatch(setFormShiftDates([momentDate])); |
||||||
|
}; |
||||||
|
|
||||||
|
onSubmitShifts = event => { |
||||||
|
event.preventDefault(); |
||||||
|
// change this into interable cshift post request bodies |
||||||
|
const { |
||||||
|
cShiftUUID, |
||||||
|
priceUUID, |
||||||
|
startTime, |
||||||
|
duration, |
||||||
|
note, |
||||||
|
shiftDates |
||||||
|
} = this.props; |
||||||
|
const postRequestBodies = []; |
||||||
|
for (let shiftDateString in shiftDates) { |
||||||
|
const dynamicStartTime = utc(startTime); |
||||||
|
const startDate = shiftDates[shiftDateString]; |
||||||
|
dynamicStartTime.set({ |
||||||
|
year: startDate.get("year"), |
||||||
|
month: startDate.get("month"), |
||||||
|
date: startDate.get("date") |
||||||
|
}); |
||||||
|
const dynamicEndTime = utc(dynamicStartTime); |
||||||
|
dynamicEndTime.add(duration, "minutes"); |
||||||
|
postRequestBodies.push({ |
||||||
|
get_price_uuid: priceUUID, |
||||||
|
set_start: dynamicStartTime.format(), |
||||||
|
set_end: dynamicEndTime.format(), |
||||||
|
description: note ? note : undefined |
||||||
|
}); |
||||||
|
} |
||||||
|
console.log({ ...postRequestBodies[0], uuid: cShiftUUID }) |
||||||
|
this.props.dispatch( |
||||||
|
editCShiftRequest({ ...postRequestBodies[0], uuid: cShiftUUID }) |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
render() { |
||||||
|
const { |
||||||
|
isSendingCShiftRequest, |
||||||
|
cShiftRequestErrors, |
||||||
|
cShiftRequestSuccess, |
||||||
|
selfUser, |
||||||
|
employeeUUID, |
||||||
|
priceUUID, |
||||||
|
startTime, |
||||||
|
duration, |
||||||
|
note, |
||||||
|
shiftDates, |
||||||
|
cShiftUUID |
||||||
|
} = this.props; |
||||||
|
|
||||||
|
if (!selfUser.client) { |
||||||
|
return <Redirect to="/" />; |
||||||
|
} |
||||||
|
if (cShiftUUID === true) { |
||||||
|
return <Redirect to="/user/profile/client/shifts" />; |
||||||
|
} |
||||||
|
|
||||||
|
const employeeChoices = selfUser.client.employees |
||||||
|
// TODO: ugly edit of state changed employee |
||||||
|
.filter(employee => !employee.deleted && !!employee.approved) |
||||||
|
.map(({ uuid, provider }) => ({ |
||||||
|
key: uuid, |
||||||
|
value: uuid, |
||||||
|
text: provider.email |
||||||
|
})); |
||||||
|
|
||||||
|
const priceChoices = []; |
||||||
|
if (employeeUUID) { |
||||||
|
const employee = selfUser.client.employees.find(emp => { |
||||||
|
return emp && emp.uuid === employeeUUID; |
||||||
|
}); |
||||||
|
priceChoices.push( |
||||||
|
...employee.prices |
||||||
|
.filter(price => !price.deleted) |
||||||
|
.map(({ amount, uuid, work_type }) => ({ |
||||||
|
key: uuid, |
||||||
|
value: uuid, |
||||||
|
text: `${work_type.label} ($${amount}/hr)`, |
||||||
|
content: ( |
||||||
|
<Header> |
||||||
|
<Label |
||||||
|
circular |
||||||
|
empty |
||||||
|
style={{ |
||||||
|
backgroundColor: work_type.color, |
||||||
|
borderColor: work_type.color |
||||||
|
}} |
||||||
|
/> |
||||||
|
{work_type.label} |
||||||
|
<Header.Subheader |
||||||
|
style={{ paddingLeft: "2em" }} |
||||||
|
content={`Hourly Rate: ${amount}`} |
||||||
|
/> |
||||||
|
</Header> |
||||||
|
) |
||||||
|
})) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
const selectedShiftDates = Object.keys(shiftDates).map( |
||||||
|
key => shiftDates[key] |
||||||
|
); |
||||||
|
return ( |
||||||
|
<ClientShiftFormView |
||||||
|
isSendingCShiftRequest={isSendingCShiftRequest} |
||||||
|
cShiftRequestErrors={cShiftRequestErrors} |
||||||
|
cShiftRequestSuccess={!!cShiftRequestSuccess.length} |
||||||
|
user={selfUser} |
||||||
|
employeeChoices={employeeChoices} |
||||||
|
priceChoices={priceChoices} |
||||||
|
employeeUUID={employeeUUID} |
||||||
|
priceUUID={priceUUID} |
||||||
|
startTime={startTime} |
||||||
|
duration={duration} |
||||||
|
note={note} |
||||||
|
selectedShiftDates={selectedShiftDates} |
||||||
|
changeSelectedEmployee={this.changeSelectedEmployee} |
||||||
|
changeSelectedPrice={this.changeSelectedPrice} |
||||||
|
changeShiftStartTime={this.changeShiftStartTime} |
||||||
|
changeShiftDuration={this.changeShiftDuration} |
||||||
|
changeShiftNote={this.changeShiftNote} |
||||||
|
handleSelectDate={this.handleSelectDate} |
||||||
|
onSubmitShifts={this.onSubmitShifts} |
||||||
|
isEditing={true} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function mapStateToProps(state) { |
||||||
|
return { ...state.cShift, selfUser: state.user.selfUser }; |
||||||
|
} |
||||||
|
|
||||||
|
class EditClientShiftWrapper extends Component { |
||||||
|
constructor(props) { |
||||||
|
super(props); |
||||||
|
this.props.dispatch(setClearCShiftState()); |
||||||
|
this.props.dispatch( |
||||||
|
getCShiftRequest({ uuid: this.props.match.params.shiftUUID }) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const { cShiftRequestSuccess } = this.props; |
||||||
|
if (cShiftRequestSuccess.uuid) { |
||||||
|
return <ClientEditShiftForm {...this.props} />; |
||||||
|
} else { |
||||||
|
return <Loader />; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EditClientShiftWrapper); |
@ -0,0 +1,159 @@ |
|||||||
|
import { utc, duration } from "moment"; |
||||||
|
import React from "react"; |
||||||
|
import DatePicker from "react-datepicker"; |
||||||
|
import { Redirect } from "react-router-dom"; |
||||||
|
import { |
||||||
|
Container, |
||||||
|
Dropdown, |
||||||
|
Form, |
||||||
|
Header, |
||||||
|
Message, |
||||||
|
TextArea |
||||||
|
} from "semantic-ui-react"; |
||||||
|
|
||||||
|
import Error from "../../Shared/Error"; |
||||||
|
import "react-datepicker/dist/react-datepicker.css"; |
||||||
|
import "./shiftStartTimeOverrides.css"; |
||||||
|
|
||||||
|
const CShiftDurationOptions = []; |
||||||
|
for (let min = 60; min <= 480; min += 30) { |
||||||
|
let displayText = duration(min, "minutes").humanize(); |
||||||
|
if (min % 60) { |
||||||
|
displayText = duration(Math.floor(min / 60), "hours").humanize(); |
||||||
|
displayText += ` and ${duration(min % 60, "minutes").humanize()}`; |
||||||
|
} |
||||||
|
CShiftDurationOptions.push({ |
||||||
|
key: min, |
||||||
|
value: min, |
||||||
|
text: displayText |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export const ClientShiftFormView = ({ |
||||||
|
isSendingCShiftRequest, |
||||||
|
cShiftRequestErrors, |
||||||
|
cShiftRequestSuccess, |
||||||
|
user, |
||||||
|
employeeChoices, |
||||||
|
priceChoices, |
||||||
|
employeeUUID, |
||||||
|
priceUUID, |
||||||
|
startTime, |
||||||
|
duration, |
||||||
|
note, |
||||||
|
selectedShiftDates, |
||||||
|
changeSelectedEmployee, |
||||||
|
changeSelectedPrice, |
||||||
|
changeShiftStartTime, |
||||||
|
changeShiftDuration, |
||||||
|
changeShiftNote, |
||||||
|
handleSelectDate, |
||||||
|
onSubmitShifts, |
||||||
|
isEditing = false |
||||||
|
}) => ( |
||||||
|
<Container> |
||||||
|
<Header> |
||||||
|
{!isEditing && "Schedule Shifts"} |
||||||
|
{isEditing && "Edit Shift"} |
||||||
|
</Header> |
||||||
|
<Form |
||||||
|
loading={isSendingCShiftRequest} |
||||||
|
onSubmit={onSubmitShifts} |
||||||
|
error={!!cShiftRequestErrors.length} |
||||||
|
success={!!cShiftRequestSuccess} |
||||||
|
> |
||||||
|
<Form.Group widths="equal"> |
||||||
|
<Form.Field> |
||||||
|
<label>Employee</label> |
||||||
|
<Dropdown |
||||||
|
onChange={changeSelectedEmployee} |
||||||
|
options={employeeChoices} |
||||||
|
placeholder="Select employee" |
||||||
|
selection |
||||||
|
fluid |
||||||
|
search |
||||||
|
noResultsMessage="No approved employees found." |
||||||
|
value={employeeUUID} |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
<Form.Field> |
||||||
|
<label>Price</label> |
||||||
|
<Dropdown |
||||||
|
onChange={changeSelectedPrice} |
||||||
|
options={priceChoices} |
||||||
|
placeholder="Select price" |
||||||
|
selection |
||||||
|
fluid |
||||||
|
search |
||||||
|
disabled={!employeeUUID} |
||||||
|
noResultsMessage="No prices for given employee." |
||||||
|
value={priceUUID} |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
<Form.Field> |
||||||
|
<label>Shift Start Time</label> |
||||||
|
<DatePicker |
||||||
|
selected={startTime} |
||||||
|
onChange={changeShiftStartTime} |
||||||
|
showTimeSelect |
||||||
|
showTimeSelectOnly |
||||||
|
timeIntervals={30} |
||||||
|
dateFormat="LT Z" |
||||||
|
timeFormat="hh:mm" |
||||||
|
placeholderText="Select shift start time" |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
<Form.Field> |
||||||
|
<label>Shift Duration</label> |
||||||
|
<Dropdown |
||||||
|
onChange={changeShiftDuration} |
||||||
|
options={CShiftDurationOptions} |
||||||
|
placeholder="Select duration" |
||||||
|
selection |
||||||
|
fluid |
||||||
|
value={duration} |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
</Form.Group> |
||||||
|
<Form.Field> |
||||||
|
<label>Note</label> |
||||||
|
<TextArea |
||||||
|
placeholder="Employee notes" |
||||||
|
value={note} |
||||||
|
onChange={changeShiftNote} |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
<Form.Field style={{ textAlign: "center" }}> |
||||||
|
<label>Dates</label> |
||||||
|
<DatePicker |
||||||
|
inline |
||||||
|
onSelect={handleSelectDate} |
||||||
|
highlightDates={selectedShiftDates} |
||||||
|
monthsShown={2} |
||||||
|
// https://github.com/Hacker0x01/react-datepicker/pull/1360 |
||||||
|
peekNextMonth={false} // this is broken? Fixed in PR 1360 |
||||||
|
minDate={utc(new Date()).add(1, "day")} |
||||||
|
maxDate={utc(new Date()) |
||||||
|
.add(1, "month") |
||||||
|
.endOf("month")} |
||||||
|
/> |
||||||
|
</Form.Field> |
||||||
|
{!!cShiftRequestErrors.length && ( |
||||||
|
<Error header="" error={cShiftRequestErrors[0]} /> |
||||||
|
)} |
||||||
|
<Message success> |
||||||
|
<Message.Header>Add Shift successful!</Message.Header> |
||||||
|
<p>Shifts successfully scheduled.</p> |
||||||
|
{!!cShiftRequestSuccess && ( |
||||||
|
<Redirect to="/user/profile/client/shifts" /> |
||||||
|
)} |
||||||
|
</Message> |
||||||
|
<Form.Button> |
||||||
|
{!isEditing && "Schedule Shifts"} |
||||||
|
{isEditing && "Edit Shift"} |
||||||
|
</Form.Button> |
||||||
|
</Form> |
||||||
|
</Container> |
||||||
|
); |
||||||
|
|
||||||
|
export default ClientShiftFormView; |
@ -0,0 +1,13 @@ |
|||||||
|
export const getEmployeeFromPrice = (priceUUID, selfUser) => { |
||||||
|
const employees = selfUser && selfUser.client && selfUser.client.employees; |
||||||
|
let matchEmployee = null; |
||||||
|
employees.forEach(employee => { |
||||||
|
const priceMatch = employee.prices.filter(price => { |
||||||
|
return price.uuid === priceUUID; |
||||||
|
}); |
||||||
|
if (priceMatch.length > 0) { |
||||||
|
matchEmployee = employee; |
||||||
|
} |
||||||
|
}); |
||||||
|
return matchEmployee; |
||||||
|
}; |
Loading…
Reference in new issue