Skip to content

Instantly share code, notes, and snippets.

@schinns
Created February 25, 2019 22:32
Show Gist options
  • Save schinns/6a6d9f0a4d513153b67b730233843aa6 to your computer and use it in GitHub Desktop.
Save schinns/6a6d9f0a4d513153b67b730233843aa6 to your computer and use it in GitHub Desktop.
Components handling sending invoice to client
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>
}
};
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>
}
};
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>
}
}
open Types.Item_;
open Bindings.Js;
open Utils;
let component = ReasonReact.statelessComponent("SideButtons");
let make = (
~items,
~showModal,
~toggleModal,
~updateModalState,
~updateStatus=(() => ()),
_children
) => {
...component,
render: _self =>
<div className="w-32 mr-6 ml-6 flex justify-center">
<div className="fixed flex flex-col mt-12">
/* SEND INVOICE */
<i
title="Send Invoice"
onClick=(_ => {
updateModalState([1,0,0]);
toggleModal();
})
className=(
items
|> filter(i => i |. status |> resolveIntOption == 4)
|> Array.map(i => i |. selected |> resolveBoolOption)
|> includesA(true)
? "icon fas fa-receipt text-2xl text-sm-theme w-10 h-10 rounded-full flex justify-center items-center cursor-pointer" : "hidden"
)
></i>
/* ATTACH CONFIRMATION */
<i
title="Attach Confirmation"
onClick=(_ => {
updateModalState([0,1,0]);
toggleModal();
})
className=(
items
|> filter(i => i |. status |> resolveIntOption == 6)
|> Array.map(i => i |. selected |> resolveBoolOption)
|> includesA(true)
? "icon fas fa-file-alt text-2xl text-sm-theme w-10 h-10 mt-2 rounded-full flex justify-center items-center cursor-pointer" : "hidden"
)
></i>
<div
className=(
items
|> Array.map(i => i |. selected |> resolveBoolOption)
|> includesA(true)
? "" : "hidden"
)>
<DropDownMenu padding="p-2">
<div
className="pr-8 pl-8 pt-4 pb-4 cursor-pointer hover"
onClick=(_ => {
updateModalState([0,0,1]);
toggleModal();
})
>
(ReasonReact.string("Update Status"))
</div>
<div
className="pr-8 pl-8 pt-4 pb-4 cursor-pointer hover"
onClick=(_ => updateStatus())
>
(ReasonReact.string("Archive"))
</div>
</DropDownMenu>
</div>
</div>
</div>
}
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>
}
};
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