diff --git a/client/src/assets/scss/_custom.scss b/client/src/assets/scss/_custom.scss index 079a287654..4fa330f5c4 100644 --- a/client/src/assets/scss/_custom.scss +++ b/client/src/assets/scss/_custom.scss @@ -852,6 +852,46 @@ h3.ui.header { width: 100%; } + .ui.form.search .form-input.yes-no { + padding-bottom: 15px; + margin-left: 25px; + margin-top: 15px; + + label { + font-size: 25px; + margin-left: 10px; + } + + label:before { + content: " "; + display: inline-block; + position: relative; + top: 5px; + margin-right: 10px; + width: 26px; + height: 26px; + border-radius: 13px; + border: 2px solid $color-gray-border; + background-color: transparent; + } + + input[type=radio] { + display: none; + } + + input[type=radio]:checked + label:after { + content: " "; + border-radius: 7px; + width: 14px; + height: 14px; + position: absolute; + top: 25px; + left: 30px; + display: block; + background: $color-text; + } + } + .results-summary { margin-top: 15px; margin-bottom: 15px; diff --git a/client/src/assets/scss/_new.scss b/client/src/assets/scss/_new.scss index 3912677f45..9141083688 100644 --- a/client/src/assets/scss/_new.scss +++ b/client/src/assets/scss/_new.scss @@ -712,6 +712,7 @@ div.page-people { display: flex; flex-direction: column; height: 100%; + overflow-x: hidden; overflow-y: hidden; div.search-middle { @@ -3553,6 +3554,7 @@ div.page-participants { display: flex; flex-direction: column; height: 100%; + overflow-x: hidden; overflow-y: hidden; div.search-middle { diff --git a/client/src/elm/App/Fetch.elm b/client/src/elm/App/Fetch.elm index f62dc7b744..5c36a70c64 100644 --- a/client/src/elm/App/Fetch.elm +++ b/client/src/elm/App/Fetch.elm @@ -173,7 +173,7 @@ fetch model = getLoggedInData model |> Maybe.map (\( _, loggedIn ) -> - Pages.People.Fetch.fetch relation initiator loggedIn.personsPage + Pages.People.Fetch.fetch relation loggedIn.personsPage |> List.map MsgIndexedDb ) |> Maybe.withDefault [] diff --git a/client/src/elm/App/Model.elm b/client/src/elm/App/Model.elm index aaecd3fd84..b19a74a187 100644 --- a/client/src/elm/App/Model.elm +++ b/client/src/elm/App/Model.elm @@ -30,6 +30,7 @@ import Backend.TuberculosisActivity.Model exposing (TuberculosisActivity) import Backend.WellChildActivity.Model exposing (WellChildActivity) import Browser import Browser.Navigation as Nav +import Components.PatientsSearchForm.Model import Config.Model import Device.Model exposing (Device) import Error.Model exposing (Error, ErrorType) @@ -330,7 +331,7 @@ emptyLoggedInModel site villageId nurse = , globalCaseManagementPage = Pages.GlobalCaseManagement.Model.emptyModel , editPersonPages = Dict.empty , personsPage = Pages.People.Model.emptyModel - , individualEncounterParticipantsPage = Pages.IndividualEncounterParticipants.Model.emptyModel + , individualEncounterParticipantsPage = Components.PatientsSearchForm.Model.emptyModel , clinicsPage = Pages.Clinics.Model.emptyModel , stockManagementPage = Pages.StockManagement.Model.emptyModel , relationshipPages = Dict.empty diff --git a/client/src/elm/Backend/Endpoints.elm b/client/src/elm/Backend/Endpoints.elm index 0ad4f21375..a33159a83c 100644 --- a/client/src/elm/Backend/Endpoints.elm +++ b/client/src/elm/Backend/Endpoints.elm @@ -102,6 +102,7 @@ personEndpoint = type PersonParams = ParamsNameContains String + | ParamsNationalIdContains String | ParamsGeoFields String @@ -111,6 +112,9 @@ encodePersonParams params = ParamsNameContains value -> [ ( "name_contains", value ) ] + ParamsNationalIdContains value -> + [ ( "national_id_contains", value ) ] + ParamsGeoFields value -> [ ( "geo_fields", value ) ] diff --git a/client/src/elm/Backend/Fetch.elm b/client/src/elm/Backend/Fetch.elm index bc2975ac7b..f8b1753aa4 100644 --- a/client/src/elm/Backend/Fetch.elm +++ b/client/src/elm/Backend/Fetch.elm @@ -137,7 +137,12 @@ shouldFetch currentTime model msg = isNotAsked model.participantForms FetchPeopleByName search -> - Dict.get (String.trim search) model.personSearches + Dict.get (String.trim search) model.personSearchesByName + |> Maybe.withDefault NotAsked + |> isNotAsked + + FetchPeopleByNationalId search -> + Dict.get (String.trim search) model.personSearchesByNationalId |> Maybe.withDefault NotAsked |> isNotAsked @@ -502,7 +507,10 @@ forget msg model = { model | participantForms = NotAsked } FetchPeopleByName search -> - { model | personSearches = Dict.remove (String.trim search) model.personSearches } + { model | personSearchesByName = Dict.remove (String.trim search) model.personSearchesByName } + + FetchPeopleByNationalId search -> + { model | personSearchesByNationalId = Dict.remove (String.trim search) model.personSearchesByNationalId } FetchPeopleInVillage id -> { model | peopleInVillage = Dict.remove id model.peopleInVillage } diff --git a/client/src/elm/Backend/Model.elm b/client/src/elm/Backend/Model.elm index bbc55dae62..e0d29e8ef4 100644 --- a/client/src/elm/Backend/Model.elm +++ b/client/src/elm/Backend/Model.elm @@ -119,7 +119,11 @@ type alias ModelIndexedDb = -- Tracks searchs for participants by name. The key is the phrase we are -- searching for. - , personSearches : Dict String (WebData (Dict PersonId Person)) + , personSearchesByName : Dict String (WebData (Dict PersonId Person)) + + -- Tracks searches for participants by name. The key is the phrase we are + -- searching for. + , personSearchesByNationalId : Dict String (WebData (Dict PersonId Person)) , peopleInVillage : Dict VillageId (WebData (Dict PersonId Person)) -- A simple cache of several things. @@ -247,7 +251,8 @@ emptyModelIndexedDb = , participantsByPerson = Dict.empty , people = Dict.empty , traceContacts = Dict.empty - , personSearches = Dict.empty + , personSearchesByName = Dict.empty + , personSearchesByNationalId = Dict.empty , peopleInVillage = Dict.empty , prenatalEncounterRequests = Dict.empty , nutritionEncounterRequests = Dict.empty @@ -373,6 +378,7 @@ type MsgIndexedDb | FetchParticipantsForPerson PersonId | FetchPeople (List PersonId) | FetchPeopleByName String + | FetchPeopleByNationalId String | FetchPeopleInVillage VillageId | FetchPerson PersonId | FetchPrenatalEncounter PrenatalEncounterId @@ -446,6 +452,7 @@ type MsgIndexedDb | HandleFetchedParticipantsForPerson PersonId (WebData (Dict PmtctParticipantId PmtctParticipant)) | HandleFetchedPeople (WebData (Dict PersonId Person)) | HandleFetchedPeopleByName String (WebData (Dict PersonId Person)) + | HandleFetchedPeopleByNationalId String (WebData (Dict PersonId Person)) | HandleFetchedPeopleInVillage VillageId (WebData (Dict PersonId Person)) | HandleFetchedPerson PersonId (WebData Person) | HandleFetchedPrenatalEncounter PrenatalEncounterId (WebData PrenatalEncounter) diff --git a/client/src/elm/Backend/Update.elm b/client/src/elm/Backend/Update.elm index b8027f4543..ca4a8afd22 100644 --- a/client/src/elm/Backend/Update.elm +++ b/client/src/elm/Backend/Update.elm @@ -1431,14 +1431,33 @@ updateIndexedDb language currentDate currentTime zscores site features nurseId h in -- We'll limit the search to 500 each for now ... basically, -- just to avoid truly pathological cases. - ( { model | personSearches = Dict.insert trimmed Loading model.personSearches } + ( { model | personSearchesByName = Dict.insert trimmed Loading model.personSearchesByName } , sw.selectRange personEndpoint (ParamsNameContains trimmed) 0 (Just 500) |> toCmd (RemoteData.fromResult >> RemoteData.map (.items >> Dict.fromList) >> HandleFetchedPeopleByName trimmed) , [] ) HandleFetchedPeopleByName trimmed data -> - ( { model | personSearches = Dict.insert trimmed data model.personSearches } + ( { model | personSearchesByName = Dict.insert trimmed data model.personSearchesByName } + , Cmd.none + , [] + ) + + FetchPeopleByNationalId nationalId -> + let + trimmed = + String.trim nationalId + in + -- We'll limit the search to 500 each for now ... basically, + -- just to avoid truly pathological cases. + ( { model | personSearchesByNationalId = Dict.insert trimmed Loading model.personSearchesByNationalId } + , sw.selectRange personEndpoint (ParamsNationalIdContains trimmed) 0 (Just 500) + |> toCmd (RemoteData.fromResult >> RemoteData.map (.items >> Dict.fromList) >> HandleFetchedPeopleByNationalId trimmed) + , [] + ) + + HandleFetchedPeopleByNationalId trimmed data -> + ( { model | personSearchesByNationalId = Dict.insert trimmed data model.personSearchesByNationalId } , Cmd.none , [] ) @@ -5886,7 +5905,8 @@ handleRevision currentDate healthCenterId villageId revision (( model, recalc ) Dict.update uuid (Maybe.map (always (Success data))) model.people in ( { model - | personSearches = Dict.empty + | personSearchesByName = Dict.empty + , personSearchesByNationalId = Dict.empty , people = people } , True diff --git a/client/src/elm/Components/PatientsSearchForm/Fetch.elm b/client/src/elm/Components/PatientsSearchForm/Fetch.elm new file mode 100644 index 0000000000..47650116e0 --- /dev/null +++ b/client/src/elm/Components/PatientsSearchForm/Fetch.elm @@ -0,0 +1,29 @@ +module Components.PatientsSearchForm.Fetch exposing (fetch) + +import Backend.Entities exposing (..) +import Backend.Model exposing (MsgIndexedDb(..)) +import Backend.Person.Model exposing (Initiator) +import Components.PatientsSearchForm.Model exposing (..) +import Maybe.Extra + + +fetch : Model -> List MsgIndexedDb +fetch model = + let + trimmed = + Maybe.withDefault "" model.search + in + if String.isEmpty trimmed then + [] + + else + let + fetchMsg = + case model.mode of + ModeSearchByName -> + FetchPeopleByName + + ModeSearchByNationalId -> + FetchPeopleByNationalId + in + [ fetchMsg trimmed ] diff --git a/client/src/elm/Components/PatientsSearchForm/Model.elm b/client/src/elm/Components/PatientsSearchForm/Model.elm new file mode 100644 index 0000000000..44632fd2ab --- /dev/null +++ b/client/src/elm/Components/PatientsSearchForm/Model.elm @@ -0,0 +1,38 @@ +module Components.PatientsSearchForm.Model exposing (..) + +import Debouncer.Basic as Debouncer exposing (Debouncer, debounce, toDebouncer) + + +{-| The `input` field represents what the user has typed as an input. + +The `search` field represents what we're searching for. (We delay +searches briefly while the user is typing). + +-} +type alias Model = + { debouncer : Debouncer Msg Msg + , mode : PatientsSearchFormMode + , search : Maybe String + , input : String + } + + +type PatientsSearchFormMode + = ModeSearchByName + | ModeSearchByNationalId + + +emptyModel : Model +emptyModel = + { debouncer = debounce 500 |> toDebouncer + , mode = ModeSearchByName + , search = Nothing + , input = "" + } + + +type Msg + = MsgDebouncer (Debouncer.Msg Msg) + | SetMode Bool + | SetInput String + | SetSearch String diff --git a/client/src/elm/Components/PatientsSearchForm/Update.elm b/client/src/elm/Components/PatientsSearchForm/Update.elm new file mode 100644 index 0000000000..9f984deca7 --- /dev/null +++ b/client/src/elm/Components/PatientsSearchForm/Update.elm @@ -0,0 +1,72 @@ +module Components.PatientsSearchForm.Update exposing (update) + +import Components.PatientsSearchForm.Model exposing (..) +import Debouncer.Basic as Debouncer exposing (provideInput) +import Maybe.Extra exposing (isJust) +import Update.Extra exposing (sequence) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + MsgDebouncer subMsg -> + let + ( subModel, subCmd, extraMsg ) = + Debouncer.update subMsg model.debouncer + in + ( { model | debouncer = subModel } + , Cmd.map MsgDebouncer subCmd + ) + |> sequence update (Maybe.Extra.toList extraMsg) + + SetMode byName -> + let + mode = + if byName then + ModeSearchByName + + else + ModeSearchByNationalId + in + ( { model | mode = mode, input = "", search = Nothing } + , Cmd.none + ) + + SetSearch search -> + let + trimmed = + String.trim search + + maybeSearch = + if String.isEmpty trimmed then + Nothing + + else + Just trimmed + in + ( { model | search = maybeSearch } + , Cmd.none + ) + + SetInput input -> + case model.mode of + ModeSearchByName -> + ( { model | input = input } + , Cmd.none + ) + |> sequence update [ MsgDebouncer <| provideInput <| SetSearch input ] + + ModeSearchByNationalId -> + let + asNumber = + String.toInt input + in + if isJust asNumber then + ( { model | input = input } + , Cmd.none + ) + |> sequence update + [ MsgDebouncer <| provideInput <| SetSearch input ] + + else + ( model, Cmd.none ) diff --git a/client/src/elm/Components/PatientsSearchForm/Utils.elm b/client/src/elm/Components/PatientsSearchForm/Utils.elm new file mode 100644 index 0000000000..1b3e9746b7 --- /dev/null +++ b/client/src/elm/Components/PatientsSearchForm/Utils.elm @@ -0,0 +1,32 @@ +module Components.PatientsSearchForm.Utils exposing (..) + +import AssocList as Dict exposing (Dict) +import Backend.Entities exposing (..) +import Backend.Model exposing (ModelIndexedDb) +import Backend.Person.Model exposing (Person) +import Components.PatientsSearchForm.Model exposing (..) +import RemoteData exposing (RemoteData(..), WebData) + + +getSearchResults : ModelIndexedDb -> Model -> WebData (Dict PersonId Person) +getSearchResults db model = + let + searchValue = + getSearchValue model + + resultsDictFunc = + case model.mode of + ModeSearchByName -> + .personSearchesByName + + ModeSearchByNationalId -> + .personSearchesByNationalId + in + resultsDictFunc db + |> Dict.get searchValue + |> Maybe.withDefault NotAsked + + +getSearchValue : Model -> String +getSearchValue = + .search >> Maybe.withDefault "" diff --git a/client/src/elm/Components/PatientsSearchForm/View.elm b/client/src/elm/Components/PatientsSearchForm/View.elm new file mode 100644 index 0000000000..cebbdcdc81 --- /dev/null +++ b/client/src/elm/Components/PatientsSearchForm/View.elm @@ -0,0 +1,30 @@ +module Components.PatientsSearchForm.View exposing (view) + +import Components.PatientsSearchForm.Model exposing (..) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (..) +import Pages.Utils exposing (viewBoolInput, viewLabel, viewTextInput) +import Translate exposing (Language, translate) + + +view : Language -> Model -> Html Msg +view language model = + let + placeholder = + case model.mode of + ModeSearchByName -> + Translate.PlaceholderEnterParticipantName + + ModeSearchByNationalId -> + Translate.PlaceholderEnterParticipantNationalId + in + div [ class "ui search form" ] + [ viewLabel language Translate.SearchBy + , viewBoolInput language + (model.mode == ModeSearchByName |> Just) + SetMode + "search-by" + (Just ( Translate.Name, Translate.NationalId )) + , viewTextInput language model.input SetInput (Just placeholder) (Just "search-input") + ] diff --git a/client/src/elm/Pages/AcuteIllness/Activity/Fetch.elm b/client/src/elm/Pages/AcuteIllness/Activity/Fetch.elm index f2fd4322a7..2748f7651a 100644 --- a/client/src/elm/Pages/AcuteIllness/Activity/Fetch.elm +++ b/client/src/elm/Pages/AcuteIllness/Activity/Fetch.elm @@ -5,6 +5,7 @@ import Backend.AcuteIllnessActivity.Model exposing (AcuteIllnessActivity(..)) import Backend.Entities exposing (..) import Backend.Measurement.Utils exposing (getMeasurementValueFunc) import Backend.Model exposing (ModelIndexedDb, MsgIndexedDb(..)) +import Components.PatientsSearchForm.Fetch import Pages.AcuteIllness.Activity.Model exposing (ContactsTracingFormState(..), Model) import Pages.AcuteIllness.Encounter.Fetch import RemoteData @@ -30,16 +31,7 @@ fetch id activity db model = fetchSearchMsg = case form.state of ContactsTracingFormSearchParticipants searchData -> - let - trimmed = - Maybe.withDefault "" searchData.search - |> String.trim - in - if String.isEmpty trimmed then - [] - - else - [ FetchPeopleByName trimmed ] + Components.PatientsSearchForm.Fetch.fetch searchData _ -> [] diff --git a/client/src/elm/Pages/AcuteIllness/Activity/Model.elm b/client/src/elm/Pages/AcuteIllness/Activity/Model.elm index fdc0b5630f..2793b7adcd 100644 --- a/client/src/elm/Pages/AcuteIllness/Activity/Model.elm +++ b/client/src/elm/Pages/AcuteIllness/Activity/Model.elm @@ -5,9 +5,9 @@ import Backend.AcuteIllnessEncounter.Types exposing (AcuteIllnessDiagnosis) import Backend.Entities exposing (..) import Backend.Measurement.Model exposing (..) import Backend.Person.Form +import Components.PatientsSearchForm.Model exposing (..) import Date exposing (Date) import DateSelector.Model exposing (DateSelectorConfig) -import Debouncer.Basic as Debouncer exposing (Debouncer, debounce, toDebouncer) import EverySet exposing (EverySet) import Form import Gizra.NominalDate exposing (NominalDate) @@ -98,9 +98,7 @@ type Msg | SetFollowUpOption FollowUpOption | SaveFollowUp PersonId (Maybe AcuteIllnessDiagnosis) (Maybe ( AcuteIllnessFollowUpId, AcuteIllnessFollowUp )) (Maybe Pages.AcuteIllness.Activity.Types.NextStepsTask) | SetContactsTracingFormState ContactsTracingFormState - | MsgContactsTracingDebouncer (Debouncer.Msg Msg) - | SetContactsTracingInput String - | SetContactsTracingSearch String + | MsgPatientsSearchForm Components.PatientsSearchForm.Model.Msg | SetContactsTracingDate Date | SetContactsTracingDateSelectorState (Maybe (DateSelectorConfig Msg)) | SetContactsTracingPhoneNumber String @@ -438,7 +436,7 @@ type alias ContactsTracingForm = type ContactsTracingFormState = ContactsTracingFormSummary - | ContactsTracingFormSearchParticipants SearchParticipantsData + | ContactsTracingFormSearchParticipants Components.PatientsSearchForm.Model.Model | ContactsTracingFormRecordContactDetails PersonId RecordContactDetailsData | ContactsTracingFormRegisterContact RegisterContactData @@ -451,21 +449,6 @@ emptyContactsTracingForm = } -type alias SearchParticipantsData = - { debouncer : Debouncer Msg Msg - , search : Maybe String - , input : String - } - - -emptySearchParticipantsData : SearchParticipantsData -emptySearchParticipantsData = - { debouncer = debounce 500 |> toDebouncer - , search = Nothing - , input = "" - } - - type alias RecordContactDetailsData = { contactDate : Maybe Date , dateSelectorPopupState : Maybe (DateSelectorConfig Msg) diff --git a/client/src/elm/Pages/AcuteIllness/Activity/Update.elm b/client/src/elm/Pages/AcuteIllness/Activity/Update.elm index 3d083fc7e0..c9ccb3d2b6 100644 --- a/client/src/elm/Pages/AcuteIllness/Activity/Update.elm +++ b/client/src/elm/Pages/AcuteIllness/Activity/Update.elm @@ -24,6 +24,7 @@ import Backend.Model exposing (ModelIndexedDb) import Backend.Person.Form import Backend.Person.Model import Backend.Village.Utils exposing (getVillageHealthCenterId, getVillageIdByGeoFields) +import Components.PatientsSearchForm.Update import Debouncer.Basic as Debouncer exposing (provideInput) import EverySet import Form @@ -1835,7 +1836,7 @@ update currentDate site selectedHealthCenter id db msg model = , fetchPersonMsg ) - MsgContactsTracingDebouncer subMsg -> + MsgPatientsSearchForm subMsg -> let form = model.nextStepsData.contactsTracingForm @@ -1843,38 +1844,8 @@ update currentDate site selectedHealthCenter id db msg model = case form.state of ContactsTracingFormSearchParticipants searchData -> let - ( subModel, subCmd, extraMsg ) = - Debouncer.update subMsg searchData.debouncer - - updatedSearchData = - { searchData | debouncer = subModel } - - updatedForm = - { form | state = ContactsTracingFormSearchParticipants updatedSearchData } - - updatedData = - model.nextStepsData - |> (\data -> { data | contactsTracingForm = updatedForm }) - in - ( { model | nextStepsData = updatedData } - , Cmd.map MsgContactsTracingDebouncer subCmd - , [] - ) - |> sequenceExtra (update currentDate site selectedHealthCenter id db) (Maybe.Extra.toList extraMsg) - - _ -> - noChange - - SetContactsTracingInput input -> - let - form = - model.nextStepsData.contactsTracingForm - in - case form.state of - ContactsTracingFormSearchParticipants searchData -> - let - updatedSearchData = - { searchData | input = input } + ( updatedSearchData, cmd ) = + Components.PatientsSearchForm.Update.update subMsg searchData updatedForm = { form | state = ContactsTracingFormSearchParticipants updatedSearchData } @@ -1884,44 +1855,7 @@ update currentDate site selectedHealthCenter id db msg model = |> (\data -> { data | contactsTracingForm = updatedForm }) in ( { model | nextStepsData = updatedData } - , Cmd.none - , [] - ) - |> sequenceExtra (update currentDate site selectedHealthCenter id db) [ MsgContactsTracingDebouncer <| provideInput <| SetContactsTracingSearch input ] - - _ -> - noChange - - SetContactsTracingSearch search -> - let - form = - model.nextStepsData.contactsTracingForm - in - case form.state of - ContactsTracingFormSearchParticipants searchData -> - let - trimmed = - String.trim search - - maybeSearch = - if String.isEmpty trimmed then - Nothing - - else - Just trimmed - - updatedSearchData = - { searchData | search = maybeSearch } - - updatedForm = - { form | state = ContactsTracingFormSearchParticipants updatedSearchData } - - updatedData = - model.nextStepsData - |> (\data -> { data | contactsTracingForm = updatedForm }) - in - ( { model | nextStepsData = updatedData } - , Cmd.none + , Cmd.map MsgPatientsSearchForm cmd , [] ) diff --git a/client/src/elm/Pages/AcuteIllness/Activity/View.elm b/client/src/elm/Pages/AcuteIllness/Activity/View.elm index 763ca6acc1..6c0aec0bff 100644 --- a/client/src/elm/Pages/AcuteIllness/Activity/View.elm +++ b/client/src/elm/Pages/AcuteIllness/Activity/View.elm @@ -21,6 +21,9 @@ import Backend.Model exposing (ModelIndexedDb) import Backend.Person.Form import Backend.Person.Model exposing (Person) import Backend.Person.Utils exposing (defaultIconForPerson, generateFullName, isPersonAFertileWoman) +import Components.PatientsSearchForm.Model +import Components.PatientsSearchForm.Utils exposing (..) +import Components.PatientsSearchForm.View import Date exposing (Unit(..)) import DateSelector.SelectorPopup exposing (viewCalendarPopup) import EverySet exposing (EverySet) @@ -2972,7 +2975,9 @@ viewContactsTracingFormSummary language currentDate db contactsTracingFinished c [ ( "ui primary button", True ) , ( "disabled", contactsTracingFinished ) ] - , onClick <| SetContactsTracingFormState <| ContactsTracingFormSearchParticipants emptySearchParticipantsData + , onClick <| + SetContactsTracingFormState <| + ContactsTracingFormSearchParticipants Components.PatientsSearchForm.Model.emptyModel ] [ text <| translate language Translate.AddContact ] @@ -3057,22 +3062,29 @@ viewTracedContact language currentDate db finished contact = ] -viewContactsTracingFormSearchParticipants : Language -> NominalDate -> Site -> ModelIndexedDb -> List PersonId -> SearchParticipantsData -> List (Html Msg) +viewContactsTracingFormSearchParticipants : + Language + -> NominalDate + -> Site + -> ModelIndexedDb + -> List PersonId + -> Components.PatientsSearchForm.Model.Model + -> List (Html Msg) viewContactsTracingFormSearchParticipants language currentDate site db existingContacts data = let searchForm = - Pages.Utils.viewSearchForm language data.input Translate.PlaceholderSearchContactName SetContactsTracingInput + Components.PatientsSearchForm.View.view language data + |> Html.map Pages.AcuteIllness.Activity.Model.MsgPatientsSearchForm searchValue = - Maybe.withDefault "" data.search + Components.PatientsSearchForm.Utils.getSearchValue data results = if String.isEmpty searchValue then Nothing else - Dict.get searchValue db.personSearches - |> Maybe.withDefault NotAsked + Components.PatientsSearchForm.Utils.getSearchResults db data |> RemoteData.map (Dict.filter (\personId _ -> @@ -3212,7 +3224,13 @@ viewContactsTracingFormRecordContactDetails language currentDate personId db dat in [ viewCustomLabel language Translate.ContactsTracingCompleteDetails ":" "instructions" , div [ class "ui items" ] <| - [ viewContactTracingParticipant language currentDate personId person True (ContactsTracingFormSearchParticipants emptySearchParticipantsData) ] + [ viewContactTracingParticipant language + currentDate + personId + person + True + (ContactsTracingFormSearchParticipants Components.PatientsSearchForm.Model.emptyModel) + ] , div [ class "contact-detail" ] [ viewLabel language Translate.DateOfContact , div @@ -3554,7 +3572,10 @@ viewCreateContactForm language currentDate site geoInfo db data = cancelButton = button [ class "ui button primary" - , onClick <| SetContactsTracingFormState <| ContactsTracingFormSearchParticipants emptySearchParticipantsData + , onClick <| + SetContactsTracingFormState <| + ContactsTracingFormSearchParticipants + Components.PatientsSearchForm.Model.emptyModel ] [ text <| translate language Translate.Cancel ] diff --git a/client/src/elm/Pages/IndividualEncounterParticipants/Fetch.elm b/client/src/elm/Pages/IndividualEncounterParticipants/Fetch.elm index 56243baafb..ff4ba776b4 100644 --- a/client/src/elm/Pages/IndividualEncounterParticipants/Fetch.elm +++ b/client/src/elm/Pages/IndividualEncounterParticipants/Fetch.elm @@ -2,20 +2,11 @@ module Pages.IndividualEncounterParticipants.Fetch exposing (fetch) import Backend.IndividualEncounterParticipant.Model exposing (IndividualEncounterType) import Backend.Model exposing (MsgIndexedDb(..)) +import Components.PatientsSearchForm.Fetch import Pages.IndividualEncounterParticipants.Model exposing (..) fetch : IndividualEncounterType -> Model -> List MsgIndexedDb fetch encounterType model = - let - trimmed = - Maybe.withDefault "" model.search - |> String.trim - in [ FetchHealthCenters, FetchVillages ] - ++ (if String.isEmpty trimmed then - [] - - else - [ FetchPeopleByName trimmed ] - ) + ++ Components.PatientsSearchForm.Fetch.fetch model diff --git a/client/src/elm/Pages/IndividualEncounterParticipants/Model.elm b/client/src/elm/Pages/IndividualEncounterParticipants/Model.elm index fcb4d3c63a..93980afc2b 100644 --- a/client/src/elm/Pages/IndividualEncounterParticipants/Model.elm +++ b/client/src/elm/Pages/IndividualEncounterParticipants/Model.elm @@ -1,6 +1,6 @@ -module Pages.IndividualEncounterParticipants.Model exposing (Model, Msg(..), emptyModel) +module Pages.IndividualEncounterParticipants.Model exposing (..) -import Debouncer.Basic as Debouncer exposing (Debouncer, debounce, toDebouncer) +import Components.PatientsSearchForm.Model import Pages.Page exposing (Page) @@ -11,22 +11,9 @@ searches briefly while the user is typing). -} type alias Model = - { debouncer : Debouncer Msg Msg - , search : Maybe String - , input : String - } - - -emptyModel : Model -emptyModel = - { debouncer = debounce 500 |> toDebouncer - , search = Nothing - , input = "" - } + Components.PatientsSearchForm.Model.Model type Msg - = MsgDebouncer (Debouncer.Msg Msg) - | SetInput String - | SetSearch String - | SetActivePage Page + = SetActivePage Page + | MsgPatientsSearchForm Components.PatientsSearchForm.Model.Msg diff --git a/client/src/elm/Pages/IndividualEncounterParticipants/Update.elm b/client/src/elm/Pages/IndividualEncounterParticipants/Update.elm index f0f256452d..5ab1cf3004 100644 --- a/client/src/elm/Pages/IndividualEncounterParticipants/Update.elm +++ b/client/src/elm/Pages/IndividualEncounterParticipants/Update.elm @@ -1,53 +1,22 @@ module Pages.IndividualEncounterParticipants.Update exposing (update) import App.Model -import Debouncer.Basic as Debouncer exposing (provideInput) -import Gizra.Update exposing (sequenceExtra) -import Maybe.Extra +import Components.PatientsSearchForm.Update import Pages.IndividualEncounterParticipants.Model exposing (..) update : Msg -> Model -> ( Model, Cmd Msg, List App.Model.Msg ) update msg model = case msg of - MsgDebouncer subMsg -> - let - ( subModel, subCmd, extraMsg ) = - Debouncer.update subMsg model.debouncer - in - ( { model | debouncer = subModel } - , Cmd.map MsgDebouncer subCmd - , [] - ) - |> sequenceExtra update (Maybe.Extra.toList extraMsg) - SetActivePage page -> ( model , Cmd.none , [ App.Model.SetActivePage page ] ) - SetSearch search -> + MsgPatientsSearchForm subMsg -> let - trimmed = - String.trim search - - maybeSearch = - if String.isEmpty trimmed then - Nothing - - else - Just trimmed + ( modelUpdated, cmd ) = + Components.PatientsSearchForm.Update.update subMsg model in - ( { model | search = maybeSearch } - , Cmd.none - , [] - ) - - SetInput input -> - ( { model | input = input } - , Cmd.none - , [] - ) - |> sequenceExtra update - [ MsgDebouncer <| provideInput <| SetSearch input ] + ( modelUpdated, Cmd.map MsgPatientsSearchForm cmd, [] ) diff --git a/client/src/elm/Pages/IndividualEncounterParticipants/View.elm b/client/src/elm/Pages/IndividualEncounterParticipants/View.elm index e4cfd64de6..81f835c5ee 100644 --- a/client/src/elm/Pages/IndividualEncounterParticipants/View.elm +++ b/client/src/elm/Pages/IndividualEncounterParticipants/View.elm @@ -7,6 +7,8 @@ import Backend.Model exposing (ModelIndexedDb) import Backend.Person.Model exposing (Initiator(..), Person) import Backend.Person.Utils exposing (defaultIconForPerson, isChildUnderAgeOf2, isNewborn, isPersonAFertileWoman, isPersonAnAdult) import Backend.Village.Utils exposing (personLivesInVillage) +import Components.PatientsSearchForm.Utils exposing (..) +import Components.PatientsSearchForm.View import Gizra.Html exposing (emptyNode, showMaybe) import Gizra.NominalDate exposing (NominalDate, diffYears) import Html exposing (..) @@ -75,11 +77,11 @@ viewSearchForm : Language -> NominalDate -> ( HealthCenterId, Maybe VillageId ) viewSearchForm language currentDate ( healthCenterId, maybeVillageId ) isChw encounterType model db = let searchForm = - Pages.Utils.viewSearchForm language model.input Translate.PlaceholderEnterParticipantName SetInput + Components.PatientsSearchForm.View.view language model + |> Html.map Pages.IndividualEncounterParticipants.Model.MsgPatientsSearchForm searchValue = - model.search - |> Maybe.withDefault "" + Components.PatientsSearchForm.Utils.getSearchValue model encounterCondition person = case encounterType of @@ -137,8 +139,7 @@ viewSearchForm language currentDate ( healthCenterId, maybeVillageId ) isChw enc Nothing else - Dict.get searchValue db.personSearches - |> Maybe.withDefault NotAsked + Components.PatientsSearchForm.Utils.getSearchResults db model |> RemoteData.map (Dict.filter (\_ filteredPerson -> diff --git a/client/src/elm/Pages/People/Fetch.elm b/client/src/elm/Pages/People/Fetch.elm index 9b2895ced5..abafeb9aeb 100644 --- a/client/src/elm/Pages/People/Fetch.elm +++ b/client/src/elm/Pages/People/Fetch.elm @@ -2,27 +2,18 @@ module Pages.People.Fetch exposing (fetch) import Backend.Entities exposing (..) import Backend.Model exposing (MsgIndexedDb(..)) -import Backend.Person.Model exposing (Initiator) +import Components.PatientsSearchForm.Fetch import Maybe.Extra import Pages.People.Model exposing (..) -fetch : Maybe PersonId -> Initiator -> Model -> List MsgIndexedDb -fetch relation initiator model = +fetch : Maybe PersonId -> Model -> List MsgIndexedDb +fetch relation model = let - trimmed = - Maybe.withDefault "" model.search - |> String.trim - - fetchPeople = - if String.isEmpty trimmed then - [] - - else - [ FetchPeopleByName trimmed ] - fetchRelation = Maybe.map FetchPerson relation |> Maybe.Extra.toList in - fetchPeople ++ fetchRelation ++ [ FetchHealthCenters, FetchVillages ] + Components.PatientsSearchForm.Fetch.fetch model + ++ fetchRelation + ++ [ FetchHealthCenters, FetchVillages ] diff --git a/client/src/elm/Pages/People/Model.elm b/client/src/elm/Pages/People/Model.elm index 9a6f32a18a..9aec8924a8 100644 --- a/client/src/elm/Pages/People/Model.elm +++ b/client/src/elm/Pages/People/Model.elm @@ -1,6 +1,6 @@ module Pages.People.Model exposing (Model, Msg(..), emptyModel) -import Debouncer.Basic as Debouncer exposing (Debouncer, debounce, toDebouncer) +import Components.PatientsSearchForm.Model exposing (..) import Pages.Page exposing (Page) @@ -11,22 +11,14 @@ searches briefly while the user is typing). -} type alias Model = - { debouncer : Debouncer Msg Msg - , search : Maybe String - , input : String - } + Components.PatientsSearchForm.Model.Model emptyModel : Model emptyModel = - { debouncer = debounce 500 |> toDebouncer - , search = Nothing - , input = "" - } + Components.PatientsSearchForm.Model.emptyModel type Msg - = MsgDebouncer (Debouncer.Msg Msg) - | SetInput String - | SetSearch String - | SetActivePage Page + = SetActivePage Page + | MsgPatientsSearchForm Components.PatientsSearchForm.Model.Msg diff --git a/client/src/elm/Pages/People/Update.elm b/client/src/elm/Pages/People/Update.elm index 4615ee4374..3b85f735f6 100644 --- a/client/src/elm/Pages/People/Update.elm +++ b/client/src/elm/Pages/People/Update.elm @@ -1,53 +1,22 @@ module Pages.People.Update exposing (update) import App.Model -import Debouncer.Basic as Debouncer exposing (provideInput) -import Gizra.Update exposing (sequenceExtra) -import Maybe.Extra +import Components.PatientsSearchForm.Update import Pages.People.Model exposing (..) update : Msg -> Model -> ( Model, Cmd Msg, List App.Model.Msg ) update msg model = case msg of - MsgDebouncer subMsg -> - let - ( subModel, subCmd, extraMsg ) = - Debouncer.update subMsg model.debouncer - in - ( { model | debouncer = subModel } - , Cmd.map MsgDebouncer subCmd - , [] - ) - |> sequenceExtra update (Maybe.Extra.toList extraMsg) - SetActivePage page -> ( model , Cmd.none , [ App.Model.SetActivePage page ] ) - SetSearch search -> + MsgPatientsSearchForm subMsg -> let - trimmed = - String.trim search - - maybeSearch = - if String.isEmpty trimmed then - Nothing - - else - Just trimmed + ( modelUpdated, cmd ) = + Components.PatientsSearchForm.Update.update subMsg model in - ( { model | search = maybeSearch } - , Cmd.none - , [] - ) - - SetInput input -> - ( { model | input = input } - , Cmd.none - , [] - ) - |> sequenceExtra update - [ MsgDebouncer <| provideInput <| SetSearch input ] + ( modelUpdated, Cmd.map MsgPatientsSearchForm cmd, [] ) diff --git a/client/src/elm/Pages/People/View.elm b/client/src/elm/Pages/People/View.elm index 663a1adbb2..03a188ff4d 100644 --- a/client/src/elm/Pages/People/View.elm +++ b/client/src/elm/Pages/People/View.elm @@ -10,6 +10,8 @@ import Backend.Person.Utils exposing (defaultIconForPerson, graduatingAgeInMonth import Backend.PrenatalActivity.Model import Backend.Session.Utils exposing (getSession) import Backend.Village.Utils exposing (personLivesInVillage) +import Components.PatientsSearchForm.Utils exposing (..) +import Components.PatientsSearchForm.View import Gizra.Html exposing (emptyNode, showIf, showMaybe) import Gizra.NominalDate exposing (NominalDate, diffMonths) import Html exposing (..) @@ -126,11 +128,11 @@ viewSearchForm : Language -> NominalDate -> Maybe VillageId -> Bool -> Initiator viewSearchForm language currentDate maybeVillageId isChw initiator relation model db = let searchForm = - Pages.Utils.viewSearchForm language model.input Translate.PlaceholderEnterParticipantName SetInput + Components.PatientsSearchForm.View.view language model + |> Html.map Pages.People.Model.MsgPatientsSearchForm relatedPerson = - relation - |> Maybe.andThen (\id -> Dict.get id db.people) + Maybe.andThen (\id -> Dict.get id db.people) relation |> Maybe.andThen RemoteData.toMaybe expectedAge = @@ -149,8 +151,7 @@ viewSearchForm language currentDate maybeVillageId isChw initiator relation mode ) searchValue = - model.search - |> Maybe.withDefault "" + Components.PatientsSearchForm.Utils.getSearchValue model results = if String.isEmpty searchValue then @@ -225,8 +226,7 @@ viewSearchForm language currentDate maybeVillageId isChw initiator relation mode else True in - Dict.get searchValue db.personSearches - |> Maybe.withDefault NotAsked + Components.PatientsSearchForm.Utils.getSearchResults db model |> RemoteData.map (Dict.filter (\filteredPersonId filteredPerson -> @@ -240,8 +240,7 @@ viewSearchForm language currentDate maybeVillageId isChw initiator relation mode |> Just summary = - results - |> Maybe.map (viewWebData language viewSummary identity) + Maybe.map (viewWebData language viewSummary identity) results |> Maybe.withDefault emptyNode viewSummary data = @@ -251,8 +250,7 @@ viewSearchForm language currentDate maybeVillageId isChw initiator relation mode |> text searchResultsParticipants = - results - |> Maybe.withDefault (Success Dict.empty) + Maybe.withDefault (Success Dict.empty) results |> RemoteData.withDefault Dict.empty |> Dict.map (viewParticipant language currentDate initiator relation db) |> Dict.values diff --git a/client/src/elm/Pages/Utils.elm b/client/src/elm/Pages/Utils.elm index 1d9f3c1425..374b1a7040 100644 --- a/client/src/elm/Pages/Utils.elm +++ b/client/src/elm/Pages/Utils.elm @@ -442,12 +442,6 @@ resolveSelectedDateForMonthSelector currentDate monthGap = -- Inputs -viewSearchForm : Language -> String -> TranslationId -> (String -> msg) -> Html msg -viewSearchForm language inputValue placeholderTransId setInputMsg = - div [ class "ui search form" ] - [ viewTextInput language inputValue setInputMsg (Just placeholderTransId) (Just "search-input") ] - - viewTextInput : Language -> String -> (String -> msg) -> Maybe TranslationId -> Maybe String -> Html msg viewTextInput language inputValue setInputMsg placeholderTransId inputClass = let diff --git a/client/src/elm/Translate.elm b/client/src/elm/Translate.elm index edb9945caf..4ddc004740 100644 --- a/client/src/elm/Translate.elm +++ b/client/src/elm/Translate.elm @@ -1033,6 +1033,7 @@ type TranslationId | MyRelatedBy MyRelatedBy | MyRelatedByQuestion MyRelatedBy | Name + | NationalId | NationalIdNumber | NCDAANCVisitsCounseling | NCDABirthweightQuestion @@ -1218,6 +1219,7 @@ type TranslationId | PlaceholderEnterHeight | PlaceholderEnterMUAC | PlaceholderEnterParticipantName + | PlaceholderEnterParticipantNationalId | PlaceholderEnterWeight | PlaceholderSearchContactName | PlacentaPrevia @@ -1698,6 +1700,7 @@ type TranslationId | SavedMoneyQuestion | SaveError | ScheduleFollowUp + | SearchBy | SearchEhezaForExistingParticipants | SearchExistingParticipants | SearchHelper @@ -11207,6 +11210,12 @@ translationSet trans = , kirundi = Just "Izina" } + NationalId -> + { english = "National ID" + , kinyarwanda = Nothing + , kirundi = Nothing + } + NationalIdNumber -> { english = "National ID Number" , kinyarwanda = Just "Numero y'irangamuntu" @@ -13925,6 +13934,12 @@ translationSet trans = , kirundi = Just "Andika izina ry'uwitavye hano" } + PlaceholderEnterParticipantNationalId -> + { english = "Enter participant national ID here" + , kinyarwanda = Nothing + , kirundi = Nothing + } + PlaceholderEnterWeight -> { english = "Enter weight here…" , kinyarwanda = Just "Andika ibiro hano…" @@ -21201,6 +21216,12 @@ translationSet trans = , kirundi = Just "Tegura ibikurikira" } + SearchBy -> + { english = "Search by" + , kinyarwanda = Nothing + , kirundi = Nothing + } + SearchEhezaForExistingParticipants -> { english = "Search E-Heza to see if the contact already exists" , kinyarwanda = Just "Reba muri E-heza niba abo bahuye basanzwe barimo" diff --git a/client/src/js/app.js b/client/src/js/app.js index 49d0b16695..5e89675731 100644 --- a/client/src/js/app.js +++ b/client/src/js/app.js @@ -312,6 +312,11 @@ dbSync.version(28).upgrade(function (tx) { dbSync.version(29).upgrade(function (tx) { return tx.nodeChanges.clear(); }); + +dbSync.version(30).stores({ + shards: '&uuid,type,vid,status,person,[shard+vid],prenatal_encounter,nutrition_encounter,acute_illness_encounter,home_visit_encounter,well_child_encounter,ncd_encounter,child_scoreboard_encounter,tuberculosis_encounter,hiv_encounter,*name_search,[type+clinic],[type+person],[type+related_to],[type+person+related_to],[type+individual_participant],[type+adult],[type+province+district+sector+cell+village],newborn,*participating_patients,*national_id_number', +}); + /** * --- !!! IMPORTANT !!! --- * @@ -370,7 +375,7 @@ function gatherWords (text) { * * @type {number} */ -const dbVersion = 29; +const dbVersion = 30; /** * Return saved info for General sync. diff --git a/client/src/js/nodes.js b/client/src/js/nodes.js index 4abf179c80..b80b9ca7d4 100644 --- a/client/src/js/nodes.js +++ b/client/src/js/nodes.js @@ -860,6 +860,8 @@ var modifyQuery = Promise.resolve(); if (type === 'person') { + // There're 3 options for SINGLE param that's passed. + // No need ot wory about combinations of several params. var nameContains = params.get('name_contains'); if (nameContains) { // For the case when there's more than one word as an input, @@ -904,23 +906,45 @@ return Promise.resolve(); }); } - else { - var geoFields = params.get('geo_fields'); - if (geoFields) { - var fields = geoFields.split('|'); - modifyQuery = modifyQuery.then(function () { - criteria.province = fields[0]; - criteria.district = fields[1]; - criteria.sector = fields[2]; - criteria.cell = fields[3]; - criteria.village = fields[4]; - query = table.where(criteria); - - countQuery = query.clone(); - return Promise.resolve(); + // Second option for param. + var nationalIdContains = params.get('national_id_contains'); + if (nationalIdContains) { + modifyQuery = modifyQuery.then(function () { + // We search for resulting persons that start with any of the input words (apply 'OR' condition). + query = table.where('national_id_number').startsWith(nationalIdContains).distinct().and(function (person) { + // If person is marked as deleted, do not include it in search results. + return (person.deleted !== true); }); - } + + // Cloning doesn't seem to work for this one. + countQuery = table.where('national_id_number').startsWith(nationalIdContains).distinct().and(function (person) { + // If person is marked as deleted, do not include it in search results. + return (person.deleted !== true); + }); + + sortBy = 'label'; + + return Promise.resolve(); + }); + } + + // Third option for param. + var geoFields = params.get('geo_fields'); + if (geoFields) { + var fields = geoFields.split('|'); + modifyQuery = modifyQuery.then(function () { + criteria.province = fields[0]; + criteria.district = fields[1]; + criteria.sector = fields[2]; + criteria.cell = fields[3]; + criteria.village = fields[4]; + query = table.where(criteria); + + countQuery = query.clone(); + + return Promise.resolve(); + }); } } diff --git a/client/src/js/sw.js b/client/src/js/sw.js index 145e6db637..7ff8172f72 100644 --- a/client/src/js/sw.js +++ b/client/src/js/sw.js @@ -31,7 +31,7 @@ var screenshotsUploadUrlRegex = /\/cache-upload\/screenshots/; * * @type {number} */ -var dbVerno = 29; +var dbVerno = 30; // All those entities are the entities we're going to get from the backend. // They should also be mapped in SyncManager.Model.BackendGeneralEntity (for diff --git a/server/hedley/modules/custom/hedley_restful/hedley_restful.module b/server/hedley/modules/custom/hedley_restful/hedley_restful.module index 4cbb4477ab..5672d084d1 100644 --- a/server/hedley/modules/custom/hedley_restful/hedley_restful.module +++ b/server/hedley/modules/custom/hedley_restful/hedley_restful.module @@ -5,7 +5,7 @@ * Code for the RESTful integration. */ -define('HEDLEY_RESTFUL_CLIENT_SIDE_INDEXEDDB_SCHEMA_VERSION', 29); +define('HEDLEY_RESTFUL_CLIENT_SIDE_INDEXEDDB_SCHEMA_VERSION', 30); define('HEDLEY_RESTFUL_INCIDENT_TYPE_CONTENT_UPLOAD', 'content-upload'); /**