diff --git a/src/clj/chicken_master/css.clj b/src/clj/chicken_master/css.clj index 1378619..5155493 100644 --- a/src/clj/chicken_master/css.clj +++ b/src/clj/chicken_master/css.clj @@ -44,6 +44,8 @@ [:.actions {:display :none :float :right}] [:.order:hover [:.actions {:display :inline}]] + [:.order.pending {:color :grey}] + [:.order.fulfilled {:color :red}] [:.who {:font-size "18px" :font-weight :bold diff --git a/src/cljs/chicken_master/calendar.cljs b/src/cljs/chicken_master/calendar.cljs index a0fbd7d..f8c4236 100644 --- a/src/cljs/chicken_master/calendar.cljs +++ b/src/cljs/chicken_master/calendar.cljs @@ -9,7 +9,7 @@ [chicken-master.time :as time])) -(defn add-order [date] +(defn edit-order [] (html/modal [:div (html/input :who "kto" @@ -25,14 +25,19 @@ (for [[i {product :prod amount :amount}] (map-indexed vector selected-prods)] (prod/product-item product amount available-prods i))]) [:button {:type :button :on-click #(re-frame/dispatch [::event/add-product])} "+"]] - js/console.log)) + ;; On success + (fn [] (re-frame/dispatch [::event/save-order])))) -(defn format-order [{:keys [id who day hour products]}] - [:li {:class :order :key (gensym)} +(defn format-order [{:keys [id who day hour products state]}] + [:li {:class [:order state] :key (gensym)} [:div {:class :actions} - [:button "O"] + (condp = state + :waiting [:button {:on-click #(re-frame/dispatch [::event/fulfill-order id])} "✓"] + :fulfilled [:button {:on-click #(re-frame/dispatch [::event/reset-order id])} "X"] + :pending nil + :default nil) [:button {:on-click #(re-frame/dispatch [::event/edit-order day id])} "E"] - [:button "-"]] + [:button {:on-click #(re-frame/dispatch [::event/remove-order id])} "-"]] [:div {:class :who} who] (if (settings :show-order-time) [:div {:class :when} hour]) @@ -45,17 +50,21 @@ [:div {:class :day-header} (time/format-date date)] [:div [:ul {:class :orders} - (map format-order customers) + (if (settings :hide-fulfilled-orders) + (->> customers (remove (comp #{:fulfilled} :state)) (map format-order)) + (map format-order customers)) [:button {:type :button - :on-click #(re-frame/dispatch [::event/edit-order date])} "+"]]]]) + :on-click #(re-frame/dispatch [::event/edit-order (time/iso-date date)])} "+"]]]]) (defn calendar-header [] - (->> (settings :day-names) + (->> (:day-names settings) + cycle (drop (:first-day-offset settings)) + (take 7) (map (fn [day] [:div {:class :day-header} day])) (into []))) (defn calendar [days] (->> days (map day) - (concat (when-not (settings :always-day-names) (calendar-header))) + (concat (when (settings :calendar-heading) (calendar-header))) (into [:div {:class [:calendar :full-height]}]))) diff --git a/src/cljs/chicken_master/config.cljs b/src/cljs/chicken_master/config.cljs index 1e1c310..6f63915 100644 --- a/src/cljs/chicken_master/config.cljs +++ b/src/cljs/chicken_master/config.cljs @@ -3,7 +3,13 @@ (def debug? ^boolean goog.DEBUG) -(def settings {:first-day-offset 1 - :day-names ["Niedz" "Pon" "Wt" "Śr" "Czw" "Pt" "Sob"] - :always-day-names true - :show-order-time false}) +(def settings {:first-day-offset 1 ; which is the first day of the week (add the offset to `day-names`) + :day-names ["Niedz" "Pon" "Wt" "Śr" "Czw" "Pt" "Sob"] ; how day should be displayed in the calendar view + :calendar-heading false ; show a header with the names of days + :show-date true ; display the date for each day + :show-day-name-with-date true ; add the day name to each date + + :show-order-time false ; display the time of each order + :editable-number-inputs false ; only allow number modifications in the edit modal + :hide-fulfilled-orders false + }) diff --git a/src/cljs/chicken_master/core.cljs b/src/cljs/chicken_master/core.cljs index e235e86..27e934e 100644 --- a/src/cljs/chicken_master/core.cljs +++ b/src/cljs/chicken_master/core.cljs @@ -20,6 +20,6 @@ (defn init [] (re-frame/dispatch-sync [::events/initialize-db]) - (re-frame/dispatch-sync [::events/show-from-date (new js/Date "2020-09-05")]) + (re-frame/dispatch-sync [::events/show-from-date (new js/Date)]) (dev-setup) (mount-root)) diff --git a/src/cljs/chicken_master/db.cljs b/src/cljs/chicken_master/db.cljs index 0fc6ff6..9972a00 100644 --- a/src/cljs/chicken_master/db.cljs +++ b/src/cljs/chicken_master/db.cljs @@ -1,31 +1,26 @@ (ns chicken-master.db) (def default-db - {:name "re-frame" - :order-edit {:show nil - :who "mr. blobby" - :when "12:32" - :products [{:prod :eggs :amount 2} - {:prod :milk :amount 5} - {}]} - :customers {1 {:id 1 :who "mr.blobby (649 234 234)" :day "2020-10-10" :hour "02:12" :products {:eggs 2 :milk 3}} - 2 {:id 2 :who "da police (0118 999 881 999 119 725 123123 12 3123 123 )" :day "2020-10-10" :hour "02:12" :products {:eggs 12}} - 3 {:id 3 :who "johnny" :day "2020-10-10" :hour "02:12" :products {:eggs 5}}} - :days {"2020-09-05" [1 2 3] - "2020-09-06" [1 2 3] - "2020-09-07" [1 2 3] - "2020-09-08" [1 2 3] - "2020-09-09" [1 2 3] - "2020-09-10" [1 2 3] - "2020-09-11" [1 2 3] - "2020-09-12" [1 2 3] - "2020-09-13" [1 2 3] - "2020-09-14" [1 2 3] - "2020-09-15" [1 2 3] - "2020-09-16" [1 2 3] - "2020-09-17" [1 2 3] - "2020-09-18" [1 2 3]} + {;; set automatically + ;; :customers {1 {:id 1 :who "mr.blobby (649 234 234)" :day "2020-10-10" :hour "02:12" :products {:eggs 2 :milk 3}} + ;; 2 {:id 2 :who "da police (0118 999 881 999 119 725 123123 12 3123 123 )" :day "2020-10-10" :hour "02:12" :products {:eggs 12}} + ;; 3 {:id 3 :who "johnny" :day "2020-10-10" :hour "02:12" :products {:eggs 5}}} + ;; :days {"2020-10-05" [1 2 3] + ;; "2020-10-06" [1 2 3] + ;; "2020-10-07" [1 2 3] + ;; "2020-10-08" [1 2 3] + ;; "2020-10-09" [1 2 3] + ;; "2020-10-10" [1 2 3] + ;; "2020-10-11" [1 2 3] + ;; "2020-10-12" [1 2 3] + ;; "2020-10-13" [1 2 3] + ;; "2020-10-14" [1 2 3] + ;; "2020-10-15" [1 2 3] + ;; "2020-10-16" [1 2 3] + ;; "2020-10-17" [1 2 3] + ;; "2020-10-18" [1 2 3]} :products {:eggs {} :milk {} :cabbage {} - :carrots {}}}) + :carrots {}} + }) diff --git a/src/cljs/chicken_master/events.cljs b/src/cljs/chicken_master/events.cljs index 6526ea2..0eb061d 100644 --- a/src/cljs/chicken_master/events.cljs +++ b/src/cljs/chicken_master/events.cljs @@ -2,19 +2,62 @@ (:require [re-frame.core :as re-frame] [chicken-master.db :as db] + [chicken-master.orders :as orders] [chicken-master.time :as time])) (re-frame/reg-event-db ::initialize-db (fn [_ _] db/default-db)) (re-frame/reg-event-db ::hide-modal (fn [db _] (assoc db :order-edit {}))) -(re-frame/reg-event-db - ::edit-order - (fn [{customers :customers :as db} [_ day id]] - (assoc db :order-edit - (-> customers - (get id) - (update :products (comp vec (partial map (partial zipmap [:prod :amount])))) - (merge {:show true :day day}))))) + +(re-frame/reg-event-fx + ::remove-order + (fn [_ [_ id]] + {:http {:method :post + :url "delete-order" + :params {:id id} + :on-success [::process-fetched-days] + :on-fail [::failed-blah]}})) + + (re-frame/reg-event-db + ::edit-order + (fn [{customers :customers :as db} [_ day id]] + (assoc db :order-edit + (-> customers + (get id) + (update :products (comp vec (partial map (partial zipmap [:prod :amount])))) + (merge {:show true :day day}))))) + +(re-frame/reg-event-fx + ::fulfill-order + (fn [{db :db} [_ id]] + {:db (assoc-in db [:customers id :state] :pending) + :fx [[:dispatch [::set-current-days]]] + :http {:method :post + :url "fulfill-order" + :params {:id id} + :on-success [::process-fetched-days] + :on-fail [::failed-blah]}})) + +(re-frame/reg-event-fx + ::reset-order + (fn [{db :db} [_ id]] + {:db (assoc-in db [:customers id :state] :waiting) + :fx [[:dispatch [::set-current-days]]] + :http {:method :post + :url "reset-order" + :params {:id id} + :on-success [::process-fetched-days] + :on-fail [::failed-blah]}})) + +(re-frame/reg-event-fx + ::save-order + (fn [{{order :order-edit} :db} _] + {:fx [[:dispatch [::hide-modal]]] + :http {:method :post + :url "save-order" + :params (orders/clean-order order) + :on-success [::process-fetched-days] + :on-fail [::failed-blah]}})) (re-frame/reg-event-db ::add-product (fn [db _] (update-in db [:order-edit :products] conj {}))) (re-frame/reg-event-db @@ -34,12 +77,66 @@ (get days) (map customers))}) - (re-frame/reg-event-db - ::show-from-date - (fn [db [_ date]] - (->> date + ::set-current-days + (fn [{start-day :start-date :as db} _] + (->> start-day + time/parse-date time/start-of-week (time/days-range 14) (map (partial get-day db)) (assoc db :current-days)))) + +(re-frame/reg-event-fx + ::process-fetched-days + (fn [{db :db} [_ days]] + (println "fetched days" days) + {:db (-> db + (update :days #(reduce-kv (fn [m k v] (assoc m k (map :id v))) % days)) + (update :customers #(reduce (fn [m cust] (assoc m (:id cust) cust)) % (-> days vals flatten)))) + :fx [[:dispatch [::set-current-days]]]})) + + +(defn missing-days + "Return a map of missing days to be fetched." + [db day] + (let [missing-days (->> day + time/parse-date + time/start-of-week + (time/days-range 28) + (remove (comp (:days db {}) time/iso-date)))] + (when (seq missing-days) + {:from (->> missing-days (sort time/before?) first time/iso-date) + :to (->> missing-days (sort time/before?) last time/iso-date)}))) + +(re-frame/reg-event-fx + ::show-from-date + (fn [{db :db} [_ day]] + (let [missing (missing-days db day) + effects {:db (assoc db :start-date day) + :fx [[:dispatch [::set-current-days]]]}] + (if-not missing + effects + (assoc effects :http {:method :get + :url "get-days" + :params missing + :on-success [::process-fetched-days] + :on-fail [::failed-blah]}))))) + + +(comment + (re-frame/dispatch-sync [::show-from-date "2020-11-11"]) + ) +;;;;;;;; Backend mocks + +(re-frame/reg-fx + :http + (fn [{:keys [method url params on-success on-fail]}] + (println params) + (condp = url + "get-days" (re-frame/dispatch (conj on-success (orders/fetch-days params))) + "save-order" (re-frame/dispatch (conj on-success (orders/replace-order params))) + "delete-order" (re-frame/dispatch (conj on-success (orders/delete-order params))) + "fulfill-order" (re-frame/dispatch (conj on-success (orders/order-state (assoc params :state :fulfilled)))) + "reset-order" (re-frame/dispatch (conj on-success (orders/order-state (assoc params :state :waiting)))) + ))) diff --git a/src/cljs/chicken_master/html.cljs b/src/cljs/chicken_master/html.cljs index 2b32b36..7c8e92c 100644 --- a/src/cljs/chicken_master/html.cljs +++ b/src/cljs/chicken_master/html.cljs @@ -16,7 +16,11 @@ (defn modal [content on-submit] [:div {:class :popup} - [:form {:action "#" :on-submit on-submit} + [:form {:action "#" + :on-submit (fn [e] + (.preventDefault e) + (when (on-submit) + (re-frame/dispatch [::event/hide-modal])))} content [:div {:class :form-buttons} [:button "add"] diff --git a/src/cljs/chicken_master/orders.cljs b/src/cljs/chicken_master/orders.cljs new file mode 100644 index 0000000..8e6a215 --- /dev/null +++ b/src/cljs/chicken_master/orders.cljs @@ -0,0 +1,78 @@ +(ns chicken-master.orders + (:require [chicken-master.time :as time])) + +(defn clean-order [order] + (-> order + (update :products #(->> % + (group-by :prod) + (reduce-kv (fn [m k v] + (assoc m k (->> v + (map :amount) + (reduce +)))) {}))) + (select-keys [:id :who :day :hour :products]))) + +;;;;;;;; Backend mocks + +(def id-counter (atom -1)) +(def days (atom + (->> (time/date-offset (new js/Date) -50) + (time/days-range 90) + (map (fn [date] + [(time/iso-date date) (repeatedly (rand-int 6) #(swap! id-counter inc))])) + (into {})))) +(def products (atom [:eggs :milk :cabbage :carrots])) +(def customer-names (atom ["mr.blobby (649 234 234)" "da police (0118 999 881 999 119 725 123123 12 3123 123 )" "johnny"])) + +(def customers + (atom + (->> @days + (map (fn [[day ids]] + (map (fn [i] + {:id i :day day :hour "02:12" :state :waiting + :who (rand-nth @customer-names) + :products (->> @products + (random-sample 0.4) + (map #(vector % (rand-int 10))) + (into {})) + }) ids) + )) + flatten + (map #(vector (:id %) %)) + (into {})))) + +(defn- day-customers [day] [day (->> day (get @days) (map (partial get @customers)))]) +(defn- days-between [from to] + (time/days-range + (int (/ (- (time/parse-date to) (time/parse-date from)) (* 24 3600000))) + (time/parse-date from))) +(defn fetch-days [{:keys [from to]}] + (->> (days-between from to) + (map time/iso-date) + (map day-customers) + (into {}))) + +(defn- replace-order [order] + (println "replacing order" order) + (let [order (update order :id #(or % (swap! id-counter inc)))] + (swap! days (fn [ds] (update ds (:day order) #(distinct (conj % (:id order)))))) + (swap! customers #(assoc % (:id order) order)) + {(->> order :day) (->> order :day day-customers second)})) + +(defn- delete-order [{id :id}] + (println "deleting order" id) + (let [day (-> (get @customers id) :day)] + (swap! days (fn [ds] (update ds day (partial remove #{id})))) + (swap! customers #(dissoc % id)) + {day (->> day day-customers second)})) + +(defn- order-state [{id :id state :state}] + (println "fulfilling order" id) + (let [day (-> (get @customers id) :day)] + (swap! customers #(assoc-in % [id :state] state)) + (println id (get @customers id)) + {day (->> day day-customers second)})) + +(comment +(replace-order + {:id 194, :day "2020-11-21", :hour "02:12", :who "mr.blobby (649 234 234)", :products {:eggs 13 :milk 4 :cabbage 7}}) +) diff --git a/src/cljs/chicken_master/products.cljs b/src/cljs/chicken_master/products.cljs index 4e2d33a..ace8890 100644 --- a/src/cljs/chicken_master/products.cljs +++ b/src/cljs/chicken_master/products.cljs @@ -1,6 +1,7 @@ (ns chicken-master.products (:require [re-frame.core :as re-frame] + [chicken-master.config :refer [settings]] [chicken-master.html :as html] [chicken-master.events :as event])) @@ -15,9 +16,13 @@ [:option {:key (gensym) :value product} (name product)])]] (html/input :amount "ile" {:type :number :default amount :min 0 - :on-blur #(re-frame/dispatch [::event/changed-amount (-> % .-target .-value) product-no])})]) + ;; :on-blur #(re-frame/dispatch [::event/changed-amount (-> % .-target .-value) product-no]) + :on-input #(re-frame/dispatch [::event/changed-amount (-> % .-target .-value) product-no]) + })]) (defn format-product [[product amount]] [:li {:key (gensym) :class :product} - [:input {:class :product-amount :type :number :min 0 :defaultValue amount}] + (if (settings :editable-number-inputs) + [:input {:class :product-amount :type :number :min 0 :defaultValue amount}] + [:span {:class :product-amount} amount]) [:span {:class :product-name} product]]) diff --git a/src/cljs/chicken_master/subs.cljs b/src/cljs/chicken_master/subs.cljs index 44b4e0d..d56212f 100644 --- a/src/cljs/chicken_master/subs.cljs +++ b/src/cljs/chicken_master/subs.cljs @@ -1,7 +1,5 @@ (ns chicken-master.subs - (:require - [re-frame.core :as re-frame] - [chicken-master.time :as time])) + (:require [re-frame.core :as re-frame])) (re-frame/reg-sub ::name (fn [db] (:name db))) (re-frame/reg-sub ::available-products (fn [db] (-> db :products keys))) @@ -11,5 +9,4 @@ (re-frame/reg-sub ::order-edit-when (fn [db] (-> db :order-edit :hour))) (re-frame/reg-sub ::order-edit-products (fn [db] (-> db :order-edit :products))) - (re-frame/reg-sub ::current-days (fn [db] (:current-days db))) diff --git a/src/cljs/chicken_master/time.cljs b/src/cljs/chicken_master/time.cljs index 9f71825..7e61dc7 100644 --- a/src/cljs/chicken_master/time.cljs +++ b/src/cljs/chicken_master/time.cljs @@ -2,6 +2,8 @@ (:require [chicken-master.config :refer [settings]]) (:import [goog.date DateTime Date Interval])) +(defn parse-date [date] (new Date (js/Date. date))) + (defn date-offset "Return the `date` offset by the given number of `days`" [date days] @@ -12,15 +14,17 @@ (defn start-of-week "Get the start of the week for the given `date" [date] - (->> (.getDay date) - (- (settings :first-day-offset)) - (date-offset date))) + (let [offset (mod (+ 7 (.getDay date) (- (settings :first-day-offset))) 7)] + (date-offset date (- offset)))) (defn days-range "Return dates starting from `date`" ([date] (map (partial date-offset date) (range))) ([n date] (take n (days-range date)))) +(defn before? [d1 d2] (< (Date/compare d1 d2) 0)) +(defn after? [d1 d2] (> (Date/compare d1 d2) 0)) + (defn same-day? "Returns true when both dates are from the same day" [d1 d2] (-> (new Date d1) @@ -29,8 +33,24 @@ (defn today? "true when `d1` is today" [d1] (same-day? (js/Date.) d1)) (defn format-date [date] - (str - (->> date .getDay (nth (settings :day-names))) - " " (.getMonth date) "/" (.getDate date))) + (when (settings :show-date) + (if (settings :show-day-name-with-date) + (str + (->> date .getDay (nth (settings :day-names))) + " " (inc (.getMonth date)) "/" (.getDate date)) + (str (inc (.getMonth date)) "/" (.getDate date))))) (defn iso-date [date] (.toIsoString ^js/goog.date.Date date true)) + + +(comment + (with-redefs [settings {:first-day-offset 0}] + (= (->> (parse-date "2020-11-22") start-of-week iso-date) "2020-11-22") + (filter #(= (-> (parse-date %) start-of-week iso-date) "2020-11-22") + ["2020-11-22" "2020-11-23" "2020-11-24" "2020-11-25" "2020-11-26" "2020-11-27" "2020-11-28"])) + + (with-redefs [settings {:first-day-offset 1}] + (= (->> (parse-date "2020-11-22") start-of-week iso-date) "2020-11-16") + (filter #(= (-> (parse-date %) start-of-week iso-date) "2020-11-23") + ["2020-11-23" "2020-11-24" "2020-11-25" "2020-11-26" "2020-11-27" "2020-11-28" "2020-11-29" ])) +) diff --git a/src/cljs/chicken_master/views.cljs b/src/cljs/chicken_master/views.cljs index b88aa40..97c1912 100644 --- a/src/cljs/chicken_master/views.cljs +++ b/src/cljs/chicken_master/views.cljs @@ -10,6 +10,6 @@ (let [name (re-frame/subscribe [::subs/name])] [:div {:class :full-height} (when @(re-frame/subscribe [::subs/show-edit-modal]) - (cal/add-order (new js/Date))) + (cal/edit-order)) (cal/calendar @(re-frame/subscribe [::subs/current-days])) ]))