(ns bbbg.handlers.events (:require [bbbg.db :as db] [bbbg.db.attendee :as db.attendee] [bbbg.db.event :as db.event] [bbbg.event :as event] [bbbg.handlers.core :refer [*authenticated?* page-response]] [bbbg.meetup.import :refer [import-attendees!]] [bbbg.util.display :refer [format-date pluralize]] [bbbg.util.time :as t] [bbbg.views.flash :as flash] [compojure.coercions :refer [as-uuid]] [compojure.core :refer [context GET POST]] [java-time :refer [local-date]] [ring.util.response :refer [not-found redirect]] [bbbg.attendee :as attendee] [bbbg.event-attendee :as event-attendee] [bbbg.db.attendee-check :as db.attendee-check] [bbbg.attendee-check :as attendee-check] [bbbg.user :as user]) (:import java.time.format.FormatStyle)) (defn- num-attendees [event] (str (:num-attendees event) (if (= (t/->LocalDate (::event/date event)) (local-date)) " Signed In" (str " Attendee" (when-not (= 1 (:num-attendees event)) "s"))))) (def index-type->label {:upcoming "Upcoming" :past "Past"}) (def other-index-type {:upcoming :past :past :upcoming}) (defn events-index [{:keys [events num-events type]}] [:div.page [:div.page-header [:h1 (pluralize num-events (str (index-type->label type) " Event"))] [:a {:href (str "/events" (when (= :upcoming type) "/past"))} "View " (index-type->label (other-index-type type)) " Events"]] (when *authenticated?* [:a.button {:href "/events/new"} "Create New Event"]) [:ul.events-list (for [event (sort-by ::event/date (comp - compare) events)] [:li [:p [:a {:href (str "/events/" (::event/id event))} (format-date (::event/date event) FormatStyle/FULL)]] [:p (pluralize (:num-rsvps event) "RSVP") ", " (num-attendees event)]])]]) (defn- import-attendee-list-form-group [] [:div.form-group [:label "Import Attendee List" [:br] [:input {:type :file :name :attendees}]]]) (defn import-attendees-form [event] [:form {:method :post :action (str "/events/" (::event/id event) "/attendees") :enctype "multipart/form-data"} (import-attendee-list-form-group) [:div.form-group [:input {:type :submit :value "Import"}]]]) (defn event-page [{:keys [event attendees]}] [:div.page [:div.page-header [:h1 (format-date (::event/date event) FormatStyle/FULL)] [:div.spacer] [:a.button {:href (str "/signup-forms/" (::event/id event) )} "Go to Signup Form"] [:form#delete-event {:method :post :action (str "/events/" (::event/id event) "/delete") :data-confirm "Are you sure you want to delete this event?"} [:input.error {:type "submit" :value "Delete Event"}]]] [:div.stats [:p (pluralize (:num-rsvps event) "RSVP")] [:p (num-attendees event)]] [:div (import-attendees-form event)] [:div [:table.attendees [:thead [:th "Meetup Name"] [:th "Discord Name"] [:th "RSVP"] [:th "Signed In"] [:th "Last Vaccination Check"]] [:tbody (for [attendee (sort-by (juxt (comp not ::event-attendee/rsvpd-attending?) (comp not ::event-attendee/attended?) (comp some? :last-check) ::attendee/meetup-name) attendees)] [:tr [:td.attendee-name (::attendee/meetup-name attendee)] [:td [:label.mobile-label "Discord Name: "] (or (not-empty (::attendee/discord-name attendee)) "—")] [:td [:label.mobile-label "RSVP: "] (if (::event-attendee/rsvpd-attending? attendee) [:span {:title "Yes"} "✔️"] [:span {:title "No"} "❌"])] [:td [:label.mobile-label "Signed In: "] (if (::event-attendee/attended? attendee) [:span {:title "Yes"} "✔️"] [:span {:title "No"} "❌"])] [:td [:label.mobile-label "Last Vaccination Check: "] (if-let [last-check (:last-check attendee)] (str "✔️ "(-> last-check ::attendee-check/checked-at format-date) ", by " (get-in last-check [:user ::user/username])) (list [:span {:title "Not Checked"} "❌"] " " [:a {:href (str "/attendees/" (::attendee/id attendee) "/checks/edit")} "Edit"]))]])]]]]) (defn import-attendees-page [{:keys [event]}] [:div.page [:h1 "Import Attendees for " (format-date (::event/date event))] (import-attendees-form event)]) (defn event-form ([] (event-form {})) ([event] [:div.page [:div.page-header [:h1 "Create New Event"]] [:form {:method "POST" :action "/events" :enctype "multipart/form-data"} [:div.form-group [:label "Date" [:input {:type "date" :id "date" :name "date" :value (str (::event/date event))}]]] (import-attendee-list-form-group) [:div.form-group [:input {:type "submit" :value "Create Event"}]]]])) (defn- events-list-handler [db query type] (let [events (db/list db (db.event/with-stats query)) num-events (db/count db query)] (page-response (events-index {:events events :num-events num-events :type type})))) (defn events-routes [{:keys [db]}] (context "/events" [] (GET "/" [] (events-list-handler db (db.event/upcoming) :upcoming)) (GET "/past" [] (events-list-handler db (db.event/past) :past)) (GET "/new" [date] (page-response {:title "New Event"} (event-form {::event/date date}))) (POST "/" [date attendees] (let [event (db.event/create! db {::event/date date}) message (if attendees (let [num-attendees (import-attendees! db (::event/id event) (:tempfile attendees))] (format "Event created with %d attendees" num-attendees)) "Event created")] (-> (str "/signup-forms/" (::event/id event)) redirect (flash/add-flash {:flash/type :success :flash/message message})))) (context "/:id" [id :<< as-uuid] (GET "/" [] (if-let [event (db/fetch db (-> {:select [:event.*] :from [:event] :where [:= :event.id id]} (db.event/with-stats)))] (let [attendees (db.attendee-check/attendees-with-last-checks db (db/list db (db.attendee/for-event id)))] (page-response (event-page {:event event :attendees attendees}))) (not-found "Event Not Found"))) (POST "/delete" [] (db/delete! db :event_attendee [:= :event-id id]) (db/delete! db :event [:= :id id]) (-> (redirect "/events") (flash/add-flash #:flash {:type :success :message "Successfully deleted event"}))) (GET "/attendees/import" [] (if-let [event (db/get db :event id)] (page-response (import-attendees-page {:event event})) (not-found "Event Not Found"))) (POST "/attendees" [attendees] (let [num-imported (import-attendees! db id (:tempfile attendees))] (-> (redirect (str "/events/" id)) (flash/add-flash #:flash{:type :success :message (format "Successfully imported %d attendees" num-imported)}))))))) (comment (def db (:db bbbg.core/system)) (-> (db/list db :event) first ::event/date format-date) )