diff --git a/frontend/src/jsMain/kotlin/ru/thetax/views/main/CardWithCalculations.kt b/frontend/src/jsMain/kotlin/ru/thetax/views/main/CardWithCalculations.kt index 4d1b1d4..03ccc61 100644 --- a/frontend/src/jsMain/kotlin/ru/thetax/views/main/CardWithCalculations.kt +++ b/frontend/src/jsMain/kotlin/ru/thetax/views/main/CardWithCalculations.kt @@ -11,6 +11,7 @@ import ru.thetax.calculator.TaxCalculator import ru.thetax.calculator.TaxDetail import ru.thetax.calculator.TaxRates import ru.thetax.views.utils.PeriodEnum +import ru.thetax.views.utils.externals.i18n.TranslationFunction import ru.thetax.views.utils.externals.i18n.useTranslation import ru.thetax.views.utils.formatNumber import web.cssom.BorderRadius @@ -20,68 +21,63 @@ import web.cssom.ClassName * Detailed tax calculations */ val cardWithCalculations = FC { props -> - // FixMe: should be added later - /*val (t) = useTranslation("calculator-card")*/ - + val (t) = useTranslation("calculator-card") val tax = TaxCalculator(props.salaryDoubleInternal, true, false) div { - className = ClassName("row justify-content-center") + className = ClassName("col-lg-5 col-md-7 col-sm-8 col-xs-12") div { - className = ClassName("col-lg-5 col-md-7 col-sm-8 col-xs-12") + className = ClassName("card border-top-0") + style = jso { + borderRadius = 0.unsafeCast() + } div { - className = ClassName("card border-top-0") - style = jso { - borderRadius = 0.unsafeCast() + className = ClassName("card-header") + when (props.periodInput) { + PeriodEnum.YEAR -> +"Расчет налога".t() + PeriodEnum.MONTH -> +"Расчет налога".t() + else -> TODO("Other periods are not supported yet") } - div { - className = ClassName("card-header") - when (props.periodInput) { - PeriodEnum.YEAR -> +"Расчет налога" - PeriodEnum.MONTH -> +"Расчет налога" - else -> TODO("Other periods are not supported yet") - } + } + div { + className = ClassName("card-body text-dark") + generalRow("Доход до налога".t(), props.salaryDoubleInternal, t) + hr { + className = ClassName("bg-danger border-2 border-top border-secondary") } - div { - className = ClassName("card-body text-dark") - generalRow("Доход до налога", props.salaryDoubleInternal) - hr { - className = ClassName("bg-danger border-2 border-top border-secondary") - } - rowWithRates(TaxRates.RATE_13, tax.taxDetails) - rowWithRates(TaxRates.RATE_15, tax.taxDetails) - rowWithRates(TaxRates.RATE_18, tax.taxDetails) - rowWithRates(TaxRates.RATE_20, tax.taxDetails) - rowWithRates(TaxRates.RATE_22, tax.taxDetails) - hr { - className = ClassName("bg-danger border-2 border-top border-secondary") - } - generalRow("Общий налог", tax.totalTax) + rowWithRates(TaxRates.RATE_13, tax.taxDetails, t) + rowWithRates(TaxRates.RATE_15, tax.taxDetails, t) + rowWithRates(TaxRates.RATE_18, tax.taxDetails, t) + rowWithRates(TaxRates.RATE_20, tax.taxDetails, t) + rowWithRates(TaxRates.RATE_22, tax.taxDetails, t) + hr { + className = ClassName("bg-danger border-2 border-top border-secondary") } + generalRow("Общий налог".t(), tax.totalTax, t) + } + } + div { + className = ClassName("card border-top-0") + style = jso { + borderRadius = 0.unsafeCast() } div { - className = ClassName("card border-top-0") - style = jso { - borderRadius = 0.unsafeCast() - } - div { - className = ClassName("card-header") - when (props.periodInput) { - PeriodEnum.YEAR -> +"Доход после налогов" - PeriodEnum.MONTH -> +"Доход после налогов" - else -> TODO("Other periods are not supported yet") - } + className = ClassName("card-header") + when (props.periodInput) { + PeriodEnum.YEAR -> +"Доход после налогов".t() + PeriodEnum.MONTH -> +"Доход после налогов".t() + else -> TODO("Other periods are not supported yet") } - div { - className = ClassName("card-body text-dark") - val salaryAfterTax = props.salaryDoubleInternal - tax.totalTax - generalRow("В год", salaryAfterTax) - hr { - className = ClassName("bg-danger border-2 border-top border-secondary") - } - generalRow("В месяц", salaryAfterTax / 12) + } + div { + className = ClassName("card-body text-dark") + val salaryAfterTax = props.salaryDoubleInternal - tax.totalTax + generalRow("В год".t(), salaryAfterTax, t) + hr { + className = ClassName("bg-danger border-2 border-top border-secondary") } + generalRow("В месяц".t(), salaryAfterTax / 12, t) } } } @@ -104,14 +100,14 @@ fun parseAndCalculateYearSalary(inputSalary: String, periodInput: PeriodEnum): D Double.NaN } -fun ChildrenBuilder.rowWithRates(rate: TaxRates, value: List) { +fun ChildrenBuilder.rowWithRates(rate: TaxRates, value: List, t: TranslationFunction) { div { className = ClassName("row") div { className = ClassName("col-7") p { className = ClassName("ms-5 fs-6") - +"Налог ${rate.rate * 100}%" + +("Налог".t() + " ${rate.rate * 100}%") } } div { @@ -121,13 +117,13 @@ fun ChildrenBuilder.rowWithRates(rate: TaxRates, value: List) { } } -fun ChildrenBuilder.generalRow(text: String, value: Double) { +fun ChildrenBuilder.generalRow(text: String, value: Double, t: TranslationFunction) { div { className = ClassName("row") div { className = ClassName("col-7") h5 { - +text + +text.t() } } div { diff --git a/frontend/src/jsMain/kotlin/ru/thetax/views/main/Header.kt b/frontend/src/jsMain/kotlin/ru/thetax/views/main/Header.kt index 3ae9a1e..d9c82d1 100644 --- a/frontend/src/jsMain/kotlin/ru/thetax/views/main/Header.kt +++ b/frontend/src/jsMain/kotlin/ru/thetax/views/main/Header.kt @@ -1,8 +1,7 @@ package ru.thetax.views.main import js.objects.jso -import react.FC -import react.Props +import react.* import react.dom.html.ReactHTML.a import react.dom.html.ReactHTML.div import react.dom.html.ReactHTML.h1 @@ -11,8 +10,6 @@ import react.dom.html.ReactHTML.input import react.dom.html.ReactHTML.option import react.dom.html.ReactHTML.p import react.dom.html.ReactHTML.select -import react.useEffect -import react.useState import ru.thetax.views.utils.PeriodEnum import ru.thetax.views.utils.externals.cookie.cookie import ru.thetax.views.utils.externals.cookie.getLanguageCode @@ -61,15 +58,22 @@ val headerAndInput = FC { props -> div { className = ClassName("col-2 d-flex justify-content-center") PlatformLanguages.entries.forEach { platformLanguage -> - img { - className = ClassName("me-2") - src = "/img/flags/${platformLanguage.code}.svg" - style = jso { - width = 1.4.rem - opacity = 0.8.unsafeCast() - cursor = "pointer".unsafeCast() + div { + className = ClassName(if (platformLanguage != language) "logo" else "") + img { + className = ClassName("me-2") + src = "/img/flags/${platformLanguage.code}.svg" + style = jso { + if (platformLanguage == language) { + opacity = 1.unsafeCast() + } else { + cursor = "pointer".unsafeCast() + opacity = 0.7.unsafeCast() + } + width = 1.4.rem + } + onClick = { setSelectedLanguage(platformLanguage) } } - onClick = { setSelectedLanguage(platformLanguage) } } } } @@ -89,11 +93,11 @@ val headerAndInput = FC { props -> className = ClassName("row text-center pt-5") h1 { className = ClassName("text-white animate__animated animate__bounce") - +"Российский налоговый калькулятор" + +"Российский налоговый калькулятор".t() } p { className = ClassName("text-white") - +"Каким будет Ваш налог с 2025го года?" + +"Каким будет Ваш налог с 2025го года?".t() } } div { @@ -107,12 +111,12 @@ val headerAndInput = FC { props -> className = ClassName("input-group-lg shadow mb-1") input { className = ClassName("form-control custom-input ${props.validInput}") - placeholder = "Доход до налога" + placeholder = "Доход до налога".t() style = jso { borderTopRightRadius = 0.unsafeCast() borderBottomRightRadius = 0.unsafeCast() } - title = "Зарплата в рублях" + title = "Зарплата в рублях".t() asDynamic()["data-toggle"] = "tooltip" asDynamic()["data-placement"] = "top" onChange = { @@ -120,9 +124,17 @@ val headerAndInput = FC { props -> setSalaryInput(inputValue) val yearSalary = parseAndCalculateYearSalary(inputValue, props.periodInput) props.setSalaryDoubleIntenal(yearSalary) - if (yearSalary.isNaN()) props.setValidInput("is-invalid") else props.setValidInput( - "is-valid" - ) + // this "startTransition" logic prevents the following error: + // A component suspended while responding to synchronous input. + // This will cause the UI to be replaced with a loading indicator. + // To fix, updates that suspend should be wrapped with startTransition. + // + // And it is somehow related to the check that we have in a parent class (where we check isValid) + startTransition { + if (yearSalary.isNaN()) props.setValidInput("is-invalid") else props.setValidInput( + "is-valid" + ) + } } } } @@ -135,7 +147,7 @@ val headerAndInput = FC { props -> defaultValue = PeriodEnum.YEAR option { value = PeriodEnum.YEAR - +"В год" + +"В год".t() } style = jso { borderTopLeftRadius = 0.unsafeCast() @@ -143,7 +155,7 @@ val headerAndInput = FC { props -> } option { value = PeriodEnum.MONTH - +"В месяц" + +"В месяц".t() } onChange = { val period = PeriodEnum.valueOf(it.target.value) diff --git a/frontend/src/jsMain/kotlin/ru/thetax/views/main/TaxCalculatorView.kt b/frontend/src/jsMain/kotlin/ru/thetax/views/main/TaxCalculatorView.kt index c0266b2..b2104fa 100644 --- a/frontend/src/jsMain/kotlin/ru/thetax/views/main/TaxCalculatorView.kt +++ b/frontend/src/jsMain/kotlin/ru/thetax/views/main/TaxCalculatorView.kt @@ -1,7 +1,9 @@ package ru.thetax.views.main import react.* +import react.dom.html.ReactHTML.div import ru.thetax.views.utils.PeriodEnum +import web.cssom.ClassName val taxCalculatorView: FC = FC { // ToDo: currency is not supported yet @@ -20,12 +22,14 @@ val taxCalculatorView: FC = FC { this.periodInput = periodInput } - if (validInput == "is-valid") { - cardWithCalculations { - this.salaryDoubleInternal = salaryDoubleInternal - this.periodInput = periodInput + + div { + className = ClassName("collapsible ${if (validInput == "is-valid") "active" else ""} row justify-content-center country") + cardWithCalculations { + this.salaryDoubleInternal = salaryDoubleInternal + this.periodInput = periodInput + } } - } } diff --git a/frontend/src/jsMain/resources/locales/cn/calculator-card.json b/frontend/src/jsMain/resources/locales/cn/calculator-card.json index 80e4db2..7cb092a 100644 --- a/frontend/src/jsMain/resources/locales/cn/calculator-card.json +++ b/frontend/src/jsMain/resources/locales/cn/calculator-card.json @@ -1,3 +1,9 @@ { - "Доход до налога": "XXX" + "Доход до налога": "", + "Расчет налога": "", + "Общий налог": "", + "Доход после налогов": "", + "В год": "", + "В месяц": "", + "Налог": "" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/locales/cn/header.json b/frontend/src/jsMain/resources/locales/cn/header.json index b1598c4..245c2d5 100644 --- a/frontend/src/jsMain/resources/locales/cn/header.json +++ b/frontend/src/jsMain/resources/locales/cn/header.json @@ -1,3 +1,9 @@ { - "thetax.ru": "thetax.ru" + "thetax.ru": "thetax.ru", + "Российский налоговый калькулятор": "", + "Каким будет Ваш налог с 2025го года?": "", + "Доход до налога": "", + "Зарплата в рублях": "", + "В год": "", + "В месяц": "" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/locales/en/calculator-card.json b/frontend/src/jsMain/resources/locales/en/calculator-card.json index 80e4db2..7cb092a 100644 --- a/frontend/src/jsMain/resources/locales/en/calculator-card.json +++ b/frontend/src/jsMain/resources/locales/en/calculator-card.json @@ -1,3 +1,9 @@ { - "Доход до налога": "XXX" + "Доход до налога": "", + "Расчет налога": "", + "Общий налог": "", + "Доход после налогов": "", + "В год": "", + "В месяц": "", + "Налог": "" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/locales/en/header.json b/frontend/src/jsMain/resources/locales/en/header.json index b1598c4..245c2d5 100644 --- a/frontend/src/jsMain/resources/locales/en/header.json +++ b/frontend/src/jsMain/resources/locales/en/header.json @@ -1,3 +1,9 @@ { - "thetax.ru": "thetax.ru" + "thetax.ru": "thetax.ru", + "Российский налоговый калькулятор": "", + "Каким будет Ваш налог с 2025го года?": "", + "Доход до налога": "", + "Зарплата в рублях": "", + "В год": "", + "В месяц": "" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/locales/ru/calculator-card.json b/frontend/src/jsMain/resources/locales/ru/calculator-card.json index 80e4db2..72bac09 100644 --- a/frontend/src/jsMain/resources/locales/ru/calculator-card.json +++ b/frontend/src/jsMain/resources/locales/ru/calculator-card.json @@ -1,3 +1,9 @@ { - "Доход до налога": "XXX" + "Доход до налога": "Доход до налога", + "Расчет налога": "Расчет налога", + "Общий налог": "Общий налог", + "Доход после налогов": "Доход после налогов", + "В год": "В год", + "В месяц": "В месяц", + "Налог": "Налог" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/locales/ru/header.json b/frontend/src/jsMain/resources/locales/ru/header.json index a7649f7..bd28fbb 100644 --- a/frontend/src/jsMain/resources/locales/ru/header.json +++ b/frontend/src/jsMain/resources/locales/ru/header.json @@ -1,3 +1,9 @@ { - "thetax.ru": "налог.онлайн" + "thetax.ru": "thetax.ru", + "Российский налоговый калькулятор": "Российский налоговый калькулятор", + "Каким будет Ваш налог с 2025го года?": "Каким будет Ваш налог с 2025го года?", + "Доход до налога": "Доход до налога", + "Зарплата в рублях": "Зарплата в рублях", + "В год": "В год", + "В месяц": "В месяц" } \ No newline at end of file diff --git a/frontend/src/jsMain/resources/scss/tax-app.scss b/frontend/src/jsMain/resources/scss/tax-app.scss index 6f2911a..a65c440 100644 --- a/frontend/src/jsMain/resources/scss/tax-app.scss +++ b/frontend/src/jsMain/resources/scss/tax-app.scss @@ -17,3 +17,31 @@ background: lightgoldenrodyellow; z-index: 0; } + +.logo img { + transition: 0.4s; +} + +.logo:hover img { + transition: 0.4s; + transform: scale(1.1); +} + +.logo svg { + opacity: 0.8; + transition: 0.4s; +} + +.logo:hover svg { + transition: 0.4s; + opacity: 1; +} + +.collapsible { + opacity: 0; +} + +.collapsible.active { + transition: 0.3s; + opacity: 100; +}