aboutsummaryrefslogtreecommitdiff
path: root/front/src
diff options
context:
space:
mode:
authorGuillermo Ramos2025-03-08 14:07:23 +0100
committerGuillermo Ramos2025-03-08 15:58:02 +0100
commite93e62ee9f573c494d570f7d5ec5e065e2d8ff73 (patch)
tree3c72447bdbe8bc529a9639ab1e5630cdf2bd5c63 /front/src
parent150734bb207fa99c4c74e8e07d79355abf5c7926 (diff)
downloadhiccup-e93e62ee9f573c494d570f7d5ec5e065e2d8ff73.tar.gz
Toggle language + Spanish translation
Diffstat (limited to 'front/src')
-rw-r--r--front/src/Main.elm365
1 files changed, 284 insertions, 81 deletions
diff --git a/front/src/Main.elm b/front/src/Main.elm
index 1fd3839..6c9b5fe 100644
--- a/front/src/Main.elm
+++ b/front/src/Main.elm
@@ -59,6 +59,80 @@ amountSep =
+-- LOCALE
+
+
+make_t : Language -> String -> String
+make_t lang str =
+ case lang of
+ EN ->
+ str
+
+ ES ->
+ case str of
+ "Principal: " ->
+ "Capital: "
+
+ "Interest: " ->
+ "Interés: "
+
+ "% from total" ->
+ "% del total"
+
+ "Title..." ->
+ "Título..."
+
+ "Property price: " ->
+ "Precio del inmueble: "
+
+ "Initial contribution: " ->
+ "Contribución inicial: "
+
+ "Interest rate: " ->
+ "Tipo de interés: "
+
+ "Years: " ->
+ "Años: "
+
+ "VAT: " ->
+ "IVA/ITP: "
+
+ "Agent fee: " ->
+ "Honorarios de agencia: "
+
+ "Simulate" ->
+ "Simular"
+
+ "Total to pay: " ->
+ "Total a pagar: "
+
+ "Initial payment: " ->
+ "Pago inicial: "
+
+ "Property: " ->
+ "Inmueble: "
+
+ "Financed (mortgage): " ->
+ "Financiado (hipoteca): "
+
+ "Year" ->
+ "Año"
+
+ "Month" ->
+ "Mes"
+
+ "Quota" ->
+ "Cuota"
+
+ "Pending" ->
+ "Pendiente"
+
+ _ ->
+ str
+
+
+
+-- "TODO TRANS"
-- MAIN
@@ -105,18 +179,20 @@ capitalDecoder =
(field "interest" float)
-capitalSumView : Capital -> Html Msg
-capitalSumView { principal, interest } =
+capitalSumView : Model -> Capital -> Html Msg
+capitalSumView { t } { principal, interest } =
let
partsTitle =
String.concat
- [ "Principal: "
+ [ t "Principal: "
, amountToString principal
- , "\nInterest: "
+ , "\n"
+ , t "Interest: "
, amountToString interest
, " ("
, Round.round 2 (100 * interest / (principal + interest))
- , "% from total)"
+ , t "% from total"
+ , ")"
]
in
span [ class "underline", title partsTitle ] [ amountView (principal + interest) ]
@@ -166,29 +242,38 @@ defaultRawSpecs =
rawSpecsParser : UQ.Parser RawSpecs
rawSpecsParser =
- UQ.map8 RawSpecs
+ let
+ apply argParser funcParser =
+ UQ.map2 (<|) funcParser argParser
+ in
+ UQ.map RawSpecs
(UQ.map (Maybe.withDefault defaultRawSpecs.title) <| UQ.string "title")
- (UQ.map (Maybe.withDefault defaultRawSpecs.total) <| UQ.string "total")
- (UQ.map (Maybe.withDefault defaultRawSpecs.initial) <| UQ.string "initial")
- (UQ.map (Maybe.withDefault defaultRawSpecs.rate) <| UQ.string "rate")
- (UQ.map (Maybe.withDefault defaultRawSpecs.i1) <| UQ.string "i1")
- (UQ.map (Maybe.withDefault defaultRawSpecs.years) <| UQ.string "years")
- (UQ.map (Maybe.withDefault defaultRawSpecs.vat) <| UQ.string "vat")
- (UQ.map (Maybe.withDefault defaultRawSpecs.fee) <| UQ.string "fee")
-
-
-rawSpecsToURL : RawSpecs -> String
-rawSpecsToURL { title, total, initial, rate, i1, years, vat, fee } =
- UB.toQuery <|
- [ UB.string "title" title
- , UB.string "total" total
- , UB.string "initial" initial
- , UB.string "rate" rate
- , UB.string "i1" i1
- , UB.string "years" years
- , UB.string "vat" vat
- , UB.string "fee" fee
- ]
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.total) <| UQ.string "total")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.initial) <| UQ.string "initial")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.rate) <| UQ.string "rate")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.i1) <| UQ.string "i1")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.years) <| UQ.string "years")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.vat) <| UQ.string "vat")
+ |> apply (UQ.map (Maybe.withDefault defaultRawSpecs.fee) <| UQ.string "fee")
+
+
+rawSpecsToQS : RawSpecs -> List UB.QueryParameter
+rawSpecsToQS { title, total, initial, rate, i1, years, vat, fee } =
+ [ UB.string "title" title
+ , UB.string "total" total
+ , UB.string "initial" initial
+ , UB.string "rate" rate
+ , UB.string "i1" i1
+ , UB.string "years" years
+ , UB.string "vat" vat
+ , UB.string "fee" fee
+ ]
+
+
+modelToUrl : Model -> String
+modelToUrl { settings, rawSpecs } =
+ UB.toQuery
+ (settingsToQS settings ++ rawSpecsToQS rawSpecs)
type alias MortgageSpecs =
@@ -212,8 +297,8 @@ parseMortgageSpecs { total, rate, i1, years } =
Nothing
-mortgageSpecsToURL : MortgageSpecs -> String
-mortgageSpecsToURL { principal, i1, years } =
+mortgageSpecsToUrl : MortgageSpecs -> String
+mortgageSpecsToUrl { principal, i1, years } =
UB.absolute [ "api", "simulate" ]
[ UB.string "principal" (String.fromFloat principal)
, UB.string "i1" (String.fromFloat (i1 / 100))
@@ -224,30 +309,77 @@ mortgageSpecsToURL { principal, i1, years } =
runMortgageSim : Model -> MortgageSpecs -> Cmd Msg
runMortgageSim m mortgageSpecs =
Http.get
- { url = mortgageSpecsToURL mortgageSpecs
+ { url = mortgageSpecsToUrl mortgageSpecs
, expect = Http.expectJson (GotMortgageSim m) simDecoder
}
+type Language
+ = ES
+ | EN
+
+
+langToString : Language -> String
+langToString lang =
+ case lang of
+ ES ->
+ "ES"
+
+ EN ->
+ "EN"
+
+
+langFromString : String -> Language
+langFromString lang =
+ case lang of
+ "ES" ->
+ ES
+
+ _ ->
+ EN
+
+
+type alias Settings =
+ { lang : Language }
+
+
+defaultSettings : Settings
+defaultSettings =
+ { lang = ES }
+
+
+settingsParser : UQ.Parser Settings
+settingsParser =
+ UQ.map Settings
+ (UQ.map (Maybe.withDefault defaultSettings.lang << Maybe.map langFromString) <| UQ.string "lang")
+
+
+settingsToQS : Settings -> List UB.QueryParameter
+settingsToQS { lang } =
+ [ UB.string "lang" (langToString lang) ]
+
+
type alias Model =
- { error : String
+ { settings : Settings
, navKey : Nav.Key
+ , error : String
, rawSpecs : RawSpecs
, expandedYears : Set Int
, simulation : Maybe ( RawSpecs, MortgageSim )
+ , t : String -> String
}
type Route
= NotFound
- | Root RawSpecs
+ | Root ( Settings, RawSpecs )
-routeQuery : Route -> RawSpecs
+routeQuery : Route -> ( Settings, RawSpecs )
routeQuery route =
case route of
NotFound ->
- defaultRawSpecs
+ ( defaultSettings, defaultRawSpecs )
Root query ->
query
@@ -256,7 +388,7 @@ routeQuery route =
routeParser : U.Parser (Route -> a) a
routeParser =
U.oneOf
- [ U.map Root (U.top <?> rawSpecsParser)
+ [ U.map Root (U.top <?> UQ.map2 Tuple.pair settingsParser rawSpecsParser)
]
@@ -267,11 +399,17 @@ toRoute url =
init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
init () url navKey =
- ( { navKey = navKey
+ let
+ ( settings, rawSpecs ) =
+ routeQuery (toRoute url)
+ in
+ ( { settings = settings
+ , navKey = navKey
, error = ""
- , rawSpecs = Debug.log "set rawSpecs" <| routeQuery (toRoute url)
+ , rawSpecs = rawSpecs
, expandedYears = Set.empty
, simulation = Nothing
+ , t = make_t settings.lang
}
, Cmd.none
)
@@ -337,10 +475,15 @@ type SpecField
| Fee
+type SettingsChange
+ = ToggleLang
+
+
type Msg
= SetUrl UrlRequest
| ChangedUrl Url
| UpdateSpecs SpecField String
+ | UpdateSettings SettingsChange
| RunMortgageSim MortgageSpecs
| GotMortgageSim Model (Result Http.Error MortgageSim)
| SetExpandedYears (Set Int)
@@ -368,7 +511,7 @@ update msg m =
( m
, batch
[ runMortgageSim m specs
- , Nav.pushUrl m.navKey (rawSpecsToURL m.rawSpecs)
+ , Nav.pushUrl m.navKey (modelToUrl m)
]
)
@@ -379,7 +522,17 @@ update msg m =
( m, Nav.load url )
ChangedUrl url ->
- ( { m | rawSpecs = routeQuery (toRoute url) }, Cmd.none )
+ let
+ ( settings, rawSpecs ) =
+ routeQuery (toRoute url)
+ in
+ ( { m
+ | settings = settings
+ , rawSpecs = rawSpecs
+ , t = make_t settings.lang
+ }
+ , Cmd.none
+ )
UpdateSpecs field val ->
let
@@ -432,16 +585,41 @@ update msg m =
SetExpandedYears eyears ->
( { m | expandedYears = eyears }, Cmd.none )
+ UpdateSettings change ->
+ let
+ settings =
+ m.settings
+
+ newSettings =
+ case change of
+ ToggleLang ->
+ { settings
+ | lang =
+ case settings.lang of
+ EN ->
+ ES
+
+ ES ->
+ EN
+ }
+ in
+ ( m, Nav.pushUrl m.navKey (modelToUrl { m | settings = newSettings }) )
+
-- VIEW (THEME)
-butAttrs : List (Attribute Msg)
-butAttrs =
+primaryButAttrs : List (Attribute Msg)
+primaryButAttrs =
[ class "px-3 rounded-md bg-lime-300 enabled:active:bg-lime-400 border border-lime-600 disabled:opacity-75" ]
+secondaryButAttrs : List (Attribute Msg)
+secondaryButAttrs =
+ [ class "px-3 rounded-md text-gray-700 enabled:active:bg-lime-400 border border-2 border-gray-500 disabled:opacity-75" ]
+
+
clickableAttrs : Msg -> List (Attribute Msg)
clickableAttrs msg =
[ onClick msg, class "text-lime-600", style "cursor" "pointer" ]
@@ -514,8 +692,26 @@ amountToString amount =
amountStr
-specsView : RawSpecs -> Html Msg
-specsView rawSpecs =
+amountView : Float -> Html Msg
+amountView amount =
+ let
+ amountStr =
+ amountToString amount
+ in
+ case String.split (String.fromChar amountSep.decimal) amountStr of
+ int :: float :: [] ->
+ span []
+ [ text int
+ , text <| String.fromChar amountSep.decimal
+ , span [ class "text-xs" ] [ text float ]
+ ]
+
+ _ ->
+ text amountStr
+
+
+specsView : Model -> Html Msg
+specsView { t, settings, rawSpecs } =
let
{ title, total, rate, initial, i1, years, vat, fee } =
rawSpecs
@@ -532,14 +728,14 @@ specsView rawSpecs =
[ div []
[ input
[ class "min-w-full mb-2 py-1 px-3 text-xl font-bold lime-100"
- , placeholder "Title..."
+ , placeholder (t "Title...")
, value title
, onInput (UpdateSpecs Title)
]
[]
]
, div [ class "flex my-1" ]
- [ text "Property price: "
+ [ text (t "Property price: ")
, slider [ Html.Attributes.min "50000", Html.Attributes.max "800000", step "5000" ]
(UpdateSpecs TotalValue)
total
@@ -549,7 +745,7 @@ specsView rawSpecs =
]
, div [ class "flex my-1" ]
[ div []
- [ text "Initial contribution: "
+ [ text (t "Initial contribution: ")
, txtInput [ class "w-[100px]", Html.Attributes.min "0", Html.Attributes.max total ]
(UpdateSpecs Initial)
initial
@@ -563,14 +759,14 @@ specsView rawSpecs =
]
]
, div [ class "my-1" ]
- [ text "Interest rate: "
+ [ text (t "Interest rate: ")
, txtInput [ class "w-[80px]", Html.Attributes.min "0", Html.Attributes.max "100" ]
(UpdateSpecs I1)
i1
, text " % (nominal)"
]
, div [ class "flex my-1" ]
- [ text "Years: "
+ [ text (t "Years: ")
, slider [ Html.Attributes.min "1", Html.Attributes.max "40", step "1" ]
(UpdateSpecs Years)
years
@@ -578,21 +774,29 @@ specsView rawSpecs =
]
, div [ class "flex my-1" ]
[ div []
- [ text "VAT: "
+ [ text (t "VAT: ")
, txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "50" ]
(UpdateSpecs VAT)
vat
, text "%"
]
, div [ class "ml-6" ]
- [ text "Agent fee: "
+ [ text (t "Agent fee: ")
, txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "10" ]
(UpdateSpecs Fee)
fee
, text "%"
]
]
- , button (butAttrs ++ simButAttrs ++ [ class "mt-2" ]) [ text "Simulate" ]
+ , div [ class "flex justify-between my-1 mt-2" ]
+ [ button (primaryButAttrs ++ simButAttrs) [ text (t "Simulate") ]
+ , div [ class "flex" ]
+ [ button (secondaryButAttrs ++ [ class "mr-1", onClick (UpdateSettings ToggleLang) ])
+ [ text <| langToString settings.lang ]
+
+ -- , button (secondaryButAttrs ++ []) [ text "€" ]
+ ]
+ ]
]
@@ -603,7 +807,16 @@ historyView m quotas =
[ "Year", "Month", "Quota", "Pending" ]
head =
- thead [ class "bg-lime-100" ] [ tr [] (List.map (\t -> th [ class "px-3 py-1 border border-gray-300" ] [ text t ]) titles) ]
+ thead [ class "bg-lime-100" ]
+ [ tr []
+ (List.map
+ (\txt ->
+ th [ class "px-3 py-1 border border-gray-300" ]
+ [ text <| m.t txt ]
+ )
+ titles
+ )
+ ]
in
div [ class "pt-4 flex flex-col items-center" ]
[ table [ class "border border-collapse bg-gray-50 border-gray-400" ]
@@ -616,24 +829,6 @@ historyView m quotas =
]
-amountView : Float -> Html Msg
-amountView amount =
- let
- amountStr =
- amountToString amount
- in
- case String.split (String.fromChar amountSep.decimal) amountStr of
- int :: float :: [] ->
- span []
- [ text int
- , text <| String.fromChar amountSep.decimal
- , span [ class "text-xs" ] [ text float ]
- ]
-
- _ ->
- text amountStr
-
-
periodToYear : Int -> Int
periodToYear period =
((period - 1) // 12) + 1
@@ -678,7 +873,7 @@ quotaView m { period, payed, pending_principal } =
]
, text
(String.fromInt period)
- , capitalSumView payed
+ , capitalSumView m payed
, amountView pending_principal
]
)
@@ -689,7 +884,7 @@ quotaView m { period, payed, pending_principal } =
[ text ""
, text
(String.fromInt period)
- , capitalSumView payed
+ , capitalSumView m payed
, amountView pending_principal
]
)
@@ -701,6 +896,9 @@ quotaView m { period, payed, pending_principal } =
simView : Model -> ( RawSpecs, MortgageSim ) -> Html Msg
simView m ( rawSpecs, { history, topay, payed } ) =
let
+ t =
+ m.t
+
parseFloat =
Maybe.withDefault 0 << String.toFloat
@@ -719,21 +917,26 @@ simView m ( rawSpecs, { history, topay, payed } ) =
div []
[ hr [ class "my-5" ] []
, pre [ class "leading-none" ]
- [ text "Total to pay: "
+ [ text <| t "Total to pay: "
, amountView (topay.principal + topay.interest + initial + vat + fee)
- , text "\n├ Initial payment: "
+ , text "\n├ "
+ , text <| t "Initial payment: "
, amountView (initial + vat + fee)
- , text "\n│ ├ Property: "
+ , text "\n│ ├ "
+ , text <| t "Property: "
, amountView initial
- , text "\n│ ├ Agent fee: "
+ , text "\n│ ├ "
+ , text <| t "Agent fee: "
, amountView fee
- , text "\n│ └ VAT: "
+ , text "\n│ └ "
+ , text <| t "VAT: "
, amountView vat
- , text "\n└ Financed (mortgage): "
- , capitalSumView topay
+ , text "\n└ "
+ , text <| t "Financed (mortgage): "
+ , capitalSumView m topay
]
- -- , div [] [ text "payed: ", capitalSumView payed ]
+ -- , div [] [ text "payed: ", capitalSumView m payed ]
, historyView m history
]
@@ -744,7 +947,7 @@ view m =
, body =
[ div [ class "flex flex-col max-w-lg mx-auto items-center mt-2 p-3 border-2 rounded-md border-gray-500 bg-gray-100" ]
[ div [ class "min-w-full" ]
- [ specsView m.rawSpecs
+ [ specsView m
, case m.simulation of
Nothing ->
text ""