(import-macros {:compile-html HTML} :macros) (local lib (require :lib)) (local shop (require :shop)) (local dicts (require :dicts)) (local templates (require :templates)) (local synonyms (require :synonyms)) (local pagination-limit 12) (local texts { :address (lib.improve-typography "г. Омск, ул. Пушкина, д. 133/9, этаж 2. Вход с крыльца Магнита, дверь слева, домофон 4. Дверь в офисе узнаете по нашему логотипу.")}) (fn all-products [db page filters] (local where-stmts []) (local where-args []) (var has-search-query? false) (when filters.only-published? (table.insert where-stmts "products.published = true")) (when filters.type (table.insert where-stmts "products.type = ?") (table.insert where-args filters.type)) (when filters.search (table.insert where-stmts "products_search MATCH ?") (var query (synonyms.replace filters.search)) (when (not (lib.ends-with? query "*")) (set query (.. query "*"))) (table.insert where-args query) (set has-search-query? true)) (local where (if (< 0 (# where-stmts)) (.. "WHERE " (table.concat where-stmts " AND\n")) "")) (local from-sql (if has-search-query? "products_search INNER JOIN products ON products_search.name = products.name" "products")) {:total (. (_G.must (luna.db.query db (.. "SELECT COUNT(products.ROWID) FROM " from-sql " " where) where-args)) 1 1) :products (_G.must (luna.db.query-assoc db (.. "SELECT products.name, products.title, products.published, products.short_description as \"short-description\", products.price_per AS \"price-per\", products.volume, products.stock, products.packaging, products.type, products.region, products.image1, products.image2, products.image3, products.image4, products.image5 FROM " from-sql " " where " ORDER BY products.position ASC, products.creation_time DESC" (if has-search-query? ", rank" "") " LIMIT ? OFFSET ?") (lib.concat where-args [pagination-limit (* (- page 1) pagination-limit)])))}) (fn filters-path [page filters] (.. "/shop?page=" (tostring page) (if filters.type (.. "&type=" filters.type) "") (if filters.search (.. "&search=" filters.search) ""))) (fn paginator-template [filters page limit total classes] (if (< limit total) (HTML [:div {:class (.. "paginator " classes)} (if (< 1 page) (HTML [:div {} [:a {:href (filters-path (- page 1) filters)} "⟵ Предыдущая страница"]]) "") (if (< page (math.ceil (/ total limit))) (HTML [:div {} [:a {:href (filters-path (+ page 1) filters)} "Следующая страница ⟶"]]) "")]) "")) (fn item-template [product basket redirect-url] (local item-url (.. "/shop/" product.name)) (local images []) (for [i 2 5] (table.insert images (. product (.. "image" i)))) (HTML [:section {:class (.. "shop-item" (if (not product.published) " shop-item-not-published" ""))} [:a {:href item-url} [:div {:class "shop-item-imgs"} [:img {:class "shop-item-img" :data-parser "image" :src (.. "/static/files/" (. product.image1) "-thumbnail.jpg")}] (table.concat (let [without-videos (icollect [_ v (ipairs images)] (if (lib.ends-with? (_G.must (luna.utf8.lower v)) ".webm") nil v))] (icollect [idx image (ipairs without-videos)] (HTML [:img {:class "shop-item-img" :src (.. "/static/files/" image "-thumbnail.jpg") :loading "lazy" :style (.. "z-index: " (+ idx 2) ";" "width: calc(100% / " (# without-videos) ");" "left: calc(100% / " (# without-videos) " * " (- idx 1) ")")}]))))]] [:a {:href item-url} [:h3 {:class "shop-item-title" :data-parser "title"} product.title]] (templates.product-overview product "mb-0-25 font-size-0-875") [:div {:class "shop-item-price"} (templates.add-to-basket-form product basket "" redirect-url)] [:div {:data-parser "description"} product.short-description]])) (fn content [db products page total filters basket agreed-to-cookies? authenticated?] (local redirect-url (filters-path page filters)) [(HTML [:aside {} (templates.header "/shop" authenticated?) (if (< 0 (# basket)) (templates.basket basket redirect-url) "") (templates.address-block) (templates.conditions-block) (templates.contact-block)]) (HTML [:div {:class "content"} (if (not agreed-to-cookies?) (templates.cookies-agreement) "") [:div {:class "back"} [:a {:href "/"} "⟵ Обратно на главную"]] [:h2 {:class "product-page-title"} "Магазин"] (if authenticated? (HTML [:div {:class "mb-1" :style "margin-top: -0.5rem"} [:a {:style "white-space: nowrap" :href (.. "/shop/add")} "+ Добавить"] [:a {:style "white-space: nowrap; margin-left: 1rem;" :href (.. "/shop/order/list")} "☰ Список заказов"]]) "") [:form {:class "d-flex-desktop gap-0-5 mb-1"} [:input {:name "search" :type "search" :placeholder "Поиск" :value (or filters.search "")}] [:select {:name "type"} [:option {:value ""} "Все товары"] (table.concat (icollect [_ v (ipairs dicts.product-type)] (HTML [:option (fn [] {:value v.value :selected (= filters.type v.value)}) v.label])))] [:button {:type "submit"} "Применить"]] (paginator-template filters page pagination-limit total "mb-1") [:div {:class "shop-items"} (if (< 0 (# products)) (table.concat (icollect [_ v (ipairs products)] (item-template v basket redirect-url))) (HTML [:em {} "Пока что здесь ничего нет!"]))] (paginator-template filters page pagination-limit total "mt-2")])]) (fn render [request db authenticated?] (let [agreed-to-cookies? request.cookies.agreed-to-cookies order-id request.cookies.order basket (if order-id (shop.basket db order-id) []) type (if (and request.query.type (not (lib.empty? (. request.query.type 1)))) (. request.query.type 1) nil) search (if (and request.query.search (not (lib.empty? (. request.query.search 1)))) (. request.query.search 1) nil) page (if (and request.query.page (not (lib.empty? (. request.query.page 1)))) (math.max 1 (tonumber (. request.query.page 1))) 1) only-published? (not authenticated?) filters {: type : search : page : only-published?} {: total : products} (all-products db page filters)] (values 200 {} (templates.base (content db products page total filters basket agreed-to-cookies? authenticated?) "Магазин чая" "Купить вкусный китайский чай с доставкой по Омску")))) {: render}