diff --git a/resources/sass/site2.scss b/resources/sass/site2.scss index b1904115f..2e3d80a24 100644 --- a/resources/sass/site2.scss +++ b/resources/sass/site2.scss @@ -516,6 +516,10 @@ form.vertical, .fields-vertical { margin-bottom: 0; } } + .step-info { + line-height: 1.9rem; + margin: 1.2rem 0 3rem; + } .mdc-select { margin-top: 16px; @@ -526,6 +530,14 @@ form.vertical, .fields-vertical { color: rgba(0, 0, 0, 0.6); margin: 25px 0 0; } + + button { + display: inline-flex; + align-items: center; + .material-icons { + margin-right: 10px; + } + } } .project-settings-actions { @@ -536,6 +548,10 @@ form.vertical, .fields-vertical { button { margin-left: 25px; + &:first-child { + float: left; + margin: 0; + } } } @@ -577,12 +593,56 @@ form.vertical, .fields-vertical { margin-right: 15px; } -.project-setting{ - width: 554px; +.project-setting { + max-width: 554px; +} +.project-setting-title { + p { + display: inline-flex; + align-items: flex-start; + .material-icons { + margin-right: 10px; + } + } } -.action-input{ - width: 110px; +.action-input { + max-width: 100px; +} +.percentage-input { + display: inline-flex; + align-items: end; + div { + margin-right: 0.8rem; + > input { + max-width: 50px; + } + } +} + +.prefix { + color: #999999; + font-style: normal; + position: absolute; + padding-bottom: 8px; + + + input { + padding-left: 15px; + } + + .suffix { + + input { + padding-left: 1rem; + } + } +} +.suffix { + @extend .prefix; + left: auto; + right: 0; + + input { + padding-right: 1rem; + padding-left: 0; + } } //empty projects @@ -770,6 +830,72 @@ form.vertical, .fields-vertical { animation: spin 2s linear infinite; } +.wizard { + .steps { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + border-bottom: 1px solid rgba(0,0,0,0.1); + margin-left: -25px; + margin-right: -25px; + padding: 5px 20px 0; + height: 56px; + + > a { + text-decoration: none; + text-transform: capitalize; + display: flex; + align-items: center; + i { + background-color: #999999; + color: #ffffff; + font-style: normal; + padding: 5px 11px; + margin-right: 10px; + border-radius: 50%; + } + div { + color: #999999; + display: inline-block; + &:after { + content: ""; + position: absolute; + height: 12px; + min-width: 85px; + margin-left: 10px; + border-bottom: 1px solid rgba(0,0,0,0.1); + } + } + &:last-child div:after { + border: none; + } + } + + .complete { + i { + background-color: $mdc-theme-primary; + } + div { + color: #000000; + } + } + .active { + @extend .complete; + font-weight: 500; + } + .material-icons { + font-size: 19px; + display: inline; + padding: 6px; + } + } + .map { + min-height: 500px; + height: 100%; + } +} + @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } diff --git a/src/planwise/client/components/common2.cljs b/src/planwise/client/components/common2.cljs index f69e0ffcc..64b5a1194 100644 --- a/src/planwise/client/components/common2.cljs +++ b/src/planwise/client/components/common2.cljs @@ -35,15 +35,18 @@ (defn mdc-input-field [props component-props] - (let [{:keys [id focus focus-extra-class label]} component-props] + + (let [{:keys [id focus focus-extra-class label]} component-props + {:keys [prefix suffix]} props] [:div.mdc-text-field.mdc-text-field--upgraded {:class (cond (:read-only props) focus-extra-class @focus (str "mdc-text-field--focused" focus-extra-class))} - [:input.mdc-text-field__input (merge props {:id id - :on-focus #(reset! focus true) - :on-blur #(reset! focus false)} - (when @focus - {:placeholder nil}))] + (when-not (empty? prefix) [:i.prefix prefix]) + (when-not (empty? suffix) [:i.suffix suffix]) + [:input.mdc-text-field__input (apply dissoc (merge props {:id id + :on-focus #(reset! focus true) + :on-blur #(reset! focus false)}) + [:prefix :suffix])] [:label.mdc-floating-label {:for id :class (when (or (not (blank? (str (:value props)))) @focus) "mdc-floating-label--float-above")} @@ -60,6 +63,7 @@ (fn [props] (let [component-props (assoc (select-keys props extra-keys) :id id + :type "text" :focus focus) props (apply dissoc props extra-keys)] [mdc-input-field props component-props])))) @@ -85,6 +89,7 @@ (select-keys props extra-keys) {:id (str (random-uuid)) :focus focus + :type "number" :focus-extra-class (when wrong-input " invalid-input")}) on-change-fn (:on-change props) global-value (str (:value props)) diff --git a/src/planwise/client/projects2/components/settings.cljs b/src/planwise/client/projects2/components/settings.cljs index 20e7de9b7..c46c875c4 100644 --- a/src/planwise/client/projects2/components/settings.cljs +++ b/src/planwise/client/projects2/components/settings.cljs @@ -2,7 +2,7 @@ (:require [reagent.core :as r] [re-frame.core :refer [subscribe dispatch] :as rf] [re-com.core :as rc] - [clojure.string :refer [blank?]] + [clojure.string :refer [blank? join]] [planwise.client.asdf :as asdf] [planwise.client.dialog :refer [dialog]] [planwise.client.components.common2 :as common2] @@ -16,7 +16,13 @@ [planwise.client.ui.filter-select :as filter-select] [planwise.client.ui.rmwc :as m] [planwise.client.utils :as utils] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [planwise.model.project-consumers] + [planwise.model.project-actions] + [planwise.model.project-coverage] + [planwise.model.project-providers] + [planwise.model.project-review] + [planwise.model.project-goal])) ;;------------------------------------------------------------------------ ;;Current Project updating @@ -31,15 +37,20 @@ attrs)] (into [filter-select/single-dropdown] (mapcat identity props)))) +;; TODO: Refactor this fn to require flat map instead of this large amount of params (defn- current-project-input ([label path type] - (current-project-input label path type {:disabled false})) + (current-project-input label path type "" "" {:disabled false})) ([label path type other-props] + (current-project-input label path type "" "" {:disabled false})) + ([label path type prefix suffix other-props] (let [current-project (rf/subscribe [:projects2/current-project]) value (or (get-in @current-project path) "") change-fn #(rf/dispatch-sync [:projects2/save-key path %]) props (merge (select-keys other-props [:class :disabled :sub-type]) - {:label label + {:prefix prefix + :suffix suffix + :label label :on-change (comp change-fn (fn [e] (-> e .-target .-value))) :value value})] (case type @@ -55,12 +66,31 @@ :on-click (utils/prevent-default #(dispatch [:projects2/start-project (:id project)]))} (if (= (keyword (:state project)) :started) "Started ..." "Start")]) + +(defn- project-next-step-button + [project next-step] + [m/Button {:id "start-project" + :type "button" + :unelevated "unelevated" + :disabled (if (nil? next-step) (not (s/valid? :planwise.model.project/starting project)) false) + :on-click (utils/prevent-default #(if (nil? next-step) + (dispatch [:projects2/start-project (:id project)]) + (dispatch [:projects2/navigate-to-step-project (:id project) next-step])))} + "Continue"]) + (defn- project-delete-button [state] [m/Button {:type "button" :theme ["text-secondary-on-secondary-light"] :on-click #(reset! state true)} "Delete"]) +(defn- project-back-button + [] + ; TODO - add on-click function and don't show it in first step + [m/Button {:type "button" + :theme ["text-secondary-on-secondary-light"]} + "Back"]) + (defn- tag-chip [props index input read-only] [m/Chip props [m/ChipText input] @@ -96,6 +126,9 @@ [:div {:class-name "step-header"} [:h2 [:span title]]]) +(defn- project-setting-title + [icon title] + [:div.project-setting-title [:p [m/Icon icon] title]]) ;------------------------------------------------------------------------------------------- ; Actions @@ -109,9 +142,9 @@ props) [m/Icon "clear"]] (when (= action-name :build) "with a capacity of ") - [current-project-input "" [:config :actions action-name idx :capacity] "number" (merge {:class "action-input"} props)] - "would cost" - [current-project-input "" [:config :actions action-name idx :investment] "number" (merge {:class "action-input"} props)]]) + [current-project-input "" [:config :actions action-name idx :capacity] "number" "" "" (merge {:class "action-input"} props)] + " would cost " + [current-project-input "" [:config :actions action-name idx :investment] "number" "$" "" (merge {:class "action-input"} props)]]) (defn- listing-actions [{:keys [read-only? action-name list]}] @@ -125,92 +158,147 @@ ;------------------------------------------------------------------------------------------- +(defn- current-project-step-goal + [read-only] + (let [current-project (subscribe [:projects2/current-project])] + [:section.project-settings-section + [section-header 1 "Goal"] + [current-project-input "Goal" [:name] "text"] + [m/TextFieldHelperText {:persistent true} "Enter the goal for this project"] + + [regions-dropdown-component {:label "Region" + :on-change #(dispatch [:projects2/save-key :region-id %]) + :model (:region-id @current-project) + :disabled? read-only}]])) +(defn- current-project-step-consumers + [read-only] + (let [current-project (subscribe [:projects2/current-project])] + [:section {:class-name "project-settings-section"} + [section-header 2 "Consumers"] + [sources-dropdown-component {:label "Consumer Dataset" + :value (:source-set-id @current-project) + :on-change #(dispatch [:projects2/save-key :source-set-id %]) + :disabled? read-only}] + [current-project-input "Consumers Unit" [:config :demographics :unit-name] "text" {:disabled read-only}] + [m/TextFieldHelperText {:persistent true} (str "How do you refer to the filtered population? (Eg: women)")] + [:div.percentage-input + [current-project-input "Target" [:config :demographics :target] "number" "" "%" {:disabled read-only :sub-type :percentage}] + [:p (str "of " (or (not-empty (get-in @current-project [:config :demographics :unit-name])) "population") " should be considered")]]])) + +(defn- current-project-step-providers + [read-only] + (let [current-project (subscribe [:projects2/current-project]) + tags (subscribe [:projects2/tags])] + [:section {:class-name "project-settings-section"} + [section-header 3 "Providers"] + [providers-set-dropdown-component {:label "Provider Dataset" + :value (:provider-set-id @current-project) + :on-change #(dispatch [:projects2/save-key :provider-set-id %]) + :disabled? read-only}] + + [current-project-input "Capacity Workload" [:config :providers :capacity] "number" {:disabled read-only :sub-type :float}] + [m/TextFieldHelperText {:persistent true} (str "How many " (or (not-empty (get-in @current-project [:config :demographics :unit-name])) "consumers") " can each provider handle?")] + (when-not read-only [tag-input]) + [:label "Tags: " [tag-set @tags read-only]] + [count-providers @tags @current-project]])) + +(defn- current-project-step-coverage + [read-only] + (let [current-project (subscribe [:projects2/current-project])] + [:section {:class-name "project-settings-section"} + [section-header 4 "Coverage"] + [:div {:class "step-info"} "These values will be used to estimate the geographic coverage that your current sites are providing. That in turn will allow Planwise to calculate areas out of reach."] + [coverage-algorithm-filter-options {:coverage-algorithm (:coverage-algorithm @current-project) + :value (get-in @current-project [:config :coverage :filter-options]) + :on-change #(dispatch [:projects2/save-key [:config :coverage :filter-options] %]) + :empty [:div {:class-name " no-provider-set-selected"} "First choose provider-set."] + :disabled? read-only}]])) + +(defn- current-project-step-actions + [read-only build-actions upgrade-actions] + (let [current-project (subscribe [:projects2/current-project]) + build-actions (subscribe [:projects2/build-actions]) + upgrade-actions (subscribe [:projects2/upgrade-actions])] + [:section {:class-name "project-settings-section"} + [section-header 5 "Actions"] + [:div {:class "step-info"} "Potential actions to increase access to services. Planwise will use these to explore and recommend the best alternatives."] + [project-setting-title "account_balance" "Available budget"] + [current-project-input "" [:config :actions :budget] "number" "$" "" {:disabled read-only :class "project-setting"}] + [m/TextFieldHelperText {:persistent true} "Planwise will keep explored scenarios below this maximum budget"] + + [project-setting-title "domain" "Building a new provider..."] + [listing-actions {:read-only? read-only + :action-name :build + :list @build-actions}] + + [project-setting-title "arrow_upward" "Upgrading a provider so that it can satisfy demand would cost..."] + [current-project-input "" [:config :actions :upgrade-budget] "number" "$" "" {:disabled read-only :class "project-setting"}] + + [project-setting-title "add" "Increase the capactiy of a provider by..."] + [listing-actions {:read-only? read-only + :action-name :upgrade + :list @upgrade-actions}]])) + +(defn- current-project-step-review + [read-only] + (let [current-project (subscribe [:projects2/current-project])] + [:section {:class "project-settings-section"} + [section-header 6 "Review"] + [:div {:class "step-info"} "After this step the system will search for different improvements scenarios based on the given parameters. Once started, the process will continue even if you leave the site. From the dashboard you will be able to see the scenarios found so far, pause the search and review the performed work."] + [project-setting-title "location_on" "Kenya health facilities - ResMap 8902"] + [project-setting-title "account_balance" "K 25,000,000"] + [project-setting-title "people" "Kenya census 2005"] + [project-setting-title "directions" "120 min walking distance, 40 min driving"] + [project-setting-title "info" "A hospital with a capacity of 100 beds will provide service for 1000 pregnancies per year"]])) + (defn current-project-settings-view - [{:keys [read-only]}] + [{:keys [read-only step sections]}] (let [current-project (subscribe [:projects2/current-project]) build-actions (subscribe [:projects2/build-actions]) upgrade-actions (subscribe [:projects2/upgrade-actions]) tags (subscribe [:projects2/tags])] - (fn [{:keys [read-only]}] - [m/Grid {} - [m/GridCell {:span 6} - [:form.vertical - [:section {:class-name "project-settings-section"} - [section-header 1 "Goal"] - [current-project-input "Goal" [:name] "text"] - [m/TextFieldHelperText {:persistent true} "Enter the goal for this project"] - - [regions-dropdown-component {:label "Region" - :on-change #(dispatch [:projects2/save-key :region-id %]) - :model (:region-id @current-project) - :disabled? read-only}]] - - [:section {:class-name "project-settings-section"} - [section-header 2 "Demand"] - [sources-dropdown-component {:label "Sources" - :value (:source-set-id @current-project) - :on-change #(dispatch [:projects2/save-key :source-set-id %]) - :disabled? read-only}] - - [current-project-input "Unit" [:config :demographics :unit-name] "text" {:disabled read-only}] - [current-project-input "Target" [:config :demographics :target] "number" {:disabled read-only :sub-type :percentage}] - [m/TextFieldHelperText {:persistent true} (str "Percentage of population that should be considered " (get-in @current-project [:config :demographics :unit-name]))]] - - [:section {:class-name "project-settings-section"} - [section-header 3 "Providers"] - [providers-set-dropdown-component {:label "Provider Set" - :value (:provider-set-id @current-project) - :on-change #(dispatch [:projects2/save-key :provider-set-id %]) - :disabled? read-only}] - - [current-project-input "Capacity workload" [:config :providers :capacity] "number" {:disabled read-only :sub-type :float}] - [m/TextFieldHelperText {:persistent true} (str "How many " (get-in @current-project [:config :demographics :unit-name]) " can be handled per provider capacity")] - - (when-not read-only [tag-input]) - [:label "Tags: " [tag-set @tags read-only]] - [count-providers @tags @current-project]] - - [:section {:class-name "project-settings-section"} - [section-header 4 "Coverage"] - [coverage-algorithm-filter-options {:coverage-algorithm (:coverage-algorithm @current-project) - :value (get-in @current-project [:config :coverage :filter-options]) - :on-change #(dispatch [:projects2/save-key [:config :coverage :filter-options] %]) - :empty [:div {:class-name " no-provider-set-selected"} "First choose provider-set."] - :disabled? read-only}]] - - [:section {:class-name "project-settings-section"} - [:div [:p [m/Icon "account_balance"] "Available budget"]] - [current-project-input "" [:config :actions :budget] "number" {:disabled read-only :class "project-setting"}] - [m/TextFieldHelperText {:persistent true} "Planwise will keep explored scenarios below this maximum budget"] - - [:div [:p [m/Icon "domain"] "Building a new provider..."]] - [listing-actions {:read-only? read-only - :action-name :build - :list @build-actions}] - - [:div [:p [m/Icon "arrow_upward"] "Upgrading a provider so that it can satisfy demand would cost..."]] - [current-project-input "" [:config :actions :upgrade-budget] "number" {:disabled read-only :class "project-setting"}] - - [:div [:p [m/Icon "add"] "Increase the capactiy of a provider by..."]] - [listing-actions {:read-only? read-only - :action-name :upgrade - :list @upgrade-actions}]]]]]))) + (fn [{:keys [read-only step]}] + (let [project @current-project + step-data (first (filter #(= (:step %) step) sections))] + + [m/Grid {:class-name "wizard"} + [m/GridCell {:span 12 :class-name "steps"} + (map-indexed (fn [i iteration-step] + [:a {:key i + :class-name (join " " [(if (= (:step iteration-step) step) "active") (if (s/valid? (keyword (str "planwise.model.project-" (:step iteration-step)) "validation") project) "complete")]) + :href (routes/projects2-show-with-step {:id (:id project) :step (:step iteration-step)})} + (if (s/valid? (:spec iteration-step) project) [m/Icon "done"] [:i (inc i)]) + [:div (:title iteration-step)]]) sections)] + [m/GridCell {:span 6} + [:form.vertical + (if (nil? step-data) + (dispatch [:projects2/infer-step @current-project]) + ((:component step-data) read-only))]] + [m/GridCell {:span 6} + [:div.map]]])))) (defn edit-current-project [] - (let [current-project (subscribe [:projects2/current-project]) + (let [page-params (subscribe [:page-params]) + current-project (subscribe [:projects2/current-project]) delete? (r/atom false) - hide-dialog (fn [] (reset! delete? false))] + hide-dialog (fn [] (reset! delete? false)) + sections [{:step "goal" :title "Goal" :component current-project-step-goal :spec :planwise.model.project/goal-step :next-step "consumers"} + {:step "consumers" :title "Consumers" :component current-project-step-consumers :spec :planwise.model.project/consumers-step :next-step "providers"} + {:step "providers" :title "Providers" :component current-project-step-providers :spec :planwise.model.project/providers-step :next-step "coverage"} + {:step "coverage" :title "Coverage" :component current-project-step-coverage :spec :planwise.model.project/coverage-step :next-step "actions"} + {:step "actions" :title "Actions" :component current-project-step-actions :spec :planwise.model.project/actions-step :next-step "review"} + {:step "review" :title "Review" :component current-project-step-review :spec :planwise.model.project/review-step :next-step nil}]] (fn [] [ui/fixed-width (common2/nav-params) [ui/panel {} - - [current-project-settings-view {:read-only false}] + [current-project-settings-view {:read-only false :step (:step @page-params) :sections sections}] [:div {:class-name "project-settings-actions"} + [project-back-button] [project-delete-button delete?] - [project-start-button {} @current-project]]] + [project-next-step-button @current-project (:next-step (first (filter #(= (:step %) (:step @page-params)) sections)))]]] [delete-project-dialog {:open? @delete? :cancel-fn hide-dialog :delete-fn #(dispatch [:projects2/delete-project (:id @current-project)])}]]))) diff --git a/src/planwise/client/projects2/handlers.cljs b/src/planwise/client/projects2/handlers.cljs index a2212ac1c..5de2de992 100644 --- a/src/planwise/client/projects2/handlers.cljs +++ b/src/planwise/client/projects2/handlers.cljs @@ -5,7 +5,15 @@ [planwise.client.routes :as routes] [planwise.client.effects :as effects] [planwise.client.projects2.db :as db] - [planwise.client.utils :as utils])) + [planwise.client.utils :as utils] + [clojure.spec.alpha :as s] + [planwise.model.project-consumers] + [planwise.model.project-actions] + [planwise.model.project-coverage] + [planwise.model.project-providers] + [planwise.model.project-review] + [planwise.model.project-goal])) + (def in-projects2 (rf/path [:projects2])) @@ -31,6 +39,22 @@ (assoc :list new-list)) :navigate (routes/projects2-show {:id project-id})}))) +(rf/reg-event-fx + :projects2/infer-step + in-projects2 + (fn [{:keys [db]} [_ project]] + (let [steps ["goal", "consumers", "providers", "coverage", "actions", "review"] + first-invalid-step (first (filter #(not (s/valid? (keyword (str "planwise.model.project-" %) "validation") project)) steps)) + selected-step (if (not-empty first-invalid-step) first-invalid-step "review")] + {:navigate (routes/projects2-show-with-step {:id (:id project) :step selected-step})}))) + +(rf/reg-event-fx + :projects2/navigate-to-step-project + in-projects2 + (fn [{:keys [db]} [_ project-id step]] + {:navigate (routes/projects2-show-with-step {:id project-id :step step})})) + + ;;------------------------------------------------------------------------------ ;; Updating db diff --git a/src/planwise/client/projects2/views.cljs b/src/planwise/client/projects2/views.cljs index 1712e0297..4cbb0a2e7 100644 --- a/src/planwise/client/projects2/views.cljs +++ b/src/planwise/client/projects2/views.cljs @@ -20,7 +20,7 @@ (not= (:id @current-project) id) (do (dispatch [:projects2/get-project id]) [common2/loading-placeholder]) - (= "draft" (:state @current-project)) [settings/edit-current-project] + (= "draft" (:state @current-project)) [settings/edit-current-project @page-params] :else [dashboard/view-current-project section]))))) ;;------------------------------------------------------------------------ diff --git a/src/planwise/client/routes.cljs b/src/planwise/client/routes.cljs index 44d439f36..4708dc64c 100644 --- a/src/planwise/client/routes.cljs +++ b/src/planwise/client/routes.cljs @@ -16,6 +16,8 @@ (dispatch [:navigate {:page :projects2, :section :index}])) (defroute projects2-show "/projects2/:id" [id] (dispatch [:navigate {:page :projects2, :id (js/parseInt id), :section :show}])) +(defroute projects2-show-with-step "/projects2/:id/steps/:step" [id step] + (dispatch [:navigate {:page :projects2, :id (js/parseInt id), :step step, :section :show}])) (defroute design "/_design" [] (dispatch [:navigate {:page :design}])) (defroute design-section "/_design/:section" [section query-params] @@ -27,4 +29,4 @@ (dispatch [:navigate {:page :projects2, :id (js/parseInt id), :section :project-scenarios}])) (defroute projects2-settings "/projects2/:id/settings" [id] (dispatch [:navigate {:page :projects2, :id (js/parseInt id), :section :project-settings}])) -(defroute download-sources-sample "/sources-sample.csv" []) \ No newline at end of file +(defroute download-sources-sample "/sources-sample.csv" []) diff --git a/src/planwise/endpoint/home.clj b/src/planwise/endpoint/home.clj index 20dab7520..74598876e 100644 --- a/src/planwise/endpoint/home.clj +++ b/src/planwise/endpoint/home.clj @@ -222,6 +222,7 @@ (GET "/sources" [] loading-page2) (context "/projects2" [] (GET "/" [] loading-page2) + (GET "/:id/steps/:step" [] loading-page2) (GET "/:id" [] loading-page2) (GET "/:id/scenarios" [] loading-page2) (GET "/:id/settings" [] loading-page2) diff --git a/src/planwise/model/project.cljc b/src/planwise/model/project.cljc index 92349936c..8a1cd5e9b 100644 --- a/src/planwise/model/project.cljc +++ b/src/planwise/model/project.cljc @@ -7,13 +7,13 @@ ;; Goal (s/def ::id number?) -(s/def ::name string?) +(s/def ::name (s/and string? (comp not blank?))) (s/def ::region-id number?) ;; Demographics (s/def ::source-set-id number?) (s/def ::target number?) -(s/def ::unit-name (comp not blank?)) +(s/def ::unit-name string?) (s/def ::demographics (s/keys :req-un [::unit-name ::target])) ;; Providers @@ -42,6 +42,17 @@ ;; Config (s/def ::config (s/keys :req-un [::demographics ::actions ::coverage ::providers])) +(s/def :planwise.model.project-consumers/config (s/keys :req-un [:planwise.model.project/demographics])) +(s/def :planwise.model.project-providers/config (s/keys :req-un [:planwise.model.project/providers])) +(s/def :planwise.model.project-coverage/config (s/keys :req-un [:planwise.model.project/coverage])) +(s/def :planwise.model.project-actions/config (s/keys :req-un [:planwise.model.project/actions])) ;; Project Starting (s/def ::starting (s/keys :req-un [::id ::owner-id ::name ::config ::provider-set-id ::source-set-id ::region-id])) + +(s/def ::goal-step (s/keys :req-un [:planwise.model.project/name :planwise.model.project/region-id])) +(s/def ::consumers-step (s/keys :req-un [:planwise.model.project/source-set-id :planwise.model.project-consumers/config])) +(s/def ::providers-step (s/keys :req-un [::provider-set-id :planwise.model.project-providers/config])) +(s/def ::coverage-step (s/keys :req-un [:planwise.model.project-coverage/config])) +(s/def ::actions-step (s/keys :req-un [:planwise.model.project-actions/config])) +(s/def ::review-step (s/keys :req-un [::id ::owner-id ::name ::config ::provider-set-id ::source-set-id ::region-id])) diff --git a/src/planwise/model/project_actions.cljc b/src/planwise/model/project_actions.cljc new file mode 100644 index 000000000..a354412f9 --- /dev/null +++ b/src/planwise/model/project_actions.cljc @@ -0,0 +1,9 @@ +(ns planwise.model.project-actions + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + +(s/def ::budget number?) +(s/def ::actions (s/keys :req-un [::budget])) +(s/def ::config (s/keys :req-un [::actions])) + +(s/def ::validation (s/keys :req-un [::config])) diff --git a/src/planwise/model/project_consumers.cljc b/src/planwise/model/project_consumers.cljc new file mode 100644 index 000000000..63f2c95d8 --- /dev/null +++ b/src/planwise/model/project_consumers.cljc @@ -0,0 +1,11 @@ +(ns planwise.model.project-consumers + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + +(s/def ::target number?) +(s/def ::unit-name (s/and string? (comp not blank?))) +(s/def ::demographics (s/keys :req-un [::unit-name ::target])) +(s/def ::source-set-id number?) + +(s/def ::config (s/keys :req-un [::demographics])) +(s/def ::validation (s/keys :req-un [::source-set-id ::config])) diff --git a/src/planwise/model/project_coverage.cljc b/src/planwise/model/project_coverage.cljc new file mode 100644 index 000000000..cd97ef582 --- /dev/null +++ b/src/planwise/model/project_coverage.cljc @@ -0,0 +1,20 @@ +(ns planwise.model.project-coverage + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + + +(s/def ::driving-time number?) +(s/def ::walking-time number?) +(s/def ::distance number?) + +(s/def ::driving-options (s/keys :req-un [::driving-time])) +(s/def ::walking-options (s/keys :req-un [::walking-time])) +(s/def ::distance-options (s/keys :req-un [::distance])) + +(s/def ::filter-options (s/or :driving-options ::driving-options + :walking-options ::walking-options + :distance-options ::distance-options)) +(s/def ::coverage (s/keys :req-un [::filter-options])) + +(s/def ::config (s/keys :req-un [::coverage])) +(s/def ::validation (s/keys :req-un [::config])) diff --git a/src/planwise/model/project_goal.cljc b/src/planwise/model/project_goal.cljc new file mode 100644 index 000000000..a11fa5a61 --- /dev/null +++ b/src/planwise/model/project_goal.cljc @@ -0,0 +1,7 @@ +(ns planwise.model.project-goal + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + +(s/def ::name (s/and string? (comp not blank?))) +(s/def ::region-id number?) +(s/def ::validation (s/keys :req-un [::name ::region-id])) diff --git a/src/planwise/model/project_providers.cljc b/src/planwise/model/project_providers.cljc new file mode 100644 index 000000000..6ccfc9287 --- /dev/null +++ b/src/planwise/model/project_providers.cljc @@ -0,0 +1,10 @@ +(ns planwise.model.project-providers + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + + +(s/def ::provider-set-id number?) +(s/def ::capacity number?) +(s/def ::providers (s/keys :req-un [::capacity])) +(s/def ::config (s/keys :req-un [::providers])) +(s/def ::validation (s/keys :req-un [::provider-set-id])) diff --git a/src/planwise/model/project_review.cljc b/src/planwise/model/project_review.cljc new file mode 100644 index 000000000..ae077965a --- /dev/null +++ b/src/planwise/model/project_review.cljc @@ -0,0 +1,42 @@ +(ns planwise.model.project-review + (:require [clojure.spec.alpha :as s] + [clojure.string :refer [blank?]])) + + +(s/def ::id number?) +(s/def ::name (s/and string? (comp not blank?))) +(s/def ::region-id number?) + +;; Demographics +(s/def ::source-set-id number?) +(s/def ::target number?) +(s/def ::unit-name string?) +(s/def ::demographics (s/keys :req-un [::unit-name ::target])) + +(s/def ::provider-set-id number?) +(s/def ::capacity number?) +(s/def ::providers (s/keys :req-un [::capacity])) + +;; Coverage +(s/def ::driving-time number?) +(s/def ::walking-time number?) +(s/def ::distance number?) + +(s/def ::driving-options (s/keys :req-un [::driving-time])) +(s/def ::walking-options (s/keys :req-un [::walking-time])) +(s/def ::distance-options (s/keys :req-un [::distance])) + + +(s/def ::filter-options (s/or :driving-options ::driving-options + :walking-options ::walking-options + :distance-options ::distance-options)) +(s/def ::coverage (s/keys :req-un [::filter-options])) + +;; Actions +(s/def ::budget number?) +(s/def ::actions (s/keys :req-un [::budget])) + +;; Config +(s/def ::config (s/keys :req-un [::demographics ::actions ::coverage ::providers])) + +(s/def ::validation (s/keys :req-un [::id ::owner-id ::name ::config ::provider-set-id ::source-set-id ::region-id]))