Created
February 25, 2019 22:32
-
-
Save schinns/6a6d9f0a4d513153b67b730233843aa6 to your computer and use it in GitHub Desktop.
Components handling sending invoice to client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
external floatToString : float => string = "%identity"; | |
external promiseErrorToJsObj : Js.Promise.error => Js.t('a) = "%identity"; | |
let component = ReasonReact.statelessComponent("FurnFinItem"); | |
let make = (~item, ~tableStyle, ~collectTax, ~handleCheck, _children) => { | |
...component, | |
render: _self => { | |
let itemId = item |. Types.Item_.id |> Utils.resolveIntOption; | |
let selected = item |. Types.Item_.selected |> Utils.resolveBoolOption; | |
let itemType = item |. Types.Item_.furnishing_id |> Utils.resolveIntOption > 0 ? "furn" : "fin"; | |
let isFurn = item |. Types.Item_.furnishing_id == None ? false : true; | |
let _f_id : Js.Nullable.t(int) = Js.Nullable.return(item |. Types.Item_.furnishing_id |> Utils.resolveIntOption); | |
open Bindings.Calc; | |
let total_ = calc_item(item, collectTax); | |
<div className="bb-light"> | |
<tr> | |
<td className="font-sans text-base text-left w-1/4 lg:ls175"> | |
<input | |
_type="checkbox" | |
checked=selected | |
className="mr-2" | |
onClick=((_) => handleCheck(itemId))/> | |
(ReasonReact.string(item |. Types.Item_.company_name |> Utils.resolveOption)) | |
</td> | |
<td className=(tableStyle ++ "w-1/4")> | |
<a | |
href=(item |. Types.Item_.link |> Utils.resolveOption) | |
className="text-sm-theme-3" | |
target="_blank"> | |
(ReasonReact.string(item |. Types.Item_.description |> Utils.resolveOption)) | |
</a> | |
</td> | |
<td className=(tableStyle ++ "w-1/4 text-center")> | |
(ReasonReact.string(floatToString(item |. Types.Item_.quantity |> Utils.resolveFloatOption))) | |
</td> | |
<td className=(tableStyle ++ "w-1/4 text-center")> | |
(ReasonReact.string((total_##total) |> int_of_float |> Utils.formatPrice)) | |
</td> | |
<td className=("text-sm-theme-3 cursor-pointer w-1/4")> | |
<DropDownMenu style2="right-5"> | |
<div | |
className="pr-8 pl-8 pt-4 pb-4 cursor-pointer hover" | |
onClick=((_) => Bindings.Js.assign("/edit-item/" ++ string_of_int(itemId) ++ "?itemType=" ++ itemType))> | |
(ReasonReact.string("Edit")) | |
</div> | |
<div | |
className="pr-8 pl-8 pt-4 pb-4 cursor-pointer hover" | |
onClick=((_) => { | |
Js.Promise.( | |
Axios.Instance.putData( | |
Utils.instance, | |
"/item/archive", | |
{ | |
"id": itemId, | |
"isFurn": isFurn | |
} | |
) | |
|> then_(response => resolve(Bindings.Js.reload())) | |
|> catch(err => { | |
let error = err |> promiseErrorToJsObj; | |
Bindings.Js.alert(error##response##data##err); | |
resolve(); | |
}) | |
|> ignore | |
) | |
})> | |
(ReasonReact.string("Archive")) | |
</div> | |
</DropDownMenu> | |
</td> | |
</tr> | |
<div className="bg-green h-24"> | |
</div> | |
</div> | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Bindings.Js; | |
open Utils; | |
external stringToFloat : string => float = "%identity"; | |
external floatToString : float => string = "%identity"; | |
type furnishing = Types.Furnishing.furnishing; | |
type client = Types.Client.client; | |
type item_ = Types.Item_.item_; | |
type state = { | |
client, | |
furnishing, | |
items: array(item_), | |
inProgress: array(item_), | |
order: array(item_), | |
fulfilled: array(item_), | |
completed: array(item_), | |
returned: array(item_), | |
modalState: list(int), | |
showModal: bool | |
}; | |
type action = | |
| FetchClient(client) | |
| FetchFurnishing(furnishing) | |
| SetItems(array(item_)) | |
| SetInProgress(array(item_)) | |
| SetOrder(array(item_)) | |
| SetFulfilled(array(item_)) | |
| SetCompleted(array(item_)) | |
| SetReturned(array(item_)) | |
| ModalState(list(int)) | |
| ToggleModal | |
| HideModal; | |
let component = ReasonReact.reducerComponent("Furnishing"); | |
let make = (~currentIDs, ~user, _children) => { | |
...component, | |
initialState: () => { | |
client: Js.Obj.empty(), | |
furnishing: Types.Furnishing.furnishing(()), | |
items: [||], | |
inProgress: [||], | |
order: [||], | |
fulfilled: [||], | |
completed: [||], | |
returned: [||], | |
showModal: false, | |
modalState: [1,0,0] | |
}, | |
reducer: (action, state) => | |
switch (action) { | |
| FetchClient(datum) => ReasonReact.Update({...state, client: datum}) | |
| FetchFurnishing(datum) => ReasonReact.Update({...state, furnishing: datum}) | |
| SetItems(data) => ReasonReact.Update({...state, items: data}) | |
| SetInProgress(data) => ReasonReact.Update({...state, inProgress: data}) | |
| SetOrder(data) => ReasonReact.Update({...state, order: data}) | |
| SetFulfilled(data) => ReasonReact.Update({...state, fulfilled: data}) | |
| SetCompleted(data) => ReasonReact.Update({...state, completed: data}) | |
| SetReturned(data) => ReasonReact.Update({...state, returned: data}) | |
| ToggleModal => ReasonReact.Update({...state, showModal: !state.showModal}) | |
| HideModal => ReasonReact.Update({...state, showModal: false}) | |
| ModalState(modal) => ReasonReact.Update({...state, modalState: modal}) | |
}, | |
didMount: ({send}) => { | |
let urlParam = Bindings.Js.pathname |> Bindings.Js.split("/"); | |
let clientId = urlParam[Array.length(urlParam) - 3]; | |
Js.log(urlParam); | |
let furnishingId = urlParam[Array.length(urlParam) - 1]; | |
Js.Promise.( | |
Axios.Instance.get(Utils.instance, "/furnishing/" ++ furnishingId) | |
|> then_(response => resolve(send(FetchFurnishing(response##data)))) | |
|> ignore | |
); | |
Js.Promise.( | |
Axios.Instance.get(Utils.instance, "/project/" ++ clientId) | |
|> then_(response => { | |
send(FetchClient(response##data)); | |
resolve(Utils.setTitle(response##data##name)); | |
}) | |
|> ignore | |
); | |
Js.Promise.( | |
Axios.Instance.get(Utils.instance, "/furnishing-item-2/" ++ furnishingId) | |
|> then_(response => { | |
send(SetInProgress(response##data##in_progress)); | |
send(SetOrder(response##data##order)); | |
send(SetFulfilled(response##data##fulfilled)); | |
send(SetCompleted(response##data##completed)); | |
send(SetReturned(response##data##returned)); | |
resolve(send(SetItems(response##data##all))); | |
}) | |
|> ignore | |
); | |
}, | |
render: self => { | |
let handleOverflow = () => { | |
!self.state.showModal ? addClassToBody("overflow-hidden") : removeClassFromBody("overflow-hidden"); | |
}; | |
let handleOverflow_ = () => { | |
self.state.showModal ? addClassToBody("overflow-hidden") : removeClassFromBody("overflow-hidden"); | |
}; | |
<div className=""> | |
<ProjectNav newText="Item" newUrl="new-item" client=self.state.client projectId=string_of_int(self.state.client##id)/> | |
<div | |
className="font-serif ls3 flex justify-center mt-10 text-xl text-sm-theme-3 mb-10"> | |
<a | |
target="_blank" | |
className="text-sm-theme-3" | |
href=(Utils.url ++ "/api/client/report/furnishing/" ++ string_of_int(self.state.furnishing |. Types.Furnishing.id |> Utils.resolveIntOption))> | |
(Utils.str(self.state.furnishing |. Types.Furnishing.name |> Utils.resolveOption)) | |
</a> | |
</div> | |
( | |
Array.length(self.state.items) == 0 | |
? | |
<Empty itemType="item"/> | |
: | |
<div className="flex"> | |
<SendInvoiceModal | |
user | |
client=self.state.client | |
handleOverflow=handleOverflow_ | |
hideModal=(() => self.send(HideModal)) | |
showModal=( | |
switch(self.state.modalState) { | |
| [1,0,0] => self.state.showModal ? true : false | |
| _ => false | |
} | |
) | |
toggleModal=(_ => { | |
self.send(ToggleModal); | |
handleOverflow(); | |
}) | |
items=( | |
self.state.items | |
|> filter(i => i |. Types.Item_.status |> resolveIntOption == 4) | |
|> filter(i => i |. Types.Item_.selected |> resolveBoolOption == true) | |
) | |
/> | |
<AttachmentModal | |
handleOverflow=handleOverflow_ | |
hideModal=(() => self.send(HideModal)) | |
showModal=( | |
switch(self.state.modalState) { | |
| [0,1,0] => self.state.showModal ? true : false | |
| _ => false | |
} | |
) | |
toggleModal=(_ => { | |
self.send(ToggleModal); | |
handleOverflow(); | |
}) | |
items=( | |
self.state.items | |
|> filter(i => i |. Types.Item_.status |> resolveIntOption == 6) | |
|> filter(i => i |. Types.Item_.selected |> resolveBoolOption == true) | |
) | |
/> | |
<UpdateStatusModal | |
handleOverflow=handleOverflow_ | |
hideModal=(() => self.send(HideModal)) | |
showModal=( | |
switch(self.state.modalState) { | |
| [0,0,1] => self.state.showModal ? true : false | |
| _ => false | |
} | |
) | |
toggleModal=(_ => { | |
self.send(ToggleModal); | |
handleOverflow(); | |
}) | |
items=( | |
self.state.items | |
|> filter(i => i |. Types.Item_.selected |> resolveBoolOption == true) | |
) | |
/> | |
<SideButtons | |
showModal=self.state.showModal | |
items=self.state.items | |
updateModalState=(modalState => self.send(ModalState(modalState))) | |
toggleModal=(_ => { | |
self.send(ToggleModal); | |
handleOverflow(); | |
}) | |
updateStatus=(_ => { | |
Js.Promise.( | |
Axios.Instance.putData( | |
instance, | |
"/furnishing-item/archive-items", | |
{ | |
"items": ( | |
self.state.items | |
|> filter(i => i |. Types.Item_.selected |> resolveBoolOption == true) | |
) | |
} | |
) | |
|> then_(response => { | |
reload(); | |
resolve(response); | |
}) | |
|> catch(error => { | |
let error = error |> promiseErrorToJsObj; | |
let code = error##response##status |> string_of_int; | |
alert(code ++ ": Error with archiving items"); | |
resolve(error); | |
}) | |
|> ignore | |
); | |
}) | |
/> | |
<SortedItemLists | |
items=self.state.items | |
clientId=self.state.client##id | |
updateItems=(data => self.send(SetItems(data))) | |
/> | |
</div> | |
) | |
</div> | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Types.Item_; | |
open Utils; | |
open Bindings.Calc; | |
open Bindings.Js; | |
type invoiceTotal = { | |
shipping: string, | |
handling: string, | |
tax: string, | |
total: string | |
}; | |
type t = | |
| Init | |
| Processing | |
| Success | |
| Error; | |
type state = { | |
modalState: t | |
}; | |
type action = UpdateState(t); | |
let component = ReasonReact.reducerComponent("SendInvoiceModal"); | |
let make = ( | |
~client, | |
~items, | |
~showModal, | |
~hideModal, | |
~toggleModal, | |
~user="", | |
~handleOverflow, | |
_children | |
) => { | |
...component, | |
initialState: () => { | |
modalState: Init | |
}, | |
reducer: (action, _state) => { | |
switch(action) { | |
| UpdateState(view) => ReasonReact.Update({modalState: view}) | |
} | |
}, | |
didMount: _self => { | |
addEventListener("keydown", (e => { | |
if(e##keyCode == 27) { | |
handleOverflow(); | |
hideModal(); | |
} | |
})); | |
}, | |
render: self => { | |
let collectTax = !showModal ? false : isInUtah(items[0] |. project_state |> resolveOption); | |
let invoiceTotal = { | |
shipping: !showModal ? "n/a" : ( | |
items | |
|> calc_total_shipping | |
|> int_of_float | |
|> formatPrice | |
), | |
handling: !showModal ? "n/a" : ( | |
items | |
|> calc_total_handling | |
|> int_of_float | |
|> formatPrice | |
), | |
tax: !showModal ? "n/a" : ( | |
collectTax | |
|> calc_total_tax(items, calc_item) | |
|> int_of_float | |
|> formatPrice | |
), | |
total: !showModal ? "n/a" : ( | |
collectTax | |
|> calc_total_total(items, calc_item) | |
|> int_of_float | |
|> formatPrice | |
) | |
}; | |
<div className=(showModal ? "" : "hidden")> | |
<div className="bg-white w-2/5 h-75 fixed top-40 left-40 z-10 modal-shadow"> | |
<div> | |
<div className=""> | |
<table className="w-full mb-40 border-collapse"> | |
<tbody className="tbody-h overflow-x-scroll block"> | |
<tr className="trmodal m-4 font-sans text-center underline"> | |
<td></td> | |
<td>(ReasonReact.string("Vendor"))</td> | |
<td>(ReasonReact.string("Description"))</td> | |
<td>(ReasonReact.string("Quantity"))</td> | |
<td>(ReasonReact.string("Unit Price"))</td> | |
<td>(ReasonReact.string("Amount"))</td> | |
</tr> | |
( | |
items | |
|> Array.mapi( | |
(i, item) => { | |
let collectTax = isInUtah(item |. project_state |> resolveOption); | |
let itemCalc = calc_item(item, collectTax); | |
<tr | |
key=string_of_int(i) | |
className="m-4 trmodal font-sans text-center" | |
> | |
<td>(ReasonReact.string(i + 1 |> string_of_int))</td> | |
<td>(ReasonReact.string(item |. company_name |> resolveOption))</td> | |
<td>(ReasonReact.string(item |. description |> resolveOption))</td> | |
<td>(ReasonReact.string(item |. quantity |> resolveFloatOption |> int_of_float |> string_of_int))</td> | |
<td>(ReasonReact.string(item |. wholesale_price |> resolveFloatOption |> int_of_float |> formatPrice))</td> | |
<td>(ReasonReact.string(itemCalc##total |> int_of_float |> formatPrice))</td> | |
</tr> | |
} | |
) | |
|> ReasonReact.array | |
) | |
</tbody> | |
</table> | |
<div className="modal-shadow bg-white flex justify-between items-center fixed modal-bottom-bar"> | |
<table id="send-invoice-modal-total" className="font-sans"> | |
<tbody> | |
<tr> | |
<td>(ReasonReact.string("Total Shipping"))</td> | |
<td>(ReasonReact.string(invoiceTotal.shipping))</td> | |
</tr> | |
<tr> | |
<td>(ReasonReact.string("Total Handling"))</td> | |
<td>(ReasonReact.string(invoiceTotal.handling))</td> | |
</tr> | |
<tr> | |
<td>(ReasonReact.string("Total Tax"))</td> | |
<td>(ReasonReact.string(invoiceTotal.tax))</td> | |
</tr> | |
<tr> | |
<td>(ReasonReact.string("Total"))</td> | |
<td>(ReasonReact.string(invoiceTotal.total))</td> | |
</tr> | |
</tbody> | |
</table> | |
<div className="flex items-center w-48"> | |
<button | |
className="text-soft-white h-12 w-32 bg-sm-theme font-sans" | |
onClick=(_ => { | |
self.send(UpdateState(Processing)); | |
Js.Promise.( | |
Axios.Instance.postData( | |
instance, | |
"/create_items_invoice", | |
{ | |
"items": items, | |
"billing_contact_id": client##billing_contact_id, | |
"primary_contact_id": client##primary_contact_id, | |
"collect_tax": collectTax | |
} | |
) | |
|> then_(response => { | |
let xeroContactId = [...response##data##xero_invoice##_Invoices][0]##_Contact##_ContactID; | |
let xeroInvoiceId = [...response##data##xero_invoice##_Invoices][0]##_InvoiceID; | |
let totals = response##data##totals; | |
Js.Promise.( | |
Axios.Instance.postData( | |
Utils.instance, | |
"/send_invoice", | |
{ | |
"billing_contact_id": client##billing_contact_id, | |
"primary_contact_id": client##primary_contact_id, | |
"invoice_type": 1, | |
"collect_tax": collectTax, | |
"project_id": client##id, | |
"xero_invoice_id": xeroInvoiceId, | |
"xero_contact_id": xeroContactId, | |
"line_items": items, | |
"totals": totals, | |
"user": user, | |
"memo": "" | |
} | |
) | |
|> then_(response => { | |
Bindings.Js.alert("Success: Invoice Sent!"); | |
self.send(UpdateState(Success)); | |
resolve(response); | |
}) | |
|> catch(err => { | |
let error = err |> promiseErrorToJsObj; | |
Bindings.Js.alert("Error " ++ error##response##status ++ ": Error sending invoice"); | |
self.send(UpdateState(Error)); | |
resolve(response); | |
}) | |
); | |
resolve(); | |
}) | |
|> catch(err => { | |
let error = err |> promiseErrorToJsObj; | |
Bindings.Js.alert("Error " ++ error##response##status ++ ": Error creating invoice"); | |
self.send(UpdateState(Error)); | |
resolve(); | |
}) | |
) | |
|> ignore; | |
}) | |
> | |
(ReasonReact.string("Send Invoice")) | |
</button> | |
<div className=(self.state.modalState == Processing ? "ml-3 loader w-10 h-10 z-10" : "hidden")></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="bg-modal w-full h-screen-plus absolute pin" onClick=(toggleModal)></div> | |
</div> | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
external floatToString : float => string = "%identity"; | |
external promiseErrorToJsObj : Js.Promise.error => Js.t('a) = "%identity"; | |
type state = { | |
hidden: bool | |
}; | |
type action = | |
| ToggleList; | |
let tableStyle = "font-sans text-base text-left lg:ls175 lg:p-4 xl:p-4 "; | |
let statusStyle = "ml-4 font-serif ls3 text-sm-theme-3 underline cursor-pointer "; | |
let component = ReasonReact.reducerComponent("SortedItemList"); | |
let make = (~data, | |
~collectTax=false, | |
~projectId, | |
~status=0, | |
~updateItems, | |
~showThead=false, | |
_children) => { | |
...component, | |
initialState: () => { | |
hidden: false | |
}, | |
reducer: (action, state) => | |
switch(action) { | |
| ToggleList => ReasonReact.Update({ hidden: !state.hidden }) | |
}, | |
render: self => { | |
<div className=""> | |
<div className=("ml-6 mr-6 flex justify-center items-center")> | |
<table className=" border-collapse overflow-hidden sm-md:m-3 w-full"> | |
<thead> | |
<tr> | |
<td className="pt-4 pb-4"> | |
<input | |
_type="checkbox" | |
onClick=(_ => { | |
/* select all: if item equals the status update item as checked */ | |
data | |
|> Array.map(item => { | |
if(item |. Types.Item_.status |> Utils.resolveIntOption == status) { | |
[%bs.raw{|{...item, selected: !item.selected}|}] | |
} else { | |
item | |
} | |
}) | |
|> (items => updateItems(items)) | |
})/> | |
<label | |
className="font-sans ml-2 font-serif ls3 text-sm-theme-3 underline" | |
onClick=(_ => self.send(ToggleList))> | |
(ReasonReact.string( | |
switch status { | |
| 4 => "In Progress" | |
| 6 => "To Be Ordered" | |
| 1 => "Fulfilled" | |
| 2 => "Completed" | |
| 3 => "Returned" | |
| _ => "n/a" | |
} | |
)) | |
</label> | |
</td> | |
</tr> | |
</thead> | |
( | |
data | |
|> Array.to_list | |
|> List.filter(item => (item |. Types.Item_.status |> Utils.resolveIntOption) == status) | |
|> List.mapi( | |
(i, item) => { | |
<tbody className=(self.state.hidden ? "slider closed" : "slider") key=string_of_int(i)> | |
<FurnFinItem | |
item | |
tableStyle | |
collectTax | |
handleCheck=((id) => { | |
/* if id matches the item clicked, update item as checked */ | |
data | |
|> Array.map(item => { | |
let id_ = item |. Types.Item_.id |> Utils.resolveIntOption; | |
if(id == id_) { | |
[%bs.raw{|{...item, selected: !item.selected}|}] | |
} else { | |
item | |
} | |
}) | |
|> (items => updateItems(items)) | |
} | |
) | |
/> | |
</tbody> | |
} | |
) | |
|> Array.of_list | |
|> ReasonReact.array | |
) | |
</table> | |
</div> | |
</div> | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Bindings.Js; | |
open Utils; | |
let filterItems = (status, data) => data |> filter(i => i |. Types.Item_.status |> resolveIntOption == status); | |
let titleStyle = "ml-4 font-serif ls3 text-sm-theme-3 underline cursor-pointer"; | |
let component = ReasonReact.statelessComponent("SortedItemLists"); | |
let make = ( | |
~items, | |
~clientId, | |
~updateItems, | |
_children | |
) => { | |
...component, | |
render: _self => { | |
<div className="mb-10 w-full"> | |
<div className=(items |> filterItems(4) == [||] ? "hidden": "")> | |
<SortedItemList | |
status=4 | |
data=items | |
showThead=true | |
updateItems | |
projectId=clientId/> | |
</div> | |
<div className=(items |> filterItems(6) == [||] ? "hidden": "mt-10")> | |
<SortedItemList | |
status=6 | |
data=items | |
updateItems | |
projectId=clientId/> | |
</div> | |
<div className=(items |> filterItems(1) == [||] ? "hidden": "mt-10")> | |
<SortedItemList | |
status=1 | |
data=items | |
updateItems | |
projectId=clientId/> | |
</div> | |
<div className=(items |> filterItems(2) == [||] ? "hidden": "mt-10")> | |
<SortedItemList | |
status=2 | |
data=items | |
updateItems | |
projectId=clientId/> | |
</div> | |
<div className=(items |> filterItems(3) == [||] ? "hidden": "mt-10")> | |
<SortedItemList | |
status=3 | |
data=items | |
updateItems | |
projectId=clientId/> | |
</div> | |
</div> | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment