diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/src/Catalog.elm | 132 | ||||
-rw-r--r-- | frontend/src/Utils.elm | 15 |
2 files changed, 139 insertions, 8 deletions
diff --git a/frontend/src/Catalog.elm b/frontend/src/Catalog.elm index 80e5e38..6882c73 100644 --- a/frontend/src/Catalog.elm +++ b/frontend/src/Catalog.elm | |||
@@ -8,8 +8,15 @@ import Html.Events exposing (..) | |||
8 | import Http | 8 | import Http |
9 | import Json.Decode as D | 9 | import Json.Decode as D |
10 | import Json.Encode as Encode | 10 | import Json.Encode as Encode |
11 | import Tuple exposing (..) | ||
11 | import Url | 12 | import Url |
12 | import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string) | 13 | import Url.Parser as P exposing ((</>), Parser, int, oneOf, s, string) |
14 | import Utils exposing (..) | ||
15 | |||
16 | |||
17 | type Order | ||
18 | = Rating | ||
19 | | Price | ||
13 | 20 | ||
14 | 21 | ||
15 | type alias Product = | 22 | type alias Product = |
@@ -18,12 +25,25 @@ type alias Product = | |||
18 | , kind : Maybe String | 25 | , kind : Maybe String |
19 | , price : Float | 26 | , price : Float |
20 | , description : Maybe String | 27 | , description : Maybe String |
28 | , averageRating : Maybe Float | ||
21 | } | 29 | } |
22 | 30 | ||
23 | 31 | ||
32 | type alias Filters = | ||
33 | { price : ( Float, Float ) | ||
34 | , rating : ( Float, Float ) | ||
35 | } | ||
36 | |||
37 | |||
38 | defaultFilters : Filters | ||
39 | defaultFilters = | ||
40 | Filters ( -1, 10000 ) ( 0, 5 ) | ||
41 | |||
42 | |||
24 | type alias Model = | 43 | type alias Model = |
25 | { pageStatus : Status | 44 | { pageStatus : Status |
26 | , products : List Product | 45 | , products : List Product |
46 | , filters : Filters | ||
27 | } | 47 | } |
28 | 48 | ||
29 | 49 | ||
@@ -36,11 +56,15 @@ type Status | |||
36 | type Msg | 56 | type Msg |
37 | = ProductsLoaded (Result Http.Error (List Product)) | 57 | = ProductsLoaded (Result Http.Error (List Product)) |
38 | | FetchProducts | 58 | | FetchProducts |
59 | | ChangePriceLower Float | ||
60 | | ChangePriceUpper Float | ||
61 | | ChangeRatingLower Float | ||
62 | | ChangeRatingUpper Float | ||
39 | 63 | ||
40 | 64 | ||
41 | init : Model | 65 | init : Model |
42 | init = | 66 | init = |
43 | Model NotLoaded [] | 67 | Model NotLoaded [] defaultFilters |
44 | 68 | ||
45 | 69 | ||
46 | update : Msg -> Model -> ( Model, Cmd Msg ) | 70 | update : Msg -> Model -> ( Model, Cmd Msg ) |
@@ -61,15 +85,56 @@ update msg model = | |||
61 | FetchProducts -> | 85 | FetchProducts -> |
62 | ( { model | pageStatus = Loading }, fetchProducts ) | 86 | ( { model | pageStatus = Loading }, fetchProducts ) |
63 | 87 | ||
88 | ChangePriceLower v -> | ||
89 | let | ||
90 | fs = | ||
91 | model.filters | ||
92 | |||
93 | nfs = | ||
94 | { fs | price = mapFirst (always v) fs.price } | ||
95 | in | ||
96 | ( { model | filters = nfs }, Cmd.none ) | ||
97 | |||
98 | ChangePriceUpper v -> | ||
99 | let | ||
100 | fs = | ||
101 | model.filters | ||
102 | |||
103 | nfs = | ||
104 | { fs | price = mapSecond (always v) fs.price } | ||
105 | in | ||
106 | ( { model | filters = nfs }, Cmd.none ) | ||
107 | |||
108 | ChangeRatingLower v -> | ||
109 | let | ||
110 | fs = | ||
111 | model.filters | ||
112 | |||
113 | nfs = | ||
114 | { fs | rating = mapFirst (always v) fs.rating } | ||
115 | in | ||
116 | ( { model | filters = nfs }, Cmd.none ) | ||
117 | |||
118 | ChangeRatingUpper v -> | ||
119 | let | ||
120 | fs = | ||
121 | model.filters | ||
122 | |||
123 | nfs = | ||
124 | { fs | rating = mapSecond (always v) fs.rating } | ||
125 | in | ||
126 | ( { model | filters = nfs }, Cmd.none ) | ||
127 | |||
64 | 128 | ||
65 | decodeProduct : D.Decoder Product | 129 | decodeProduct : D.Decoder Product |
66 | decodeProduct = | 130 | decodeProduct = |
67 | D.map5 Product | 131 | D.map6 Product |
68 | (D.field "id" D.int) | 132 | (D.field "id" D.int) |
69 | (D.field "name" D.string) | 133 | (D.field "name" D.string) |
70 | (D.field "kind" (D.nullable D.string)) | 134 | (D.field "kind" (D.nullable D.string)) |
71 | (D.field "price" D.float) | 135 | (D.field "price" D.float) |
72 | (D.field "description" (D.nullable D.string)) | 136 | (D.field "description" (D.nullable D.string)) |
137 | (D.field "average_rating" (D.nullable D.float)) | ||
73 | 138 | ||
74 | 139 | ||
75 | decodeResponse : D.Decoder (List Product) | 140 | decodeResponse : D.Decoder (List Product) |
@@ -105,14 +170,63 @@ viewStatus s = | |||
105 | viewProduct : Product -> Html Msg | 170 | viewProduct : Product -> Html Msg |
106 | viewProduct p = | 171 | viewProduct p = |
107 | div [] | 172 | div [] |
108 | [ text p.name | 173 | [ div [] [ text p.name ] |
109 | , text <| Maybe.withDefault "" p.kind | 174 | , div [] [ text <| Maybe.withDefault "" p.kind ] |
110 | , text <| Maybe.withDefault "" p.description | 175 | , div [] [ text <| Maybe.withDefault "" p.description ] |
111 | , text <| String.fromFloat p.price | 176 | , div [] [ text <| String.fromFloat p.price ] |
112 | , a [ href ("/product/" ++ String.fromInt p.id) ] [ text "View Product" ] | 177 | , case p.averageRating of |
178 | Just v -> | ||
179 | text <| "Avg Rating: " ++ String.fromFloat v | ||
180 | |||
181 | Nothing -> | ||
182 | text "No Ratings" | ||
183 | , div [] [ a [ href ("/product/" ++ String.fromInt p.id) ] [ text "View Product" ] ] | ||
113 | ] | 184 | ] |
114 | 185 | ||
115 | 186 | ||
187 | viewFilters : Model -> Html Msg | ||
188 | viewFilters model = | ||
189 | let | ||
190 | priceRange = | ||
191 | range 0 50000 5000 | ||
192 | |||
193 | ratingRange = | ||
194 | List.range 1 5 | ||
195 | |||
196 | viewRange = | ||
197 | List.map (\i -> option [] [ text <| String.fromInt i ]) | ||
198 | |||
199 | inp = | ||
200 | Maybe.withDefault 0 << String.toFloat | ||
201 | in | ||
202 | div [] | ||
203 | [ div [] | ||
204 | [ text "Price" | ||
205 | , select [ onInput (ChangePriceLower << inp) ] (viewRange priceRange) | ||
206 | , text "to" | ||
207 | , select [ onInput (ChangePriceUpper << inp) ] (viewRange priceRange) | ||
208 | ] | ||
209 | , div [] | ||
210 | [ text "Rating" | ||
211 | , select [ onInput (ChangeRatingLower << inp) ] (viewRange ratingRange) | ||
212 | , text "to" | ||
213 | , select [ onInput (ChangeRatingUpper << inp) ] (viewRange ratingRange) | ||
214 | ] | ||
215 | ] | ||
216 | |||
217 | |||
218 | filterProducts : Model -> List Product | ||
219 | filterProducts model = | ||
220 | model.products | ||
221 | |> List.filter (between model.filters.price << .price) | ||
222 | |> List.filter | ||
223 | (\p -> | ||
224 | p.averageRating | ||
225 | |> Maybe.withDefault 5.0 | ||
226 | |> between model.filters.rating | ||
227 | ) | ||
228 | |||
229 | |||
116 | view : Model -> Html Msg | 230 | view : Model -> Html Msg |
117 | view model = | 231 | view model = |
118 | case model.pageStatus of | 232 | case model.pageStatus of |
@@ -121,5 +235,7 @@ view model = | |||
121 | 235 | ||
122 | _ -> | 236 | _ -> |
123 | div [] | 237 | div [] |
124 | [ ul [] (List.map viewProduct model.products) | 238 | [ div [] [ viewFilters model ] |
239 | , ul [] | ||
240 | (filterProducts model |> List.map viewProduct) | ||
125 | ] | 241 | ] |
diff --git a/frontend/src/Utils.elm b/frontend/src/Utils.elm new file mode 100644 index 0000000..825e4b7 --- /dev/null +++ b/frontend/src/Utils.elm | |||
@@ -0,0 +1,15 @@ | |||
1 | module Utils exposing (..) | ||
2 | |||
3 | |||
4 | between : ( Float, Float ) -> Float -> Bool | ||
5 | between ( l, u ) v = | ||
6 | v >= l && v <= u | ||
7 | |||
8 | |||
9 | range : Int -> Int -> Int -> List Int | ||
10 | range start stop step = | ||
11 | if start >= stop then | ||
12 | [] | ||
13 | |||
14 | else | ||
15 | start :: range (start + step) stop step | ||