onSubmit(mapValuesToProduct(product, values))}
80 | onCancel
81 | />;
82 |
83 | let make = _children => {
84 | ...component,
85 | render: _self =>
86 | ,
93 | };
--------------------------------------------------------------------------------
/src/components/SearchModal.re:
--------------------------------------------------------------------------------
1 | type state = {products: list(Product.t)};
2 |
3 | type action =
4 | | FilterProduct(string);
5 |
6 | let component = ReasonReact.reducerComponent("SearchModal");
7 |
8 | let lowercase = text => Js.String.toLowerCase(text);
9 |
10 | let make =
11 | (
12 | ~onCancel=() => (),
13 | ~onSelect,
14 | ~isOpen=false,
15 | ~allProducts: list(Product.t),
16 | ~label: string,
17 | _children,
18 | ) => {
19 | ...component,
20 | initialState: () => {products: []},
21 | reducer: (action, _state) =>
22 | switch (action) {
23 | | FilterProduct(text) =>
24 | ReasonReact.Update({
25 | products:
26 | allProducts
27 | |> List.filter((x: Product.t) =>
28 | lowercase(x.name)
29 | |> Js.String.indexOf(lowercase(text)) > (-1)
30 | ),
31 | })
32 | },
33 | render: self =>
34 |
35 |
36 |
37 | {ReactUtils.sloc(label)}
38 |
45 |
46 | self.send(FilterProduct(text))} />
47 |
48 |
49 |
50 | {
51 | self.state.products
52 | |> List.map((i: Product.t) =>
53 |
54 | |
55 | |
62 | {ReactUtils.s(i.sku)} |
63 | {ReactUtils.s(i.name)} |
64 |
65 | {ReactUtils.s(i.suggestedPrice |> Money.toDisplay)}
66 | |
67 |
68 | )
69 | |> Array.of_list
70 | |> ReasonReact.array
71 | }
72 |
73 |
74 |
75 |
76 |
77 |
84 |
85 |
,
86 | };
87 |
--------------------------------------------------------------------------------
/src/components/OrderList.re:
--------------------------------------------------------------------------------
1 | type state = {language: string};
2 | type action =
3 | | LoadConfig
4 | | ConfigLoaded(Config.App.t);
5 | let component = ReasonReact.reducerComponent("OrderList");
6 |
7 | let language = Config.App.get().language;
8 | let headerRow =
9 |
10 | |
11 | {ReactUtils.s("order.customerName" |> Lang.translate)} |
12 | {ReactUtils.s("order.subTotal" |> Lang.translate)} |
13 | {ReactUtils.s("order.tax" |> Lang.translate)} |
14 | {ReactUtils.s("order.total" |> Lang.translate)} |
15 | {ReactUtils.s("order.paidDate" |> Lang.translate)} |
16 | {ReactUtils.s("order.paidTime" |> Lang.translate)} |
17 |
;
18 |
19 | let row = (o: Order.orderVm, onSelect) => {
20 | let totals = o.orderItems |> OrderItemCalculation.getTotals(o.discounts);
21 | let paidOn =
22 | switch (o.paid) {
23 | | Some(paid) => paid.on
24 | | None => 0.0
25 | };
26 |
27 | |
28 | |
35 | {ReactUtils.s(o.customerName)} |
36 | {ReactUtils.s(totals.subTotal |> Money.toDisplay)} |
37 | {ReactUtils.s(totals.tax |> Money.toDisplay)} |
38 | {ReactUtils.s(totals.total |> Money.toDisplay)} |
39 |
40 | {
41 | language === "EN" ?
42 | ReactUtils.s(paidOn |> Date.toDisplayDateEN) :
43 | ReactUtils.s(paidOn |> Date.toDisplayDate)
44 | }
45 | |
46 | {ReactUtils.s(paidOn |> Date.toDisplayTime)} |
47 |
;
48 | };
49 |
50 | let make = (~orders: list(Order.orderVm), ~onSelect, _children) => {
51 | ...component,
52 | initialState: () => {language: "EN"},
53 | didMount: self => {
54 | self.send(LoadConfig);
55 | ();
56 | },
57 | reducer: (action, _state) =>
58 | switch (action) {
59 | | LoadConfig =>
60 | ReasonReact.SideEffects(
61 | (
62 | self => {
63 | let cfg = Config.App.get();
64 | Js.log(cfg);
65 | self.send(ConfigLoaded(cfg));
66 | }
67 | ),
68 | )
69 | | ConfigLoaded(config) => ReasonReact.Update({language: config.language})
70 | },
71 | render: _self =>
72 |
73 | headerRow
74 |
75 | {
76 | orders
77 | |> List.sort((a: Order.orderVm, b: Order.orderVm) => {
78 | let getPaidDate = (p: option(Paid.t)) =>
79 | switch (p) {
80 | | None => 0.0
81 | | Some(paid) => paid.on
82 | };
83 | getPaidDate(b.paid) -. getPaidDate(a.paid) |> int_of_float;
84 | })
85 | |> List.map(o => row(o, onSelect))
86 | |> Array.of_list
87 | |> ReasonReact.array
88 | }
89 |
90 |
,
91 | };
92 |
--------------------------------------------------------------------------------
/src/components/EditableText.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | type mode =
4 | | TouchToEdit
5 | | EditOnly
6 | | ReadOnly;
7 |
8 | type submitBehavior =
9 | | SubmitOnEnter
10 | | SubmitOnKey;
11 |
12 | type action =
13 | | EnableMod
14 | | DisableMod
15 | | KeyDown(int)
16 | | ValueChanged(string);
17 |
18 | type state = {
19 | modifying: bool,
20 | value: string,
21 | original: string,
22 | };
23 |
24 | let component = ReasonReact.reducerComponent("EditableText");
25 |
26 | let make =
27 | (
28 | ~text: string,
29 | ~placeholder="",
30 | ~big=true,
31 | ~onChange,
32 | ~mode=TouchToEdit,
33 | ~autoFocus=true,
34 | ~required=false,
35 | _children,
36 | ) => {
37 | ...component,
38 | initialState: () => {
39 | modifying: mode === EditOnly,
40 | original: text,
41 | value: text,
42 | },
43 | reducer: (action, state) =>
44 | switch (action) {
45 | | EnableMod =>
46 | ReasonReact.Update({
47 | ...state,
48 | value: text,
49 | modifying: mode === TouchToEdit,
50 | })
51 | | DisableMod =>
52 | ReasonReact.Update({...state, modifying: mode === EditOnly})
53 | | ValueChanged(value) =>
54 | ReasonReact.UpdateWithSideEffects(
55 | {...state, value},
56 | (self => onChange(self.state.value)),
57 | )
58 | | KeyDown(164) =>
59 | ReasonReact.Update({
60 | ...state,
61 | value: state.value ++ (164 |> Js.String.fromCharCode),
62 | })
63 | | KeyDown(165) =>
64 | ReasonReact.Update({
65 | ...state,
66 | value: state.value ++ (165 |> Js.String.fromCharCode),
67 | })
68 | | KeyDown(27) =>
69 | ReasonReact.UpdateWithSideEffects(
70 | {...state, value: state.original},
71 | (
72 | self =>
73 | if (mode !== EditOnly) {
74 | self.send(DisableMod);
75 | }
76 | ),
77 | )
78 | | KeyDown(13) =>
79 | ReasonReact.SideEffects(
80 | (
81 | self => {
82 | onChange(self.state.value);
83 | if (mode !== EditOnly) {
84 | self.send(DisableMod);
85 | };
86 | }
87 | ),
88 | )
89 | | KeyDown(_) => ReasonReact.NoUpdate
90 | },
91 | render: self =>
92 | self.state.modifying ?
93 |
94 | self.send(ValueChanged(getVal(ev)))}
99 | onKeyDown={
100 | event => self.send(KeyDown(ReactEvent.Keyboard.which(event)))
101 | }
102 | className={big ? "big-text" : ""}
103 | />
104 |
107 |
:
108 | self.send(EnableMod)}> {ReactUtils.s(text)}
,
109 | };
110 |
--------------------------------------------------------------------------------
/src/components/orderTaking/PaymentMethodSelector.re:
--------------------------------------------------------------------------------
1 | type state = {
2 | externalId: string,
3 | methods: list(PaymentMethod.t),
4 | selected: option(PaymentMethod.t),
5 | };
6 |
7 | type action =
8 | | Select(PaymentMethod.t)
9 | | PaymentMethodsLoaded(list(PaymentMethod.t))
10 | | ExternalIdChanged(string);
11 |
12 | let component = ReasonReact.reducerComponent("PaymentMethodSelector");
13 |
14 | let isValid = (method: PaymentMethod.t, externalId) =>
15 | switch (method.hasExternalId, externalId) {
16 | | (true, "") => false
17 | | (true, _) => true
18 | | (false, _) => true
19 | };
20 |
21 | let validateMethod = (state, onValid, onInvalid) =>
22 | switch (state.selected) {
23 | | None => ()
24 | | Some(method) =>
25 | isValid(method, state.externalId) ?
26 | onValid(method, state.externalId) : onInvalid()
27 | };
28 |
29 | let make =
30 | (
31 | ~onValid: (PaymentMethod.t, string) => unit,
32 | ~onInvalid: unit => unit,
33 | _children,
34 | ) => {
35 | ...component,
36 | didMount: self => {
37 | self.send(PaymentMethodsLoaded(PaymentMethod.tempAllPaymentMethods));
38 | ();
39 | },
40 | initialState: () => {externalId: "", methods: [], selected: None},
41 | reducer: (action, state) =>
42 | switch (action) {
43 | | ExternalIdChanged(id) =>
44 | ReasonReact.UpdateWithSideEffects(
45 | {...state, externalId: id},
46 | (self => validateMethod(self.state, onValid, onInvalid)),
47 | )
48 | | PaymentMethodsLoaded(methods) =>
49 | ReasonReact.Update({...state, methods})
50 | | Select(method) =>
51 | ReasonReact.UpdateWithSideEffects(
52 | {...state, selected: Some(method)},
53 | (self => validateMethod(self.state, onValid, onInvalid)),
54 | )
55 | },
56 | render: self =>
57 |
58 | (
59 | self.state.methods
60 | |> List.map((m: PaymentMethod.t) => {
61 | let selected =
62 | switch (self.state.selected) {
63 | | None => false
64 | | Some(method) => method === m
65 | };
66 |
67 | ;
88 | })
89 | |> Array.of_list
90 | |> ReasonReact.array
91 | )
92 |
,
93 | };
--------------------------------------------------------------------------------
/src/components/orderTaking/ClosedOrderItems.re:
--------------------------------------------------------------------------------
1 | let component = ReasonReact.statelessComponent("ClosedOrderItems");
2 |
3 | let make =
4 | (
5 | ~orderItems: list(OrderItem.t),
6 | ~discounts: list(Discount.t),
7 | ~deselectDiscount=_d => (),
8 | _children,
9 | ) => {
10 | ...component,
11 | render: _self => {
12 | let totals = OrderItemCalculation.getTotals(discounts, orderItems);
13 |
14 |
(ReactUtils.sloc("order.orderItems.header"))
15 |
16 |
17 | (
18 | orderItems
19 | |> List.map((i: OrderItem.t) => {
20 | let totals = OrderItemCalculation.getTotals(discounts, [i]);
21 |
22 | |
23 | (ReactUtils.s(i.quantity |> string_of_int))
24 | |
25 | |
26 | (ReactUtils.s(i.name)) |
27 |
28 | (
29 | ReactUtils.s(
30 | Js.Float.toFixedWithPrecision(
31 | (i.suggestedPrice |> float_of_int) /. 100.,
32 | ~digits=2,
33 | ),
34 | )
35 | )
36 | |
37 |
38 | (ReactUtils.s(totals.subTotal |> Money.toDisplay))
39 | |
40 |
;
41 | })
42 | |> Array.of_list
43 | |> ReasonReact.array
44 | )
45 |
46 |
47 |
48 | | (ReactUtils.sloc("order.subTotal")) |
49 |
50 | (ReactUtils.s(totals.subTotal |> Money.toDisplay))
51 | |
52 |
53 | (
54 | if (discounts |> List.length > 0) {
55 |
56 | | (ReactUtils.sloc("order.discounts")) |
57 | (ReactUtils.s(totals.discounts |> Money.toDisplay)) |
58 |
;
59 | } else {
60 |
;
61 | }
62 | )
63 |
64 | | (ReactUtils.sloc("order.tax")) |
65 | (ReactUtils.s(totals.tax |> Money.toDisplay)) |
66 |
67 |
68 | | (ReactUtils.sloc("order.total")) |
69 | (ReactUtils.s(totals.total |> Money.toDisplay)) |
70 |
71 |
72 |
73 | (
74 | discounts
75 | |> List.map((d: Discount.t) =>
76 |
82 | )
83 | |> Array.of_list
84 | |> ReasonReact.array
85 | )
86 |
;
87 | },
88 | };
--------------------------------------------------------------------------------
/src/components/dailyReport/ExpenseReportSection.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | let header =
4 |
5 | | (sloc("expense.date")) |
6 | (sloc("expense.vendor")) |
7 | (sloc("expense.description")) |
8 | (sloc("expense.subTotals")) |
9 | (sloc("expense.tax")) |
10 | (sloc("expense.total")) |
11 |
;
12 |
13 | let expenseRow = (e: Expense.denormalized) =>
14 |
15 | | (s(e.date |> Date.toDisplayDate)) |
16 | (s(e.vendor.name)) |
17 | (s(e.description)) |
18 | (s(e.subTotal |> Money.toDisplay)) |
19 | (s(e.tax |> Money.toDisplay)) |
20 | (s(e.total |> Money.toDisplay)) |
21 |
;
22 |
23 | let expensesBody = (title: string, expenses: list(Expense.denormalized)) => {
24 | let grandSubTotal =
25 | expenses |. Belt.List.reduce(0, (a, c) => a + c.subTotal);
26 | let grandTax = expenses |. Belt.List.reduce(0, (a, c) => a + c.tax);
27 | let grandTotal = expenses |. Belt.List.reduce(0, (a, c) => a + c.total);
28 |
29 |
30 | (s(title)) |
31 |
32 | header
33 | (
34 | expenses
35 | |> List.map((e: Expense.denormalized) => expenseRow(e))
36 | |> Array.of_list
37 | |> ReasonReact.array
38 | )
39 |
40 | |
41 | |
42 | |
43 | (s(grandSubTotal |> Money.toDisplay)) |
44 | (s(grandTax |> Money.toDisplay)) |
45 | (s(grandTotal |> Money.toDisplay)) |
46 |
47 | ;
48 | };
49 |
50 | let component = ReasonReact.statelessComponent("ExpenseReportSection");
51 |
52 | let make = (~expenses: list(Expense.t), ~key="", _children) => {
53 | ...component,
54 | render: _self => {
55 | let denormalized = expenses |> Expense.denormalize;
56 | let groups =
57 | denormalized
58 | |> Group.by((x: Expense.denormalized) => {
59 | let pre = "daily.expensesSection.title.pre" |> Lang.translate;
60 | let post = "daily.expensesSection.title.post" |> Lang.translate;
61 | let title =
62 | x.expenseType.name
63 | ++ " "
64 | ++ pre
65 | ++ " "
66 | ++ (x.taxRate |> Percent.toDisplay)
67 | ++ " "
68 | ++ post;
69 | title;
70 | });
71 |
72 |
73 | (
74 | groups
75 | |> List.map((g: Group.group(Expense.denormalized)) =>
76 | expensesBody(g.key, g.value)
77 | )
78 | |> Array.of_list
79 | |> ReasonReact.array
80 | )
81 |
82 |
;
83 | },
84 | };
--------------------------------------------------------------------------------
/src/components/dailyReport/SalesReportSection.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | let component = ReasonReact.statelessComponent("SalesReportSection");
4 |
5 | let make = (~title: string, ~sales: list(Sale.t), ~key="", _children) => {
6 | ...component,
7 | render: _self => {
8 | let groups: list(ProductGroup.t) = sales |> ProductGroup.fromSalesList;
9 | let grandSubTotal =
10 | groups |. Belt.List.reduce(0, (a, c) => a + c.subTotal);
11 | let grandTax = groups |. Belt.List.reduce(0, (a, c) => a + c.tax);
12 | let grandTotal = groups |. Belt.List.reduce(0, (a, c) => a + c.total);
13 |
14 |
(s(title))
15 |
16 |
17 |
18 | |
19 | (sloc("daily.salesSection.product"))
20 | |
21 |
22 | (sloc("daily.salesSection.price"))
23 | |
24 |
25 | (sloc("daily.salesSection.quantity"))
26 | |
27 |
28 | (sloc("daily.salesSection.subTotal"))
29 | |
30 |
31 | (sloc("daily.salesSection.tax"))
32 | |
33 |
34 | (sloc("daily.salesSection.total"))
35 | |
36 |
37 |
38 |
39 | (
40 | groups
41 | |> List.map((group: ProductGroup.t) =>
42 |
43 | | (s(group.productName)) |
44 |
45 | (s(group.salePrice |> Money.toDisplay))
46 | |
47 |
48 | (s(group.quantity |> string_of_int))
49 | |
50 |
51 | (s(group.subTotal |> Money.toDisplay))
52 | |
53 |
54 | (s(group.tax |> Money.toDisplay))
55 | |
56 |
57 | (s(group.total |> Money.toDisplay))
58 | |
59 |
60 | )
61 | |> Array.of_list
62 | |> ReasonReact.array
63 | )
64 |
65 |
66 |
67 | |
68 | |
69 | |
70 |
71 | (s(grandSubTotal |> Money.toDisplay))
72 | |
73 |
74 | (s(grandTax |> Money.toDisplay))
75 | |
76 |
77 | (s(grandTotal |> Money.toDisplay))
78 | |
79 |
80 |
81 |
82 |
;
83 | },
84 | };
--------------------------------------------------------------------------------
/src/components/webhooks/WebhookManagementNew.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | type state = {
4 | name: string,
5 | url: string,
6 | event: string,
7 | source: string,
8 | behavior: string,
9 | payload: string,
10 | };
11 |
12 | type action =
13 | | ChangeName(string)
14 | | ChangeUrl(string)
15 | | ChangeEvent(string)
16 | | ChangeSource(string)
17 | | ChangeBehavior(string)
18 | | ChangePayload(string)
19 | | ClearInputs;
20 |
21 | let component = ReasonReact.reducerComponent("WebhookManagementNew");
22 |
23 | let make = (~create, _children) => {
24 | ...component,
25 | initialState: () => {
26 | name: "",
27 | url: "",
28 | event: "",
29 | source: "Order",
30 | behavior: "FireAndForget",
31 | payload: Webhook.PayloadType.(Json |> toString),
32 | },
33 | reducer: (action, state) =>
34 | switch (action) {
35 | | ChangeName(newVal) => ReasonReact.Update({...state, name: newVal})
36 | | ChangeUrl(newVal) => ReasonReact.Update({...state, url: newVal})
37 | | ChangeEvent(newVal) => ReasonReact.Update({...state, event: newVal})
38 | | ChangeSource(newVal) => ReasonReact.Update({...state, source: newVal})
39 | | ChangeBehavior(newVal) =>
40 | ReasonReact.Update({...state, behavior: newVal})
41 | | ChangePayload(newVal) =>
42 | ReasonReact.Update({...state, payload: newVal})
43 | | ClearInputs => ReasonReact.Update({...state, name: "", url: ""})
44 | },
45 | render: self => {
46 | let finishedEnteringData = () => {
47 | let newWebhook: Webhook.New.t = {
48 | name: self.state.name,
49 | url: self.state.url,
50 | event: self.state.event |> Webhook.EventType.toT,
51 | source: self.state.source |> Webhook.EventSource.toT,
52 | behavior: self.state.behavior |> Webhook.Behavior.fromString,
53 | payload: self.state.payload |> Webhook.PayloadType.fromString,
54 | };
55 | self.send(ClearInputs);
56 | create(newWebhook);
57 | };
58 |
59 | |
60 | self.send(ChangeName(getVal(ev)))}
63 | />
64 | |
65 |
66 | self.send(ChangeUrl(getVal(ev)))}
69 | />
70 | |
71 |
72 | self.send(ChangeEvent(getVal(ev)))}
75 | />
76 | |
77 |
78 | self.send(ChangeSource(getVal(ev)))}
81 | />
82 | |
83 |
84 | self.send(ChangeBehavior(getVal(ev)))}
87 | />
88 | |
89 |
90 | self.send(ChangePayload(getVal(ev)))}
93 | />
94 | |
95 |
96 |
99 | |
100 |
;
101 | },
102 | };
--------------------------------------------------------------------------------
/src/cafeRouter.re:
--------------------------------------------------------------------------------
1 | type view =
2 | | Home
3 | | Order
4 | | Pay
5 | | AllOrders
6 | | Admin
7 | | Products
8 | | Config
9 | | Daily
10 | | Vendors
11 | | Cashiers
12 | | Webhooks
13 | | Discounts
14 | | Logs;
15 |
16 | type customerName = string;
17 |
18 | type state = {currentView: view};
19 |
20 | type action =
21 | | Show(view);
22 |
23 | let component = ReasonReact.reducerComponent("CafeRouter");
24 |
25 | let joinStrings = l => l |> Array.of_list |> Js.Array.joinWith(",");
26 |
27 | let make = _children => {
28 | ...component,
29 | initialState: () => {currentView: Home},
30 | reducer: (action, _state) =>
31 | switch (action) {
32 | | Show(view) => ReasonReact.Update({currentView: view})
33 | },
34 | didMount: self => {
35 | let watcherId =
36 | ReasonReact.Router.watchUrl(url =>
37 | switch (url.path) {
38 | | [] => self.send(Show(Home))
39 | | ["order"] => self.send(Show(Order))
40 | | ["pay"] => self.send(Show(Pay))
41 | | ["orders"] => self.send(Show(AllOrders))
42 | | ["admin"] => self.send(Show(Admin))
43 | | ["products"] => self.send(Show(Products))
44 | | ["config"] => self.send(Show(Config))
45 | | ["daily"] => self.send(Show(Daily))
46 | | ["logs"] => self.send(Show(Logs))
47 | | ["vendors"] => self.send(Show(Vendors))
48 | | ["discounts"] => self.send(Show(Discounts))
49 | | ["webhooks"] => self.send(Show(Webhooks))
50 | | ["cashiers"] => self.send(Show(Cashiers))
51 | | p => Js.log("I don't know this path. " ++ (p |> joinStrings))
52 | }
53 | );
54 | self.onUnmount(() => ReasonReact.Router.unwatchUrl(watcherId));
55 | },
56 | render: self => {
57 | let onStartNewOrder = customerName =>
58 | ReasonReact.Router.push("order?customerName=" ++ customerName);
59 | let goHome = () => ReasonReact.Router.push("/");
60 | let goToOrders = () => ReasonReact.Router.push("/orders");
61 | let goToOrder = order =>
62 | ReasonReact.Router.push(
63 | "/order?orderId=" ++ (order |> Order.fromVm).id,
64 | );
65 | let queryString = ReasonReact.Router.dangerouslyGetInitialUrl().search;
66 | let orderId =
67 | switch (Util.QueryParam.get("orderId", queryString)) {
68 | | None => ""
69 | | Some(orderId) => orderId
70 | };
71 | let startDate = ConfigurableDate.now() |> Date.startOfDay;
72 | let endDate = ConfigurableDate.now() |> Date.endOfDay;
73 |
74 | {
75 | switch (self.state.currentView) {
76 | | Home =>
77 | | Order =>
78 | | Pay =>
79 |
goHome())
82 | onCancel=(o => goToOrder(o))
83 | />
84 | | AllOrders =>
85 | | Admin =>
86 | | Products =>
87 | | Config =>
88 | | Logs =>
89 | | Vendors =>
90 | | Cashiers =>
91 | | Webhooks =>
92 | | Discounts =>
93 | | Daily =>
94 | }
95 | }
96 | ;
97 | },
98 | };
--------------------------------------------------------------------------------
/src/components/vendorManagement/VendorManagement.re:
--------------------------------------------------------------------------------
1 | open Js.Promise;
2 |
3 | type state = {vendors: list(Vendor.t)};
4 |
5 | type action =
6 | | LoadVendors(list(Vendor.t))
7 | | VendorRemoved(Vendor.t)
8 | | VendorModified(Vendor.t)
9 | | NewVendorCreated(Vendor.t);
10 |
11 | let component = ReasonReact.reducerComponent("VendorManagement");
12 |
13 | let make = _children => {
14 | ...component,
15 | didMount: self => {
16 | VendorStore.getAll()
17 | |> Js.Promise.then_(vendors => {
18 | self.send(LoadVendors(vendors));
19 | Js.Promise.resolve();
20 | })
21 | |> ignore;
22 | ();
23 | },
24 | initialState: () => {vendors: []},
25 | reducer: (action, state) =>
26 | switch (action) {
27 | | LoadVendors(vendors) => ReasonReact.Update({vendors: vendors})
28 | | VendorRemoved(exp) =>
29 | ReasonReact.Update({
30 | vendors:
31 | state.vendors |> List.filter((d: Vendor.t) => d.id !== exp.id),
32 | })
33 | | VendorModified(exp) =>
34 | ReasonReact.Update({
35 | vendors:
36 | state.vendors
37 | |> List.map((d: Vendor.t) => d.id !== exp.id ? exp : d),
38 | })
39 | | NewVendorCreated(exp) =>
40 | ReasonReact.Update({vendors: List.concat([state.vendors, [exp]])})
41 | },
42 | render: self => {
43 | let goBack = _ => ReasonReact.Router.push("/admin");
44 | let removeVendor = (p: Vendor.t) => {
45 | VendorStore.remove(~id=p.id)
46 | |> then_(_ => {
47 | self.send(VendorRemoved(p));
48 | resolve();
49 | })
50 | |> ignore;
51 | ();
52 | };
53 | let modifyVendor = (modifiedVendor: Vendor.t) =>
54 | VendorStore.update(modifiedVendor)
55 | |> then_(_ => {
56 | self.send(VendorModified(modifiedVendor));
57 | resolve();
58 | })
59 | |> ignore;
60 | let createVendor = (newVendor: Vendor.New.t) => {
61 | VendorStore.add(newVendor)
62 | |> Js.Promise.then_((newVendor: Vendor.t) => {
63 | self.send(NewVendorCreated(newVendor));
64 | Js.Promise.resolve();
65 | })
66 | |> ignore;
67 | ();
68 | };
69 |
70 |
71 |
72 |
78 |
79 |
80 | (ReactUtils.sloc("admin.vendors.header"))
81 |
82 |
83 |
84 |
85 |
86 | | (ReactUtils.sloc("vendor.name")) |
87 |
88 |
89 | (
90 | self.state.vendors
91 | |> List.map((d: Vendor.t) =>
92 |
98 | )
99 | |> Array.of_list
100 | |> ReasonReact.array
101 | )
102 |
103 |
104 |
105 |
106 |
;
107 | },
108 | };
--------------------------------------------------------------------------------
/src/components/vendorManagement/VendorManagementRow.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | type state = {
4 | modifying: bool,
5 | showModal: bool,
6 | modifiedVendor: Vendor.t,
7 | originalVendor: Vendor.t,
8 | name: string,
9 | };
10 |
11 | type action =
12 | | EnableMod
13 | | CancelMod
14 | | ShowDialog
15 | | HideDialog
16 | | SaveMod(Vendor.t)
17 | | ChangeName(string);
18 |
19 | let component = ReasonReact.reducerComponent("VendorManagementRow");
20 |
21 | let make = (~vendor, ~remove, ~modify, _children) => {
22 | ...component,
23 | initialState: () => {
24 | modifying: false,
25 | originalVendor: vendor,
26 | modifiedVendor: vendor,
27 | name: vendor.name,
28 | showModal: false,
29 | },
30 | reducer: (action, state) =>
31 | switch (action) {
32 | | EnableMod => ReasonReact.Update({...state, modifying: true})
33 | | CancelMod =>
34 | ReasonReact.Update({
35 | ...state,
36 | modifying: false,
37 | modifiedVendor: state.originalVendor,
38 | })
39 | | SaveMod(vendor) =>
40 | ReasonReact.Update({
41 | ...state,
42 | modifying: false,
43 | originalVendor: vendor,
44 | modifiedVendor: vendor,
45 | })
46 | | ShowDialog => ReasonReact.Update({...state, showModal: true})
47 | | HideDialog => ReasonReact.Update({...state, showModal: false})
48 | | ChangeName(newVal) =>
49 | ReasonReact.Update({
50 | ...state,
51 | modifiedVendor: {
52 | ...state.modifiedVendor,
53 | name: newVal,
54 | },
55 | })
56 | },
57 | render: self => {
58 | let saveModification = _ => {
59 | let modified = self.state.modifiedVendor;
60 | modify(modified);
61 | self.send(SaveMod(modified));
62 | };
63 |
64 | remove(self.state.originalVendor)}
69 | onCancel={() => self.send(HideDialog)}
70 | />
71 | {
72 | switch (self.state.modifying) {
73 | | false =>
74 |
75 | |
76 | |
82 | {s(self.state.originalVendor.name)} |
83 |
84 | |
91 |
92 | | true =>
93 |
94 | |
95 | |
101 |
102 | self.send(ChangeName(getVal(ev))))
105 | />
106 | |
107 |
108 |
113 | |
114 |
115 | }
116 | }
117 |
;
118 | },
119 | };
--------------------------------------------------------------------------------
/src/components/discounts/DiscountEdit.re:
--------------------------------------------------------------------------------
1 | module DiscountFormParams = {
2 | type state = {
3 | name: string,
4 | percent: string,
5 | };
6 | type fields = [ | `name | `percent | `price | `taxCalculation | `tags];
7 | let lens = [
8 | (`name, s => s.name, (s, name) => {...s, name}),
9 | (`percent, s => s.percent, (s, percent) => {...s, percent}),
10 | ];
11 | };
12 |
13 | let validationMessage = message =>
14 | switch (message) {
15 | | None => ReasonReact.null
16 | | Some(msg) => {ReactUtils.sloc(msg)}
17 | };
18 |
19 | module EditDiscountForm = ReForm.Create(DiscountFormParams);
20 |
21 | let component = ReasonReact.statelessComponent("DiscountEdit");
22 |
23 | let make =
24 | (
25 | ~discount: option(Discount.t)=None,
26 | ~onCancel=() => (),
27 | ~onSubmit,
28 | ~discounts,
29 | _children,
30 | ) => {
31 | ...component,
32 | render: _self => {
33 | let hasDuplicateName = name => {
34 | let duplicates =
35 | discounts |> List.filter((c: Discount.t) => c.name === name);
36 | let isDuplicate = duplicates |> List.length > 0;
37 | isDuplicate ? Some("validation.duplicate") : None;
38 | };
39 | let isUnique = (original: option(Discount.t), new_) =>
40 | switch (original) {
41 | | None => hasDuplicateName(new_)
42 | | Some(disc) =>
43 | if (disc.name === new_) {
44 | None;
45 | } else {
46 | hasDuplicateName(new_);
47 | }
48 | };
49 | {name: "", percent: ""}
54 | | Some(disc) => {
55 | name: disc.name,
56 | percent: disc.percent |> Percent.toDisplay,
57 | }
58 | }
59 | }
60 | schema=[
61 | (`name, Custom(v => v.name |> isUnique(discount))),
62 | (`percent, Required),
63 | ]>
64 | ...{
65 | ({handleSubmit, handleChange, form, getErrorForField}) => {
66 | let field = (label, value, fieldType: DiscountFormParams.fields) =>
67 |
68 |
79 | {validationMessage(getErrorForField(fieldType))}
80 |
;
81 | ;
100 | }
101 | }
102 | ;
103 | },
104 | };
--------------------------------------------------------------------------------
/src/components/orderTaking/ClosedOrderInfo.re:
--------------------------------------------------------------------------------
1 | open ReactUtils;
2 |
3 | let component = ReasonReact.statelessComponent("ClosedOrderInfo");
4 | let language = Config.App.get().language;
5 | let make = (~order: Order.orderVm, ~paidDateChanged, _children) => {
6 | ...component,
7 | render: _self =>
8 |
9 |
10 |
11 |
12 | {sloc("order.created.header")} |
13 |
14 |
15 | | {sloc("order.created.date")} |
16 |
17 | {
18 | language === "EN" ?
19 | s(order.createdOn |> Date.toDisplayEN) :
20 | s(order.createdOn |> Date.toDisplay)
21 | }
22 | |
23 |
24 |
25 | {
26 | switch (order.paid) {
27 | | None =>
28 | | Some(paid) =>
29 |
30 |
31 | {sloc("order.paid.header")} |
32 |
33 |
34 | | {sloc("order.paid.date")} |
35 |
36 | {
37 | switch (order.returned) {
38 | | None =>
39 | Js.String.toLowerCase}
41 | value={paid.on |> Js.Date.fromFloat}
42 | onChange=(
43 | moment =>
44 | paidDateChanged(moment |> MomentRe.Moment.valueOf)
45 | )
46 | />
47 | | Some(_) =>
48 | language === "EN" ?
49 | s(paid.on |> Date.toDisplayEN) :
50 | s(paid.on |> Date.toDisplay)
51 | }
52 | }
53 | |
54 |
55 |
56 | | {sloc("order.paid.by")} |
57 | {s(paid.by)} |
58 |
59 |
60 | | {sloc("paymentMethod")} |
61 | {sloc(paid.method.name)} |
62 |
63 | {
64 | switch (paid.externalId) {
65 | | "" => ReasonReact.null
66 | | id =>
67 |
68 | | {sloc(paid.method.name ++ ".externalId")} |
69 | {s(id)} |
70 |
71 | }
72 | }
73 |
74 | }
75 | }
76 | {
77 | switch (order.returned) {
78 | | None =>
79 | | Some(returned) =>
80 |
81 |
82 |
83 | {sloc("order.returned.header")}
84 | |
85 |
86 |
87 | | {sloc("order.returned.date")} |
88 |
89 | {
90 | language === "EN" ?
91 | s(returned.on |> Date.toDisplayEN) :
92 | s(returned.on |> Date.toDisplay)
93 | }
94 | |
95 |
96 |
97 | | {sloc("order.returned.by")} |
98 | {s(returned.by)} |
99 |
100 |
101 | }
102 | }
103 |
104 |
,
115 | };
--------------------------------------------------------------------------------
/src/components/orderTaking/KeyInput.re:
--------------------------------------------------------------------------------
1 | [@bs.val] external window: Dom.window = "";
2 |
3 | [@bs.send]
4 | external addEventListener: (Dom.window, string, 'a => unit) => unit = "";
5 |
6 | [@bs.send]
7 | external removeEventListener: (Dom.window, string, 'a => unit) => unit = "";
8 |
9 | type state = {value: string};
10 |
11 | type action =
12 | | Reset
13 | | KeyDown(int);
14 |
15 | let component = ReasonReact.reducerComponent("KeyInput");
16 |
17 | let stringOrDefault = (opt: option(string)) =>
18 | switch (opt) {
19 | | None => ""
20 | | Some(s) => s
21 | };
22 |
23 | let make =
24 | (
25 | ~onCancel=() => (),
26 | ~acceptInput=true,
27 | ~onFinish,
28 | ~className="",
29 | _children,
30 | ) => {
31 | ...component,
32 | initialState: () => {value: ""},
33 | reducer: (action, state) =>
34 | switch (action) {
35 | | KeyDown(27) =>
36 | ReasonReact.UpdateWithSideEffects({value: ""}, (_ => onCancel()))
37 | | KeyDown(8) =>
38 | ReasonReact.Update({
39 | value: Js.String.slice(~from=0, ~to_=-1, state.value),
40 | })
41 | | KeyDown(20) => ReasonReact.Update({value: state.value})
42 | | KeyDown(112) => ReasonReact.Update({value: state.value})
43 | | KeyDown(113) => ReasonReact.Update({value: state.value})
44 | | KeyDown(114) => ReasonReact.Update({value: state.value})
45 | | KeyDown(115) => ReasonReact.Update({value: state.value})
46 | | KeyDown(116) => ReasonReact.Update({value: state.value})
47 | | KeyDown(117) => ReasonReact.Update({value: state.value})
48 | | KeyDown(118) => ReasonReact.Update({value: state.value})
49 | | KeyDown(119) => ReasonReact.Update({value: state.value})
50 | | KeyDown(120) => ReasonReact.Update({value: state.value})
51 | | KeyDown(121) => ReasonReact.Update({value: state.value})
52 | | KeyDown(122) => ReasonReact.Update({value: state.value})
53 | | KeyDown(123) => ReasonReact.Update({value: state.value})
54 | | KeyDown(17) => ReasonReact.Update({value: state.value})
55 | | KeyDown(38) => ReasonReact.Update({value: state.value})
56 | | KeyDown(39) => ReasonReact.Update({value: state.value})
57 | | KeyDown(37) => ReasonReact.Update({value: state.value})
58 | | KeyDown(40) => ReasonReact.Update({value: state.value})
59 | | KeyDown(45) => ReasonReact.Update({value: state.value})
60 | | KeyDown(46) => ReasonReact.Update({value: state.value})
61 | | KeyDown(18) => ReasonReact.Update({value: state.value})
62 | | KeyDown(191) => ReasonReact.Update({value: state.value})
63 | | KeyDown(192) => ReasonReact.Update({value: state.value})
64 | | KeyDown(188) => ReasonReact.Update({value: state.value})
65 | | KeyDown(190) => ReasonReact.Update({value: state.value})
66 | | KeyDown(16) => ReasonReact.Update({value: state.value})
67 | | KeyDown(35) => ReasonReact.Update({value: state.value})
68 | | KeyDown(186) => ReasonReact.Update({value: state.value})
69 | | KeyDown(189) => ReasonReact.Update({value: state.value})
70 | | KeyDown(187) => ReasonReact.Update({value: state.value})
71 | | KeyDown(219) => ReasonReact.Update({value: state.value})
72 | | KeyDown(220) => ReasonReact.Update({value: state.value})
73 | | KeyDown(221) => ReasonReact.Update({value: state.value})
74 | | KeyDown(222) => ReasonReact.Update({value: state.value})
75 | | KeyDown(13) =>
76 | ReasonReact.UpdateWithSideEffects(
77 | {value: ""},
78 | (_ => onFinish(state.value)),
79 | )
80 | | KeyDown(key) =>
81 | ReasonReact.Update({
82 | value:
83 | acceptInput ?
84 | state.value ++ (key |> Js.String.fromCharCode) : state.value,
85 | })
86 | | Reset => ReasonReact.Update({value: ""})
87 | },
88 | didMount: self => {
89 | let logKey = ev => self.send(KeyDown(ReactEvent.Keyboard.which(ev)));
90 | addEventListener(window, "keydown", logKey);
91 | self.onUnmount(() => removeEventListener(window, "keydown", logKey));
92 | },
93 | render: self =>
94 | ,
99 | };
--------------------------------------------------------------------------------
/src/components/webhooks/WebhookManagement.re:
--------------------------------------------------------------------------------
1 | open Js.Promise;
2 |
3 | type intent =
4 | | Viewing
5 | | Deleting(Webhook.t);
6 |
7 | type state = {
8 | webhooks: list(Webhook.t),
9 | intent,
10 | };
11 |
12 | type action =
13 | | LoadWebhooks(list(Webhook.t))
14 | | WebhookRemoved(Webhook.t)
15 | | ShowDialog(Webhook.t)
16 | | HideDialog
17 | | NewWebhookCreated(Webhook.t);
18 |
19 | let component = ReasonReact.reducerComponent("WebhookManagement");
20 |
21 | let make = _children => {
22 | ...component,
23 | didMount: self =>
24 | WebhookStore.getAll()
25 | |> Js.Promise.then_(webhooks => {
26 | self.send(LoadWebhooks(webhooks));
27 | Js.Promise.resolve();
28 | })
29 | |> ignore,
30 | initialState: () => {webhooks: [], intent: Viewing},
31 | reducer: (action, state) =>
32 | switch (action) {
33 | | LoadWebhooks(webhooks) => ReasonReact.Update({...state, webhooks})
34 | | ShowDialog(dis) =>
35 | ReasonReact.Update({...state, intent: Deleting(dis)})
36 | | HideDialog => ReasonReact.Update({...state, intent: Viewing})
37 | | WebhookRemoved(dis) =>
38 | ReasonReact.Update({
39 | intent: Viewing,
40 | webhooks:
41 | state.webhooks |> List.filter((d: Webhook.t) => d.id !== dis.id),
42 | })
43 | | NewWebhookCreated(dis) =>
44 | ReasonReact.Update({
45 | ...state,
46 | webhooks: List.concat([state.webhooks, [dis]]),
47 | })
48 | },
49 | render: self => {
50 | let goBack = _ => ReasonReact.Router.push("/admin");
51 | let displayDialog = (p: Webhook.t) => self.send(ShowDialog(p));
52 | let removeWebhook = (p: Webhook.t) => {
53 | WebhookStore.remove(~id=p.id)
54 | |> then_(_ => {
55 | self.send(WebhookRemoved(p));
56 | resolve();
57 | })
58 | |> ignore;
59 | ();
60 | };
61 | let createWebhook = (newWebhook: Webhook.New.t) => {
62 | WebhookStore.add(newWebhook)
63 | |> Js.Promise.then_((newWebhook: Webhook.t) => {
64 | self.send(NewWebhookCreated(newWebhook));
65 | Js.Promise.resolve();
66 | })
67 | |> ignore;
68 | ();
69 | };
70 |
71 | {
72 | switch (self.state.intent) {
73 | | Deleting(dis) =>
74 |
removeWebhook(dis))
79 | onCancel=(() => self.send(HideDialog))
80 | />
81 | | Viewing =>
82 |
83 |
84 |
85 |
86 | {ReactUtils.s("Atras")}
87 |
88 |
89 |
90 | {ReactUtils.s("Gestion de Webhooks")}
91 |
92 |
93 |
94 |
95 |
96 |
97 | | {ReactUtils.s("Nombre")} |
98 | {ReactUtils.s("Url")} |
99 | {ReactUtils.s("Evento")} |
100 | {ReactUtils.s("Fuente")} |
101 | {ReactUtils.s("Comportamiento")} |
102 | {ReactUtils.s("Tipo")} |
103 | |
104 |
105 |
106 |
107 | {
108 | self.state.webhooks
109 | |> List.map((d: Webhook.t) =>
110 |
115 | )
116 | |> Array.of_list
117 | |> ReasonReact.array
118 | }
119 |
120 |
121 |
122 |
123 |
124 | }
125 | }
126 | ;
127 | },
128 | };
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/orderTaking/orderActions.re:
--------------------------------------------------------------------------------
1 | [@bs.val] external alert : string => unit = "";
2 | open OrderHelper;
3 |
4 | type userIntent =
5 | | Building
6 | | Returning;
7 |
8 | type state = {
9 | userIntent,
10 | showModal: bool,
11 | };
12 |
13 | type action =
14 | | ChangeIntent(userIntent)
15 | | SaveAndExit
16 | | SaveAndGoToPayScreen
17 | | ShowDialog
18 | | HideDialog
19 | | DeleteAndExit
20 | | ReturnAndExit(Cashier.t);
21 |
22 | let component = ReasonReact.reducerComponent("OrderActions");
23 |
24 | let stringOrDefault = (opt: option(string)) =>
25 | switch (opt) {
26 | | None => ""
27 | | Some(s) => s
28 | };
29 |
30 | let make =
31 | (
32 | ~order: Order.orderVm,
33 | ~onShowProductModal=() => (),
34 | ~onFinish,
35 | _children,
36 | ) => {
37 | ...component,
38 | initialState: () => {userIntent: Building, showModal: false},
39 | reducer: (action, state) =>
40 | switch (action) {
41 | | ChangeIntent(intent) =>
42 | ReasonReact.Update({...state, userIntent: intent})
43 | | SaveAndExit =>
44 | ReasonReact.SideEffects((_self => saveOrder(order, onFinish)))
45 | | ReturnAndExit(cashier) =>
46 | ReasonReact.SideEffects(
47 | (_self => returnOrder(cashier, order, onFinish)),
48 | )
49 | | ShowDialog => ReasonReact.Update({...state, showModal: true})
50 | | HideDialog => ReasonReact.Update({...state, showModal: false})
51 | | DeleteAndExit =>
52 | ReasonReact.SideEffects((_self => removeOrder(order, onFinish)))
53 | | SaveAndGoToPayScreen =>
54 | ReasonReact.SideEffects(
55 | (
56 | _self =>
57 | saveOrder(order, o =>
58 | ReasonReact.Router.push(
59 | "/pay?orderId=" ++ stringOrDefault(o.id),
60 | )
61 | )
62 | ),
63 | )
64 | },
65 | render: self => {
66 | let items = order.orderItems |> Array.of_list;
67 | let disablePayButton = items |> Array.length === 0;
68 | let saveButton =
69 |