From 07c5537c114cb175c27b94200f23f573209fb88f Mon Sep 17 00:00:00 2001 From: Prashant Pathak Date: Mon, 5 Feb 2018 09:45:51 +0000 Subject: [PATCH] Combine Results module into Search --- src/client/App.fs | 28 ++------ src/client/client.fsproj | 3 +- src/client/pages/Results.fs | 85 ---------------------- src/client/pages/Search.fs | 137 ++++++++++++++++++++++++++++-------- 4 files changed, 116 insertions(+), 137 deletions(-) delete mode 100644 src/client/pages/Results.fs diff --git a/src/client/App.fs b/src/client/App.fs index 1a0f8d5..f8400b8 100644 --- a/src/client/App.fs +++ b/src/client/App.fs @@ -11,42 +11,26 @@ open Fable.Helpers.React.Props open Pages type Model = - { SearchModel : Search.Model - ResultsModel : Results.Model } + { SearchModel : Search.Model } type AppMsg = | SearchMsg of Search.Msg -| ResultsMsg of Results.Msg let update msg model = - let updateSearch msg model = + match msg with + | SearchMsg msg -> let searchModel, searchCmd = Search.update msg model.SearchModel { model with SearchModel = searchModel }, Cmd.map SearchMsg searchCmd - match msg with - | SearchMsg (Search.SearchCompleted(term, response) as msg) -> - let model, cmd = model |> updateSearch msg - { model with ResultsModel = Results.update (Results.DisplayResults(term, response)) model.ResultsModel }, cmd - | SearchMsg msg -> model |> updateSearch msg - | ResultsMsg (Results.FilterSet(facet, value)) -> model |> updateSearch (Search.ApplyFilter (facet, value)) - | ResultsMsg (Results.ChangePage page) -> model |> updateSearch (Search.ChangePage page) - | ResultsMsg (Results.SetPostcode postcode) -> model |> updateSearch (Search.DoSearch (Postcode postcode)) - | ResultsMsg (Results.SelectTransaction _ as msg) -> { model with ResultsModel = Results.update msg model.ResultsModel }, Cmd.none - | ResultsMsg (Results.DisplayResults _) -> model, Cmd.none let init _ = let model = - { SearchModel = Search.init () - ResultsModel = Results.init () } + { SearchModel = Search.init () } model, Cmd.none let view model dispatch = - let toTh c = th [ Scope "col" ] [ str c ] - let toTd c = td [ Scope "row" ] [ str c ] - div [ ClassName "container-fluid" ] [ - div [ ClassName "row" ] [ div [ ClassName "col" ] [ h1 [] [ str "Property Search" ] ] ] - div [ ClassName "row" ] [ Pages.Search.view model.SearchModel (SearchMsg >> dispatch) ] - div [ ClassName "row" ] [ Pages.Results.view model.ResultsModel (ResultsMsg >> dispatch) ] + div [ ClassName "row" ] [ div [ ClassName "col" ] [ h1 [] [ str "Property Search" ] ] ] + div [ ClassName "row" ] [ Search.view model.SearchModel (SearchMsg >> dispatch) ] ] JsInterop.importSideEffects "whatwg-fetch" diff --git a/src/client/client.fsproj b/src/client/client.fsproj index 43388dd..7383cd8 100644 --- a/src/client/client.fsproj +++ b/src/client/client.fsproj @@ -7,9 +7,8 @@ - - + diff --git a/src/client/pages/Results.fs b/src/client/pages/Results.fs deleted file mode 100644 index 2e30a7d..0000000 --- a/src/client/pages/Results.fs +++ /dev/null @@ -1,85 +0,0 @@ -module Pages.Results - -open PropertyMapper.Contracts -open Fable.Helpers.React -open Fable.Helpers.React.Props - -type SearchResults = { SearchTerm : SearchTerm; Response : SearchResponse } -type Model = { SearchResults : SearchResults option; Selected : PropertyResult option } - -type Msg = - | FilterSet of facet:string * value: string - | DisplayResults of SearchTerm * SearchResponse - | ChangePage of int - | SelectTransaction of PropertyResult - | SetPostcode of string -let init _ : Model = { SearchResults = None; Selected = None } - -let view model dispatch = - let toTh c = th [ Scope "col" ] [ str c ] - let toDetailsLink row c = - td [ Scope "row" ] [ - a [ Href "#" - DataToggle "modal" - unbox ("data-target", "#exampleModal") - OnClick(fun _ -> dispatch (SelectTransaction row)) - ] [ str c ] - ] - let toTd c = td [ Scope "row" ] [ str c ] - div [ ClassName "container-fluid border rounded m-3 p-3 bg-light" ] [ - yield model.Selected |> Option.map Details.view |> Option.defaultValue (div [] []) - match model.SearchResults with - | None -> yield div [ ClassName "row" ] [ div [ ClassName "col" ] [ h3 [] [ str "Please perform a search!" ] ] ] - | Some { Response = { Results = [||] } } -> yield div [ ClassName "row" ] [ div [ ClassName "col" ] [ h3 [] [ str "Your search yielded no results." ] ] ] - | Some { SearchTerm = term; Response = response } -> - let hits = response.TotalTransactions |> Option.map (commaSeparate >> sprintf " (%s hits)") |> Option.defaultValue "" - let description = - match term with - | Term term -> sprintf "Search results for '%s'%s." term hits - | Postcode postcode -> sprintf "Showing properties within a 1km radius of '%s'%s." postcode hits - - yield div [ ClassName "row" ] [ - div [ ClassName "col-2" ] [ Pages.Filter.createFilters (FilterSet >> dispatch) response.Facets ] - div [ ClassName "col-10" ] [ - div [ ClassName "row" ] [ div [ ClassName "col" ] [ h4 [] [ str description ] ] ] - table [ ClassName "table table-bordered table-hover" ] [ - thead [] [ - tr [] [ toTh "Street" - toTh "Town" - toTh "Postcode" - toTh "Date" - toTh "Price" ] - ] - tbody [] [ - for row in response.Results -> - tr [] [ toDetailsLink row row.Address.FirstLine - toTd row.Address.TownCity - td [ Scope "row" ] [ a [ Href "#"; OnClick(fun _ -> row.Address.PostCode |> Option.iter(SetPostcode >> dispatch)) ] [ row.Address.PostCode |> Option.defaultValue "" |> str ] ] - toTd (row.DateOfTransfer.ToShortDateString()) - toTd (sprintf "£%s" (commaSeparate row.Price)) ] - ] - ] - nav [] [ - ul [ ClassName "pagination" ] [ - let buildPager enabled content current page = - li [ ClassName ("page-item" + (if enabled then "" else " disabled") + (if current then " active" else "")) ] [ - button [ ClassName "page-link"; Style [ Cursor "pointer" ]; OnClick (fun _ -> dispatch (ChangePage page)) ] [ str content ] - ] - let currentPage = response.Page - let totalPages = int ((response.TotalTransactions |> Option.defaultValue 0 |> float) / 20.) - yield buildPager (currentPage > 0) "Previous" false (currentPage - 1) - yield! - [ for page in 0 .. totalPages -> - buildPager true (string (page + 1)) (page = currentPage) page ] - yield buildPager (currentPage < totalPages) "Next" false (currentPage + 1) - ] - ] - ] - ] - ] - -let update msg model : Model = - match msg with - | FilterSet _ | ChangePage _ | SetPostcode _ -> model - | DisplayResults (term, response) -> { SearchResults = Some { SearchTerm = term; Response = response }; Selected = None } - | SelectTransaction transaction -> { model with Selected = Some transaction } \ No newline at end of file diff --git a/src/client/pages/Search.fs b/src/client/pages/Search.fs index ce7b61d..403f618 100644 --- a/src/client/pages/Search.fs +++ b/src/client/pages/Search.fs @@ -10,57 +10,134 @@ open PropertyMapper.Contracts type SearchState = Searching | Displaying type SearchParameters = { Facet : (string * string) option; Page : int } +type SearchResults = { SearchTerm : SearchTerm; Response : SearchResponse } type Model = { Text : SearchTerm LastSearch : SearchTerm Status : SearchState - Parameters : SearchParameters } + Parameters : SearchParameters + SearchResults : SearchResults option + Selected : PropertyResult option } type Msg = | SetSearch of string | DoSearch of SearchTerm - | ApplyFilter of string * string + | SetFilter of string * string | ChangePage of int | SearchCompleted of SearchTerm * SearchResponse | SearchError of exn + | SelectTransaction of PropertyResult -let init _ = { Text = SearchTerm.Empty; LastSearch = SearchTerm.Empty; Status = SearchState.Displaying; Parameters = { Facet = None; Page = 0 } } +let init _ = + { Text = SearchTerm.Empty + LastSearch = SearchTerm.Empty + Status = SearchState.Displaying + Parameters = { Facet = None; Page = 0 } + SearchResults = None + Selected = None } -let view model dispatch = - div [ ClassName "col border rounded m-3 p-3 bg-light" ] [ - let progressBarVisibility = match model.Status with | Searching -> "visible" | Displaying -> "invisible" - yield div [ ClassName "form-group" ] [ - label [ HtmlFor "searchValue" ] [ str "Search for" ] - input [ - ClassName "form-control" - Id "searchValue" - Placeholder "Enter Search" - OnChange (fun ev -> dispatch (SetSearch !!ev.target?value)) - Client.Style.onEnter (DoSearch model.Text) dispatch - ] +let viewResults searchResults dispatch = + let toTh c = th [ Scope "col" ] [ str c ] + let toDetailsLink row c = + td [ Scope "row" ] [ + a [ Href "#" + DataToggle "modal" + unbox ("data-target", "#exampleModal") + OnClick(fun _ -> dispatch (SelectTransaction row)) ] [ str c ] ] - yield div [ ClassName "form-group" ] [ - div [ ClassName "progress" ] [ - div [ ClassName (sprintf "progress-bar progress-bar-striped progress-bar-animated %s" progressBarVisibility) - Role "progressbar" - Style [ Width "100%" ] ] - [ str <| sprintf "Searching for '%s'..." model.LastSearch.Description ] + let toTd c = td [ Scope "row" ] [ str c ] + div [ ClassName "border rounded m-3 p-3 bg-light" ] [ + match searchResults with + | None -> yield div [ ClassName "row" ] [ div [ ClassName "col" ] [ h3 [] [ str "Please perform a search!" ] ] ] + | Some { Response = { Results = [||] } } -> yield div [ ClassName "row" ] [ div [ ClassName "col" ] [ h3 [] [ str "Your search yielded no results." ] ] ] + | Some { SearchTerm = term; Response = response } -> + let hits = response.TotalTransactions |> Option.map (commaSeparate >> sprintf " (%s hits)") |> Option.defaultValue "" + let description = + match term with + | Term term -> sprintf "Search results for '%s'%s." term hits + | Postcode postcode -> sprintf "Showing properties within a 1km radius of '%s'%s." postcode hits + + yield div [ ClassName "row" ] [ + div [ ClassName "col-2" ] [ Filter.createFilters (SetFilter >> dispatch) response.Facets ] + div [ ClassName "col-10" ] [ + div [ ClassName "row" ] [ div [ ClassName "col" ] [ h4 [] [ str description ] ] ] + table [ ClassName "table table-bordered table-hover" ] [ + thead [] [ + tr [] [ toTh "Street" + toTh "Town" + toTh "Postcode" + toTh "Date" + toTh "Price" ] + ] + tbody [] [ + for row in response.Results -> + let postcodeLink = + a + [ Href "#"; OnClick(fun _ -> row.Address.PostCode |> Option.iter(Postcode >> DoSearch >> dispatch)) ] + [ row.Address.PostCode |> Option.defaultValue "" |> str ] + tr [] [ toDetailsLink row row.Address.FirstLine + toTd row.Address.TownCity + td [ Scope "row" ] [ postcodeLink ] + toTd (row.DateOfTransfer.ToShortDateString()) + toTd (sprintf "£%s" (commaSeparate row.Price)) ] + ] + ] + nav [] [ + ul [ ClassName "pagination" ] [ + let buildPager enabled content current page = + li [ ClassName ("page-item" + (if enabled then "" else " disabled") + (if current then " active" else "")) ] [ + button [ ClassName "page-link"; Style [ Cursor "pointer" ]; OnClick (fun _ -> dispatch (ChangePage page)) ] [ str content ] + ] + let currentPage = response.Page + let totalPages = int ((response.TotalTransactions |> Option.defaultValue 0 |> float) / 20.) + yield buildPager (currentPage > 0) "Previous" false (currentPage - 1) + yield! + [ for page in 0 .. totalPages -> + buildPager true (string (page + 1)) (page = currentPage) page ] + yield buildPager (currentPage < totalPages) "Next" false (currentPage + 1) + ] + ] + ] ] - ] - yield button [ ClassName "btn btn-primary"; OnClick (fun _ -> dispatch (DoSearch model.Text)) ] [ str "Search!" ] ] +let view model dispatch = + let progressBarVisibility = match model.Status with | Searching -> "visible" | Displaying -> "invisible" + div [ ClassName "col" ] [ + yield! model.Selected |> function Some property -> [ Details.view property ] | None -> [] + yield div [ ClassName "border rounded m-3 p-3 bg-light" ] [ + div [ ClassName "form-group" ] [ + label [ HtmlFor "searchValue" ] [ str "Search for" ] + input [ + ClassName "form-control" + Id "searchValue" + Placeholder "Enter Search" + OnChange (fun ev -> dispatch (SetSearch !!ev.target?value)) + Client.Style.onEnter (DoSearch model.Text) dispatch + ] + ] + div [ ClassName "form-group" ] [ + div [ ClassName "progress" ] [ + div [ ClassName (sprintf "progress-bar progress-bar-striped progress-bar-animated %s" progressBarVisibility) + Role "progressbar" + Style [ Width "100%" ] ] + [ str <| sprintf "Searching for '%s'..." model.LastSearch.Description ] + ] + ] + button [ ClassName "btn btn-primary"; OnClick (fun _ -> dispatch (DoSearch model.Text)) ] [ str "Search!" ] ] + yield viewResults model.SearchResults dispatch ] + let findTransactions (text, filter, page) = let filter = filter |> Option.map(fun (facet, value) -> sprintf "?%s=%s" facet value) |> Option.defaultValue "" Fetch.fetchAs (sprintf "http://localhost:5000/property/find/%s/%d%s" text page filter) [] -let findByPostcode (postCode, page) = +let findByPostcode (postCode, page) = Fetch.fetchAs (sprintf "http://localhost:5000/property/%s/1/%d" postCode page) [] let update msg model : Model * Cmd = let initiateSearch model term facet page = - let cmd = + let cmd = match term with | Term text -> Cmd.ofPromise findTransactions (text, facet, page) | Postcode postcode -> Cmd.ofPromise findByPostcode (postcode, page) @@ -73,8 +150,12 @@ let update msg model : Model * Cmd = | SetSearch text -> { model with Text = Term text }, Cmd.none | DoSearch (Term text) when System.String.IsNullOrWhiteSpace text || text.Length <= 3 -> model, Cmd.none | DoSearch term -> initiateSearch model term None 0 - | ApplyFilter (facet, value) -> initiateSearch model model.LastSearch (Some(facet, value)) model.Parameters.Page + | SetFilter (facet, value) -> initiateSearch model model.LastSearch (Some(facet, value)) model.Parameters.Page | ChangePage page -> initiateSearch model model.LastSearch model.Parameters.Facet page - | SearchCompleted _ -> { model with Status = Displaying }, Cmd.none + | SearchCompleted (term, response) -> + { model with + Status = Displaying + SearchResults = Some { SearchTerm = term; Response = response } + Selected = None }, Cmd.none | SearchError _ -> model, Cmd.none - + | SelectTransaction transaction -> { model with Selected = Some transaction }, Cmd.none \ No newline at end of file