diff --git a/resources/public/output.css b/resources/public/output.css index bf55e1ee..24a68b5c 100644 --- a/resources/public/output.css +++ b/resources/public/output.css @@ -1368,6 +1368,10 @@ input:checked + .toggle-bg { width: 100%; } +.w-48 { + width: 12rem; +} + .max-w-2xl { max-width: 42rem; } @@ -1520,6 +1524,10 @@ input:checked + .toggle-bg { align-items: center; } +.items-baseline { + align-items: baseline; +} + .items-stretch { align-items: stretch; } @@ -1677,6 +1685,11 @@ input:checked + .toggle-bg { border-top-right-radius: 0.25rem; } +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + .border { border-width: 1px; } @@ -1835,16 +1848,16 @@ input:checked + .toggle-bg { background-color: rgb(255 154 154 / var(--tw-bg-opacity)); } +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(255 104 104 / var(--tw-bg-opacity)); +} + .bg-red-50 { --tw-bg-opacity: 1; background-color: rgb(255 230 230 / var(--tw-bg-opacity)); } -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(255 3 3 / var(--tw-bg-opacity)); -} - .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -1859,15 +1872,15 @@ input:checked + .toggle-bg { background-color: rgb(253 246 178 / var(--tw-bg-opacity)); } -.bg-red-300 { - --tw-bg-opacity: 1; - background-color: rgb(255 104 104 / var(--tw-bg-opacity)); -} - .bg-opacity-50 { --tw-bg-opacity: 0.5; } +.object-contain { + -o-object-fit: contain; + object-fit: contain; +} + .p-1 { padding: 0.25rem; } @@ -2012,6 +2025,10 @@ input:checked + .toggle-bg { text-align: right; } +.align-baseline { + vertical-align: baseline; +} + .text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -2078,6 +2095,11 @@ input:checked + .toggle-bg { line-height: 1.25; } +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + .text-blue-400 { --tw-text-opacity: 1; color: rgb(51 176 238 / var(--tw-text-opacity)); @@ -2758,6 +2780,10 @@ input:checked + .toggle-bg { --tw-ring-offset-color: #1F2937; } +:is(.dark .dark\:ring-offset-gray-700) { + --tw-ring-offset-color: #374151; +} + :is(.dark .dark\:hover\:bg-blue-600:hover) { --tw-bg-opacity: 1; background-color: rgb(0 125 187 / var(--tw-bg-opacity)); @@ -2858,6 +2884,15 @@ input:checked + .toggle-bg { --tw-ring-color: rgb(97 145 37 / var(--tw-ring-opacity)); } +:is(.dark .dark\:focus\:ring-blue-600:focus) { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(0 125 187 / var(--tw-ring-opacity)); +} + +:is(.dark .dark\:focus\:ring-offset-gray-700:focus) { + --tw-ring-offset-color: #374151; +} + :is(.dark .group:hover .dark\:group-hover\:text-white) { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); diff --git a/src/clj/auto_ap/datomic/sales_orders.clj b/src/clj/auto_ap/datomic/sales_orders.clj index 36c5fe00..09f9c409 100644 --- a/src/clj/auto_ap/datomic/sales_orders.clj +++ b/src/clj/auto_ap/datomic/sales_orders.clj @@ -164,3 +164,8 @@ matching-count (summarize-orders ids-to-retrieve)])) +(defn summarize-graphql [args] + (let [db (dc/db conn) + {ids-to-retrieve :ids matching-count :count} (mu/trace ::get-sales-order-ids [] (raw-graphql-ids db args))] + (summarize-orders ids-to-retrieve))) + diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 5df4f447..9f9e1484 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -9,7 +9,8 @@ [auto-ap.ssr.components.page :as page] [auto-ap.ssr.components.data-grid :as data-grid] [auto-ap.ssr.components.tags :as tags] - [auto-ap.ssr.components.paginator :as paginator])) + [auto-ap.ssr.components.paginator :as paginator] + [auto-ap.ssr.components.radio :as radio])) (def breadcrumbs breadcrumbs/breadcrumbs-) @@ -36,6 +37,7 @@ (def navbar navbar/navbar-) (def page page/page-) +(def radio radio/radio-) (def pill tags/pill-) (def badge tags/badge-) diff --git a/src/clj/auto_ap/ssr/components/radio.clj b/src/clj/auto_ap/ssr/components/radio.clj new file mode 100644 index 00000000..150ef0fb --- /dev/null +++ b/src/clj/auto_ap/ssr/components/radio.clj @@ -0,0 +1,13 @@ +(ns auto-ap.ssr.components.radio) + +(defn radio- [{:keys [options name title]}] + [:h3 {:class "mb-4 font-semibold text-gray-900 dark:text-white"} title] + [:ul {:class "w-48 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white"} + (for [{:keys [value content]} options] + [:li {:class "w-full border-b border-gray-200 rounded-t-lg dark:border-gray-600"} + [:div {:class "flex items-center pl-3"} + [:input {:id (str "list-" name "-" value) + :type "radio", + :value value + :name name :class "w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 dark:focus:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"}] + [:label {:for (str "list-" name "-" value) :class "w-full py-3 ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"} content]]])]) diff --git a/src/clj/auto_ap/ssr/grid_page_helper.clj b/src/clj/auto_ap/ssr/grid_page_helper.clj index 14d24d3e..357c7986 100644 --- a/src/clj/auto_ap/ssr/grid_page_helper.clj +++ b/src/clj/auto_ap/ssr/grid_page_helper.clj @@ -44,17 +44,24 @@ first :sort-icon)) -(defn sort-by-list [sort] +(defn sort-by-list [grid-spec sort] (if (seq sort) (into [:div.flex.gap-2.items-center + "sorted by" ] - (for [{:keys [name sort-icon ]} sort] + (for [{:keys [name sort-icon sort-key ]} sort] [:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon] [:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"} - [:div.h-4.w-4 svg/x] + [:a {:href (str (bidi/path-for ssr-routes/only-routes + (:route grid-spec)) "?remove-sort=" sort-key) + :hx-boost "true" + :hx-target (str "#" (:id grid-spec)) + + } + [:div.h-4.w-4 svg/x]] ]] )) "default sort")) @@ -73,7 +80,7 @@ :total total :subtitle [:div.flex.items-center.gap-2 [:span (format "Total %s: %d, " (:entity-name grid-spec) total)] - (sort-by-list sort)] + (sort-by-list grid-spec sort)] :action-buttons ((:action-buttons grid-spec) user params) :rows (for [entity entities] (row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity)) :params params})) @@ -169,7 +176,7 @@ (defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}] (let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params - {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params + {q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort" q-remove-sort "remove-sort"} query-params raw-query-params (merge (or hx-query-params {}) query-params) parsed-query-params (cond-> (into {} (map (fn [[k v]] [(keyword k) v]) raw-query-params)) @@ -180,7 +187,8 @@ hx-sort (assoc :sort (doto (parse-sort grid-spec hx-sort) println)) q-sort (assoc :sort (doto (parse-sort grid-spec q-sort) println )) (not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) ) - true (dissoc :toggle-sort))] + (not-empty q-remove-sort) (update :sort (fn [s] (filter (comp (complement #{q-remove-sort}) :sort-key) s)) ) + true (dissoc :toggle-sort :remove-sort))] {:raw-query-params raw-query-params :parsed-query-params parsed-query-params :client-selection (:client-selection (:session request)) diff --git a/src/clj/auto_ap/ssr/pos/sales_orders.clj b/src/clj/auto_ap/ssr/pos/sales_orders.clj index 03cc6b3b..48dbb610 100644 --- a/src/clj/auto_ap/ssr/pos/sales_orders.clj +++ b/src/clj/auto_ap/ssr/pos/sales_orders.clj @@ -11,11 +11,11 @@ [bidi.bidi :as bidi] [clj-time.coerce :as coerce])) -;; TODO more filters ;; TODO refunds ;; TODO expected deposits -;; TODO navigate between pages shouldnt copy sort if not applicable -;; TODO remove sort button should work +;; TODO loading screen +;; TODO default date range +;; always should be fast (defn filters [params] [:form {"hx-trigger" "change delay:1000ms" @@ -33,11 +33,84 @@ (com/date-input {:name "end-date" :value (:end-date (:parsed-query-params params)) :placeholder "Date"}))] - [:div + [:div (com/field {:label "Total"} - (com/money-input {:name "total-gte" - :value (:total-gte (:parsed-query-params params)) - :placeholder "Total >="}))]]]) + [:div.flex.space-x-4.items-baseline + (com/money-input {:name "total-gte" + :value (:total-gte (:parsed-query-params params)) + :placeholder "0.01"}) + [:div.align-baseline + "to"] + (com/money-input {:name "total-lte" + :value (:total-lte (:parsed-query-params params)) + :placeholder "9999.34"})])] + [:div + (com/field {:label "Payment Method"} + (com/radio {:name "payment-method" + :options [{:value "all" + :content "All"} + {:value "cash" + :content "Cash"} + {:value "card" + :content "Card"} + {:value "gift-card" + :content "Gift Card"} + {:value "other" + :content "Other"} + ]}))] + [:div + + (com/field {:label "Processor"} + (com/radio {:name "processor" + :options [{:value "" + :content "All"} + {:value "square" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/square.png" :style {:width "16px" :height "16px"}}] [:div "Square"]]} + {:value "doordash" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/doordash.png" :style {:width "16px" :height "16px"}}] [:div "Doordash"]]} + {:value "uber-eats" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ubereats.png" :style {:width "16px" :height "16px"}}] [:div "Uber eats"]]} + {:value "grubhub" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/grubhub.png" :style {:width "16px" :height "16px"}}] [:div "Grubhub"]]} + {:value "koala" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/koala.png" :style {:width "16px" :height "16px"}}] [:div "Koala"]]} + {:value "ezcater" + :content [:div.flex.space-x-2 [:img.align-center {:src "/img/ezcater.png" :style {:width "16px" :height "16px"}}] [:div "EZCater"]]} + {:value "na" + :content "No Processor"} + ]}))] + + [:div + (com/field {:label "Total"} + (com/text-input {:name "category" + :value (:category (:parsed-query-params params)) + :placeholder "Fries"}))]]]) + +(defn args->graphql-params [args] + {:clients (:clients args) + :start (:start (:parsed-query-params args)) + :sort (:sort (:parsed-query-params args)) + :per-page (:per-page (:parsed-query-params args)) + :category (not-empty (:category (:parsed-query-params args))) + :date-range {:start (some-> args + :raw-query-params + (get "start-date") + (atime/parse atime/iso-date)) + :end (some-> args + :raw-query-params + (get "end-date") + (atime/parse atime/iso-date))} + :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + :total-lte (some-> args :raw-query-params (get "total-lte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) + :type-name (condp = (:payment-method (:parsed-query-params args)) + "cash" "CASH" + "" nil + "all" nil + "card" "CARD" + "gift-card" "SQUARE_GIFT_CARD" + "other" "OTHER" + nil) + :processor (some-> args :parsed-query-params :processor not-empty keyword)}) (def grid-page {:id "sales-table" :nav (com/main-aside-nav) @@ -45,31 +118,27 @@ :id-fn :db/id :fetch-page (fn [user args] (d-sales/get-graphql - {:clients (:clients args) - :start (:start (:parsed-query-params args)) - :sort (:sort (:parsed-query-params args)) - :per-page (:per-page (:parsed-query-params args)) - :date-range {:start (some-> args - :raw-query-params - (get "start-date") - (atime/parse atime/iso-date)) - :end (some-> args - :raw-query-params - (get "end-date") - (atime/parse atime/iso-date))} - :total-gte (some-> args :raw-query-params (get "total-gte") not-empty (#(if (string? %) (Double/parseDouble %) (double %)))) })) + (args->graphql-params args) + )) :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :company)} - "POS"] + "POS"] - [:a {:href (bidi/path-for ssr-routes/only-routes - :pos-sales)} - "Sales"]] + [:a {:href (bidi/path-for ssr-routes/only-routes + :pos-sales)} + "Sales"]] :title "Sales orders" :entity-name "Sales orders" :route :pos-sales-table - :action-buttons (fn [user _] - nil) + :action-buttons (fn [user args] + (let [{:keys [total tax]} (d-sales/summarize-graphql (args->graphql-params args))] + [ + (com/pill {:color :primary} + (format "Total $%.2f" total) + ) + (com/pill {:color :secondary} + (format "Tax $%.2f" tax ) + )])) :row-buttons (fn [user e] (when (:sales-order/reference-link e) @@ -82,42 +151,42 @@ :hx-target "closest tr"} svg/trash))])) :headers [ - {:key "client" - :name "Client" - :sort-key "client" - :hide? (fn [args] - (= (count (:clients args)) 1)) - :render #(-> % :sales-order/client :client/code)} - {:key "date" - :name "Date" - :sort-key "date" - :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} - {:key "source" - :name "Source" - :sort-key "source" - :render (fn [sales-order] - (when (:sales-order/source sales-order) - (com/pill {:color :primary } - (:sales-order/source sales-order))))} - {:key "total" - :name "Total" - :sort-key "total" - :render #(some->> % :sales-order/total (format "$%.2f"))} - {:key "tax" - :name "Tax" - :sort-key "tax" - :render #(some->> % :sales-order/tax (format "$%.2f"))} - {:key "tip" - :name "Tip" - :sort-key "tip" - :render #(some->> % :sales-order/tip (format "$%.2f"))} - {:key "Payment methods" - :name "Payment Methods" - :render (fn [sales-order] - (println ) - (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] - (com/pill {:color :primary } - method)))}]}) + {:key "client" + :name "Client" + :sort-key "client" + :hide? (fn [args] + (= (count (:clients args)) 1)) + :render #(-> % :sales-order/client :client/code)} + {:key "date" + :name "Date" + :sort-key "date" + :render #(atime/unparse-local (:sales-order/date %) atime/standard-time)} + {:key "source" + :name "Source" + :sort-key "source" + :render (fn [sales-order] + (when (:sales-order/source sales-order) + (com/pill {:color :primary } + (:sales-order/source sales-order))))} + {:key "total" + :name "Total" + :sort-key "total" + :render #(some->> % :sales-order/total (format "$%.2f"))} + {:key "tax" + :name "Tax" + :sort-key "tax" + :render #(some->> % :sales-order/tax (format "$%.2f"))} + {:key "tip" + :name "Tip" + :sort-key "tip" + :render #(some->> % :sales-order/tip (format "$%.2f"))} + {:key "Payment methods" + :name "Payment Methods" + :render (fn [sales-order] + (println ) + (for [method (->> sales-order :sales-order/charges (map :charge/type-name) set)] + (com/pill {:color :primary } + method)))}]}) (def row* (partial helper/row* grid-page)) (def table* (partial helper/table* grid-page))