From 631d1f6d399946439b7195982d8156fb667ed2e0 Mon Sep 17 00:00:00 2001 From: Alexander Wong Date: Wed, 7 Feb 2018 15:52:29 -0700 Subject: [PATCH] Functionality to add prices as a client, and view them as a provider --- src/actions/price/reducer.actions.js | 78 ++++++ src/actions/price/saga.actions.js | 26 ++ src/api/price.api.js | 32 +++ src/components/App.jsx | 10 + .../User/Client/ClientProviders.jsx | 100 +++++++- .../User/Client/CreatePriceForm.jsx | 227 ++++++++++++++++++ .../User/Client/UpdatePriceForm.jsx | 184 ++++++++++++++ .../User/Provider/ProviderClients.jsx | 38 +++ src/constants/price.constants.js | 14 ++ src/reducers/index.js | 4 +- src/reducers/priceReducer.js | 68 ++++++ src/sagas/index.js | 13 + src/sagas/price.sagas.js | 85 +++++++ 13 files changed, 875 insertions(+), 4 deletions(-) create mode 100644 src/actions/price/reducer.actions.js create mode 100644 src/actions/price/saga.actions.js create mode 100644 src/api/price.api.js create mode 100644 src/components/User/Client/CreatePriceForm.jsx create mode 100644 src/components/User/Client/UpdatePriceForm.jsx create mode 100644 src/constants/price.constants.js create mode 100644 src/reducers/priceReducer.js create mode 100644 src/sagas/price.sagas.js diff --git a/src/actions/price/reducer.actions.js b/src/actions/price/reducer.actions.js new file mode 100644 index 0000000..ef7d623 --- /dev/null +++ b/src/actions/price/reducer.actions.js @@ -0,0 +1,78 @@ +import { + IS_SENDING_PRICE_REQUEST, + SET_PRICE_REQUEST_ERROR, + CLEAR_PRICE_REQUEST_ERROR, + SET_PRICE_REQUEST_SUCCESS, + CLEAR_PRICE_REQUEST_SUCCESS, + SET_GET_EMPLOYEE_UUID, + SET_GET_WORKTYPE_UUID, + SET_FORM_PRICE_AMOUNT +} from "../../constants/price.constants"; +import { parseError } from "../common.actions"; + +export function isSendingPriceRequest(sendingRequest) { + return { + type: IS_SENDING_PRICE_REQUEST, + data: sendingRequest + }; +} + +export function setPriceRequestError(exception) { + let error = parseError(exception); + if (error.amount) { + error["Amount"] = error.amount; + delete error["amount"]; + } + if (error.get_employee_uuid) { + error["Employee"] = error.get_employee_uuid; + delete error["get_employee_uuid"]; + } + if (error.get_work_type_uuid) { + error["WorkType"] = error.get_work_type_uuid; + delete error["get_work_type_uuid"]; + } + return { + type: SET_PRICE_REQUEST_ERROR, + data: error + }; +} + +export function clearPriceRequestError() { + return { + type: CLEAR_PRICE_REQUEST_ERROR + }; +} + +export function setPriceRequestSuccess(response) { + return { + type: SET_PRICE_REQUEST_SUCCESS, + data: response.detail || response + }; +} + +export function clearPriceRequestSuccess() { + return { + type: CLEAR_PRICE_REQUEST_SUCCESS + }; +} + +export function setGetEmployeeUUID(uuid) { + return { + type: SET_GET_EMPLOYEE_UUID, + data: uuid + }; +} + +export function setGetWorktypeUUID(uuid) { + return { + type: SET_GET_WORKTYPE_UUID, + data: uuid + }; +} + +export function setFormPriceAmount(amount) { + return { + type: SET_FORM_PRICE_AMOUNT, + data: amount + }; +} diff --git a/src/actions/price/saga.actions.js b/src/actions/price/saga.actions.js new file mode 100644 index 0000000..bd92a88 --- /dev/null +++ b/src/actions/price/saga.actions.js @@ -0,0 +1,26 @@ +import { + CREATE_PRICE_REQUEST, + UPDATE_PRICE_REQUEST, + DELETE_PRICE_REQUEST +} from "../../constants/price.constants"; + +export function createPriceRequest(postBody) { + return { + type: CREATE_PRICE_REQUEST, + data: postBody + }; +} + +export function updatePriceRequest(payload) { + return { + type: UPDATE_PRICE_REQUEST, + data: payload + }; +} + +export function deletePriceRequest(payload) { + return { + type: DELETE_PRICE_REQUEST, + data: payload + }; +} diff --git a/src/api/price.api.js b/src/api/price.api.js new file mode 100644 index 0000000..e81bd46 --- /dev/null +++ b/src/api/price.api.js @@ -0,0 +1,32 @@ +import { post, patch, del } from "./baseApi"; + +/** + * Function wrapping POST request for creating prices + * @param {string} get_employee_uuid - UUID of the employee + * @param {string} get_work_type_uuid - UUID of the work type + * @param {double} amount - amount rate per hour + */ +export function createPrice(get_employee_uuid, get_work_type_uuid, amount) { + return post("/price/", { + get_employee_uuid, + get_work_type_uuid, + amount + }).then(resp => Promise.resolve(resp)); +} + +/** + * Function wrapping PATCH request for updating prices + * @param {string} uuid - UUID of the price + * @param {double} amount - new amount rate per hour + */ +export function updatePrice(uuid, amount) { + return patch(`/price/${uuid}/`, { amount }).then(resp => Promise.resolve(resp)); +} + +/** + * Function wrapping DELETE request for deleting the price + * @param {string} uuid - UUID of the price + */ +export function deletePrice(uuid) { + return del(`/price/${uuid}/`).then(resp => Promise.resolve(resp)); +} diff --git a/src/components/App.jsx b/src/components/App.jsx index bf45cd1..1afbf93 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -11,6 +11,8 @@ import CreateWorkTypeForm from "./Worktype/CreateWorkTypeForm"; import UpdateWorkTypeForm from "./Worktype/UpdateWorkTypeForm"; import Worktypes from "./Worktype/Worktypes"; import ClientProviders from "./User/Client/ClientProviders"; +import CreatePriceForm from "./User/Client/CreatePriceForm"; +import UpdatePriceForm from "./User/Client/UpdatePriceForm"; import ClientAddProviderForm from "./User/Client/ClientAddProviderForm"; import ProviderClients from "./User/Provider/ProviderClients"; import CompleteRegistration from "./User/CompleteRegistration"; @@ -64,6 +66,14 @@ class App extends Component { path="/user/profile/client/create-worktype" component={CreateWorkTypeForm} /> + + { this.props.dispatch(deleteEmployeeRequest(uuid)); }; + deletePrice = uuid => { + this.props.dispatch(deletePriceRequest(uuid)); + }; + render() { const { selfUser } = this.props; if (selfUser.client) { @@ -25,6 +31,7 @@ class ClientProviders extends Component { ); } else { @@ -37,7 +44,7 @@ function mapStateToProps(state) { return { selfUser: state.user.selfUser }; } -const ClientProvidersView = ({ user, deleteEmployee }) => ( +const ClientProvidersView = ({ user, deleteEmployee, deletePrice }) => (
Providers
@@ -62,13 +69,13 @@ const ClientProvidersView = ({ user, deleteEmployee }) => ( {(employee.approved === null || employee.approved === false) && (
- {employee.provider} + {employee.provider} {employee.note}
)} {employee.approved && (
- + {`${employee.provider.first_name} ${ employee.provider.last_name }`.trim() || "No Name!"} @@ -100,6 +107,93 @@ const ClientProvidersView = ({ user, deleteEmployee }) => ( : !!employee.approved ? "Approved" : "Ended"} + {employee.approved && ( + + + + Assigned Work{" "} + + + + {employee.prices + .filter(price => !price.deleted) + .map((price, index) => ( + + + + + + Are you sure you want to delete this work?
+ +
+ } + trigger={ + + } + on="click" + position="top right" + /> + + + + + + Hourly Rate: ${price.amount} + + + ))} + {employee.prices.filter(price => !price.deleted) + .length === 0 && ( + + No Prices for this Provider + + )} + + + + )} !worktype.deleted) + .map(worktype => ({ + key: worktype.uuid, + text: worktype.label, + color: worktype.color, + value: worktype.uuid, + content: ( + + + ) + })); + + // Get referenced employee + const employeeUUID = this.props.match.params.providerUUID; + const employees = ((selfUser || {}).client || {}).employees || []; + const employee = + employees.filter(employee => employee.uuid === employeeUUID)[0] || null; + this.props.dispatch(setGetEmployeeUUID((employee || {}).uuid || "")); + this.state = { + workTypeOptions, + employee + }; + } + + componentWillMount = () => { + this.props.dispatch(clearPriceRequestError()); + this.props.dispatch(clearPriceRequestSuccess()); + }; + + changeWorkType = (e, data) => { + this.props.dispatch(setGetWorktypeUUID(data.value)); + }; + + changeAmount = event => { + this.props.dispatch(setFormPriceAmount(event.target.value)); + }; + + onSubmitPrice = event => { + event.preventDefault(); + const { getEmployeeUUID, getWorktypeUUID, amount } = this.props; + this.props.dispatch( + createPriceRequest({ + get_employee_uuid: getEmployeeUUID, + get_work_type_uuid: getWorktypeUUID, + amount + }) + ); + }; + + render() { + const { + isSendingPriceRequest, + priceRequestError, + priceRequestSuccess, + getEmployeeUUID, + getWorktypeUUID, + amount, + selfUser + } = this.props; + const { employee, workTypeOptions } = this.state; + + if (!selfUser.client) { + return ; + } + + return ( + + ); + } +} + +const CreatePriceFormView = ({ + isSendingPriceRequest, + priceRequestError, + priceRequestSuccess, + employee, + workTypeOptions, + getEmployeeUUID, + getWorktypeUUID, + amount, + changeWorkType, + changeAmount, + onSubmitPrice +}) => ( + +
Assign Work
+ {employee && ( + + Employee Information + + UUID: {employee.uuid} + + Provider Email: {(employee.provider || {}).email || "No Email!"} + + + Provider Name:{" "} + {`${(employee.provider || {}).first_name} ${ + (employee.provider || {}).last_name + }`.trim() || "No Name!"} + + + Provider Phone Number:{" "} + {`${((employee.provider || {}).userinfo || {}).phone_number || + "No Number!"}`} + + Approved: {"" + employee.approved} + Deleted: {"" + employee.deleted} + + + )} + {!employee && ( + + Invalid Employee +

No accessable employee exists for given UUID.

+
+ )} + {!workTypeOptions.length && ( + + No Work Types +

+ No worktypes exists. Please{" "} + + create some work types + . +

+
+ )} +
+ + + + + + + + + + + + + + Create Price successful! +

Price successfully created.

+ {!!priceRequestSuccess && ( + + )} +
+ Submit Price + +
+); + +function mapStateToProps(state) { + return { ...state.price, selfUser: state.user.selfUser }; +} + +export default connect(mapStateToProps)(CreatePriceForm); diff --git a/src/components/User/Client/UpdatePriceForm.jsx b/src/components/User/Client/UpdatePriceForm.jsx new file mode 100644 index 0000000..e0c0aac --- /dev/null +++ b/src/components/User/Client/UpdatePriceForm.jsx @@ -0,0 +1,184 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import { Redirect } from "react-router-dom"; +import { + Container, + Form, + Header, + Input, + Label, + Message +} from "semantic-ui-react"; +import { + clearPriceRequestError, + clearPriceRequestSuccess, + setFormPriceAmount +} from "../../../actions/price/reducer.actions"; +import { updatePriceRequest } from "../../../actions/price/saga.actions"; +import Error from "../../Shared/Error"; + +class UpdatePriceForm extends Component { + constructor(props) { + super(props); + + // Get referenced employee + const { selfUser } = props; + const employeeUUID = this.props.match.params.providerUUID; + const employees = ((selfUser || {}).client || {}).employees || []; + const employee = + employees.filter(employee => employee.uuid === employeeUUID)[0] || null; + + // Get referenced price + const priceUUID = this.props.match.params.priceUUID; + const price = + (employee.prices || []).filter( + price => price.uuid === priceUUID && !price.deleted + )[0] || null; + this.props.dispatch(setFormPriceAmount((price || {}).amount || "")); + this.state = { + price, + employee + }; + } + + componentWillMount = () => { + this.props.dispatch(clearPriceRequestError()); + this.props.dispatch(clearPriceRequestSuccess()); + }; + + changeAmount = event => { + this.props.dispatch(setFormPriceAmount(event.target.value)); + }; + + onSubmitPrice = event => { + event.preventDefault(); + const { amount } = this.props; + const { price } = this.state; + this.props.dispatch( + updatePriceRequest({ uuid: (price || {}).uuid, amount }) + ); + }; + + render() { + const { + isSendingPriceRequest, + priceRequestError, + priceRequestSuccess, + amount, + selfUser + } = this.props; + const { employee, price } = this.state; + + if (!selfUser.client) { + return ; + } + + return ( + + ); + } +} + +const UpdatePriceFormView = ({ + isSendingPriceRequest, + priceRequestError, + priceRequestSuccess, + employee, + price, + amount, + changeAmount, + onSubmitPrice +}) => ( + +
Update Assigned Work
+ {employee && ( + + Employee Information + + UUID: {employee.uuid} + + Provider Email: {(employee.provider || {}).email || "No Email!"} + + + Provider Name:{" "} + {`${(employee.provider || {}).first_name} ${ + (employee.provider || {}).last_name + }`.trim() || "No Name!"} + + + Provider Phone Number:{" "} + {`${((employee.provider || {}).userinfo || {}).phone_number || + "No Number!"}`} + + Approved: {"" + employee.approved} + Deleted: {"" + employee.deleted} + + + )} + {!employee && ( + + Invalid Employee +

No accessable employee exists for given UUID.

+
+ )} +
+ + + + + + + + + + + + + + + Update Price successful! +

Price successfully created.

+ {!!priceRequestSuccess && ( + + )} +
+ Update Price + +
+); + +function mapStateToProps(state) { + return { ...state.price, selfUser: state.user.selfUser }; +} + +export default connect(mapStateToProps)(UpdatePriceForm); diff --git a/src/components/User/Provider/ProviderClients.jsx b/src/components/User/Provider/ProviderClients.jsx index a7e627d..47e0f2b 100644 --- a/src/components/User/Provider/ProviderClients.jsx +++ b/src/components/User/Provider/ProviderClients.jsx @@ -89,6 +89,44 @@ const ProviderClientsView = ({ user, updateEmployer }) => ( : !!employer.approved ? "Approved" : "Ended"}
+ {employer.approved && ( + + + Assigned Work + + {employer.prices + .filter(price => !price.deleted) + .map((price, index) => ( + + + + + Hourly Rate: ${price.amount} + + + ))} + {employer.prices.filter(price => !price.deleted) + .length === 0 && ( + + No Prices for this Client + + )} + + + + )} {!employer.approved && ( diff --git a/src/constants/price.constants.js b/src/constants/price.constants.js new file mode 100644 index 0000000..b034849 --- /dev/null +++ b/src/constants/price.constants.js @@ -0,0 +1,14 @@ +// Reducer Price Action Constants +export const IS_SENDING_PRICE_REQUEST = "IS_SENDING_PRICE_REQUEST"; +export const SET_PRICE_REQUEST_ERROR = "SET_PRICE_REQUEST_ERROR"; +export const CLEAR_PRICE_REQUEST_ERROR = "CLEAR_PRICE_REQUEST_ERROR"; +export const SET_PRICE_REQUEST_SUCCESS = "SET_PRICE_REQUEST_SUCCESS"; +export const CLEAR_PRICE_REQUEST_SUCCESS = "CLEAR_PRICE_REQUEST_SUCCESS"; +export const SET_GET_EMPLOYEE_UUID = "SET_GET_EMPLOYEE_UUID"; +export const SET_GET_WORKTYPE_UUID = "SET_GET_WORKTYPE_UUID"; +export const SET_FORM_PRICE_AMOUNT = "SET_FORM_PRICE_AMOUNT"; + +// Saga Price Action Constants +export const CREATE_PRICE_REQUEST = "CREATE_PRICE_REQUEST"; +export const UPDATE_PRICE_REQUEST = "UPDATE_PRICE_REQUEST"; +export const DELETE_PRICE_REQUEST = "DELETE_PRICE_REQUEST"; diff --git a/src/reducers/index.js b/src/reducers/index.js index 90b20de..67a69a4 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -3,12 +3,14 @@ import authReducer from "./authReducer"; import userReducer from "./userReducer"; import worktypeReducer from "./worktypeReducer"; import employeeReducer from "./employeeReducer"; +import priceReducer from "./priceReducer"; const reducer = combineReducers({ auth: authReducer, user: userReducer, worktype: worktypeReducer, - employee: employeeReducer + employee: employeeReducer, + price: priceReducer }); export default reducer; diff --git a/src/reducers/priceReducer.js b/src/reducers/priceReducer.js new file mode 100644 index 0000000..7ec94aa --- /dev/null +++ b/src/reducers/priceReducer.js @@ -0,0 +1,68 @@ +import { + IS_SENDING_PRICE_REQUEST, + SET_PRICE_REQUEST_ERROR, + CLEAR_PRICE_REQUEST_ERROR, + SET_PRICE_REQUEST_SUCCESS, + CLEAR_PRICE_REQUEST_SUCCESS, + SET_GET_EMPLOYEE_UUID, + SET_GET_WORKTYPE_UUID, + SET_FORM_PRICE_AMOUNT +} from "../constants/price.constants"; + +const initialState = { + isSendingPriceRequest: false, + priceRequestError: "", + priceRequestSuccess: "", + getEmployeeUUID: "", + getWorktypeUUID: "", + amount: "" +}; + +function priceReducer(state = initialState, action) { + switch (action.type) { + case IS_SENDING_PRICE_REQUEST: + return { + ...state, + isSendingPriceRequest: action.data + }; + case SET_PRICE_REQUEST_ERROR: + return { + ...state, + priceRequestError: action.data + }; + case CLEAR_PRICE_REQUEST_ERROR: + return { + ...state, + priceRequestError: "" + }; + case SET_PRICE_REQUEST_SUCCESS: + return { + ...state, + priceRequestSuccess: action.data + }; + case CLEAR_PRICE_REQUEST_SUCCESS: + return { + ...state, + priceRequestSuccess: "" + }; + case SET_GET_EMPLOYEE_UUID: + return { + ...state, + getEmployeeUUID: action.data + }; + case SET_GET_WORKTYPE_UUID: + return { + ...state, + getWorktypeUUID: action.data + }; + case SET_FORM_PRICE_AMOUNT: + return { + ...state, + amount: action.data + }; + default: + return state; + } +} + +export default priceReducer; diff --git a/src/sagas/index.js b/src/sagas/index.js index 27cd98f..72c65b4 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -59,6 +59,16 @@ import { } from "./employee.sagas"; import { UPDATE_EMPLOYER_REQUEST } from "../constants/employer.constants"; import { updateEmployerFlow } from "./employer.sagas"; +import { + CREATE_PRICE_REQUEST, + UPDATE_PRICE_REQUEST, + DELETE_PRICE_REQUEST +} from "../constants/price.constants"; +import { + createPriceFlow, + updatePriceFlow, + deletePriceFlow +} from "./price.sagas"; export default function* rootSaga() { yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow); @@ -83,4 +93,7 @@ export default function* rootSaga() { yield takeLatest(READ_EMPLOYEE_REQUEST, readEmployeeFlow); yield takeLatest(DELETE_EMPLOYEE_REQUEST, deleteEmployeeFlow); yield takeLatest(UPDATE_EMPLOYER_REQUEST, updateEmployerFlow); + yield takeLatest(CREATE_PRICE_REQUEST, createPriceFlow); + yield takeLatest(UPDATE_PRICE_REQUEST, updatePriceFlow); + yield takeLatest(DELETE_PRICE_REQUEST, deletePriceFlow); } diff --git a/src/sagas/price.sagas.js b/src/sagas/price.sagas.js new file mode 100644 index 0000000..ce1938f --- /dev/null +++ b/src/sagas/price.sagas.js @@ -0,0 +1,85 @@ +import { effects } from "redux-saga"; +import { + isSendingPriceRequest, + setPriceRequestError, + clearPriceRequestError, + setPriceRequestSuccess, + clearPriceRequestSuccess, + setGetEmployeeUUID, + setGetWorktypeUUID, + setFormPriceAmount +} from "../actions/price/reducer.actions"; +import { getSelfUserRequest } from "../actions/user/saga.actions"; +import { createPrice, updatePrice, deletePrice } from "../api/price.api"; + +function* createPriceCall(postBody) { + yield effects.put(isSendingPriceRequest(true)); + const { get_employee_uuid, get_work_type_uuid, amount } = postBody; + try { + return yield effects.call( + createPrice, + get_employee_uuid, + get_work_type_uuid, + amount + ); + } catch (exception) { + yield effects.put(setPriceRequestError(exception)); + return false; + } finally { + yield effects.put(isSendingPriceRequest(false)); + } +} + +function* updatePriceCall(payload) { + yield effects.put(isSendingPriceRequest(true)); + const { uuid, amount } = payload; + try { + return yield effects.call(updatePrice, uuid, amount); + } catch (exception) { + yield effects.put(setPriceRequestError(exception)); + return false; + } finally { + yield effects.put(isSendingPriceRequest(false)); + } +} + +function* deletePriceCall(uuid) { + yield effects.put(isSendingPriceRequest(true)); + try { + return yield effects.call(deletePrice, uuid); + } catch (exception) { + yield effects.put(setPriceRequestError(exception)); + return false; + } finally { + yield effects.put(isSendingPriceRequest(false)); + } +} + +export function* createPriceFlow(request) { + yield effects.put(clearPriceRequestSuccess()); + yield effects.put(clearPriceRequestError()); + const wasSuccessful = yield effects.call(createPriceCall, request.data); + if (wasSuccessful) { + yield effects.put(getSelfUserRequest()); + yield effects.put(setPriceRequestSuccess(wasSuccessful)); + yield effects.put(setGetEmployeeUUID("")); + yield effects.put(setGetWorktypeUUID("")); + yield effects.put(setFormPriceAmount("")); + yield effects.put(clearPriceRequestError()); + } +} + +export function* updatePriceFlow(request) { + yield effects.put(clearPriceRequestSuccess()); + yield effects.put(clearPriceRequestError()); + const wasSuccessful = yield effects.call(updatePriceCall, request.data); + if (wasSuccessful) { + yield effects.put(getSelfUserRequest()); + yield effects.put(setPriceRequestSuccess(wasSuccessful)); + } +} + +export function* deletePriceFlow(request) { + yield effects.call(deletePriceCall, request.data); + yield effects.put(getSelfUserRequest()); +}