{{title}}
+ {{#error }} +{{error}}
+ {{/error}} + +diff --git a/web/src/main/kotlin/com/wbrawner/twigs/web/WebUtils.kt b/web/src/main/kotlin/com/wbrawner/twigs/web/WebUtils.kt
new file mode 100644
index 0000000..06c1d2e
--- /dev/null
+++ b/web/src/main/kotlin/com/wbrawner/twigs/web/WebUtils.kt
@@ -0,0 +1,30 @@
+package com.wbrawner.twigs.web
+
+import io.ktor.http.*
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.text.DecimalFormat
+import java.text.NumberFormat
+import java.util.*
+
+val currencyFormat = NumberFormat.getCurrencyInstance(Locale.US)
+val decimalFormat = DecimalFormat.getNumberInstance(Locale.US).apply {
+ with(this as DecimalFormat) {
+ decimalFormatSymbols = decimalFormatSymbols.apply {
+ currencySymbol = ""
+ isGroupingUsed = false
+ }
+ }
+}
+
+fun Parameters.getAmount() = decimalFormat.parse(get("amount"))
+ ?.toDouble()
+ ?.toBigDecimal()
+ ?.times(BigDecimal(100))
+ ?.toLong()
+ ?: 0L
+
+fun Long?.toDecimalString(): String {
+ if (this == null) return ""
+ return decimalFormat.format(toBigDecimal().divide(BigDecimal(100), 2, RoundingMode.HALF_UP))
+}
\ No newline at end of file
diff --git a/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryPages.kt b/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryPages.kt
index 81bb9d2..fb99183 100644
--- a/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryPages.kt
+++ b/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryPages.kt
@@ -40,6 +40,7 @@ fun TransactionResponse.toListItem(numberFormat: NumberFormat) = TransactionList
data class CategoryFormPage(
val category: CategoryResponse,
+ val amountLabel: String,
val budget: BudgetResponse,
override val user: UserResponse,
override val error: String? = null
@@ -49,6 +50,10 @@ data class CategoryFormPage(
} else {
"Edit Category"
}
+
+ val expenseChecked: String = if (category.expense) "checked" else ""
+ val incomeChecked: String = if (!category.expense) "checked" else ""
+ val archivedChecked: String = if (category.archived) "checked" else ""
}
data class CategoryWithBalanceResponse(
diff --git a/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryWebRoutes.kt b/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryWebRoutes.kt
index 9f0b3f1..193503d 100644
--- a/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryWebRoutes.kt
+++ b/web/src/main/kotlin/com/wbrawner/twigs/web/category/CategoryWebRoutes.kt
@@ -14,6 +14,8 @@ import com.wbrawner.twigs.toInstant
import com.wbrawner.twigs.toInstantOrNull
import com.wbrawner.twigs.web.NotFoundPage
import com.wbrawner.twigs.web.budget.toCurrencyString
+import com.wbrawner.twigs.web.getAmount
+import com.wbrawner.twigs.web.toDecimalString
import com.wbrawner.twigs.web.user.TWIGS_SESSION_COOKIE
import io.ktor.http.*
import io.ktor.server.application.*
@@ -57,9 +59,10 @@ fun Application.categoryWebRoutes(
description = "",
amount = 0,
budgetId = budgetId,
- expense = true,
+ expense = call.request.queryParameters["expense"]?.toBoolean() ?: true,
archived = false,
),
+ amountLabel = 0L.toDecimalString(),
budget = budgetService.budget(budgetId = budgetId, userId = user.id),
user = user
)
@@ -71,7 +74,7 @@ fun Application.categoryWebRoutes(
val user = userService.user(requireSession().userId)
val budgetId = call.parameters.getOrFail("budgetId")
try {
- val request = call.receiveParameters().toCategoryRequest()
+ val request = call.receiveParameters().toCategoryRequest(budgetId)
val category = categoryService.save(request, user.id)
call.respondRedirect("/budgets/${category.budgetId}/categories/${category.id}")
} catch (e: HttpException) {
@@ -84,11 +87,12 @@ fun Application.categoryWebRoutes(
id = "",
title = call.parameters["title"].orEmpty(),
description = call.parameters["description"].orEmpty(),
- amount = call.parameters["amount"]?.toLongOrNull() ?: 0L,
+ amount = 0L,
expense = call.parameters["expense"]?.toBoolean() ?: false,
archived = call.parameters["archived"]?.toBoolean() ?: false,
budgetId = budgetId
),
+ amountLabel = call.parameters["amount"]?.toLongOrNull().toDecimalString(),
budget = budgetService.budget(budgetId = budgetId, userId = user.id),
user = user,
error = e.message
@@ -167,10 +171,43 @@ fun Application.categoryWebRoutes(
call.respond(
MustacheContent(
"category-form.mustache",
- CategoryFormPage(category, budget, user)
+ CategoryFormPage(category, category.amount.toDecimalString(), budget, user)
)
)
}
+
+ post {
+ val user = userService.user(requireSession().userId)
+ val budgetId = call.parameters.getOrFail("budgetId")
+ val categoryId = call.parameters.getOrFail("id")
+ try {
+ val request = call.receiveParameters().toCategoryRequest(budgetId)
+ val category = categoryService.save(request, userId = user.id, categoryId = categoryId)
+ call.respondRedirect("/budgets/${category.budgetId}/categories/${category.id}")
+ } catch (e: HttpException) {
+ call.respond(
+ status = e.statusCode,
+ MustacheContent(
+ "category-form.mustache",
+ CategoryFormPage(
+ CategoryResponse(
+ id = "",
+ title = call.parameters["title"].orEmpty(),
+ description = call.parameters["description"].orEmpty(),
+ amount = 0L,
+ expense = call.parameters["expense"]?.toBoolean() ?: false,
+ archived = call.parameters["archived"]?.toBoolean() ?: false,
+ budgetId = budgetId
+ ),
+ amountLabel = call.parameters["amount"]?.toLongOrNull().toDecimalString(),
+ budget = budgetService.budget(budgetId = budgetId, userId = user.id),
+ user = user,
+ error = e.message
+ )
+ )
+ )
+ }
+ }
}
}
}
@@ -178,11 +215,11 @@ fun Application.categoryWebRoutes(
}
}
-private fun Parameters.toCategoryRequest() = CategoryRequest(
+private fun Parameters.toCategoryRequest(budgetId: String) = CategoryRequest(
title = get("title"),
description = get("description"),
- amount = get("amount")?.toLongOrNull(),
+ amount = getAmount(),
expense = get("expense")?.toBoolean(),
- archived = get("archived")?.toBoolean(),
- budgetId = get("budgetId"),
+ archived = get("archived") == "on",
+ budgetId = budgetId
)
\ No newline at end of file
diff --git a/web/src/main/kotlin/com/wbrawner/twigs/web/transaction/TransactionWebRoutes.kt b/web/src/main/kotlin/com/wbrawner/twigs/web/transaction/TransactionWebRoutes.kt
index 7c9ea2d..ccc2b12 100644
--- a/web/src/main/kotlin/com/wbrawner/twigs/web/transaction/TransactionWebRoutes.kt
+++ b/web/src/main/kotlin/com/wbrawner/twigs/web/transaction/TransactionWebRoutes.kt
@@ -12,6 +12,9 @@ import com.wbrawner.twigs.service.user.UserService
import com.wbrawner.twigs.toInstant
import com.wbrawner.twigs.web.NotFoundPage
import com.wbrawner.twigs.web.budget.toCurrencyString
+import com.wbrawner.twigs.web.currencyFormat
+import com.wbrawner.twigs.web.getAmount
+import com.wbrawner.twigs.web.toDecimalString
import com.wbrawner.twigs.web.user.TWIGS_SESSION_COOKIE
import io.ktor.http.*
import io.ktor.server.application.*
@@ -21,27 +24,10 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.util.*
-import java.math.BigDecimal
-import java.math.RoundingMode
-import java.text.DecimalFormat
-import java.text.NumberFormat
import java.time.Instant
import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter
-import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit
-import java.util.*
-
-private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
-private val currencyFormat = NumberFormat.getCurrencyInstance(Locale.US)
-private val decimalFormat = DecimalFormat.getNumberInstance(Locale.US).apply {
- with(this as DecimalFormat) {
- decimalFormatSymbols = decimalFormatSymbols.apply {
- currencySymbol = ""
- isGroupingUsed = false
- }
- }
-}
fun Application.transactionWebRoutes(
budgetService: BudgetService,
@@ -62,6 +48,7 @@ fun Application.transactionWebRoutes(
get {
val user = userService.user(requireSession().userId)
val budgetId = call.parameters.getOrFail("budgetId")
+ val categoryId = call.request.queryParameters["categoryId"]
val transaction = TransactionResponse(
id = "",
title = "",
@@ -70,7 +57,7 @@ fun Application.transactionWebRoutes(
budgetId = budgetId,
expense = true,
date = Instant.now().toHtmlInputString(),
- categoryId = null,
+ categoryId = categoryId,
createdBy = user.id
)
call.respond(
@@ -78,9 +65,14 @@ fun Application.transactionWebRoutes(
"transaction-form.mustache",
TransactionFormPage(
transaction = transaction,
- amountLabel = currencyFormat.format(0L),
+ amountLabel = 0L.toDecimalString(),
budget = budgetService.budget(budgetId = budgetId, userId = user.id),
- categoryOptions = categoryOptions(transaction, categoryService, budgetId, user),
+ categoryOptions = categoryOptions(
+ transaction = transaction,
+ categoryService = categoryService,
+ budgetId = budgetId,
+ user = user
+ ),
user = user
)
)
@@ -313,7 +305,7 @@ private suspend fun categoryOptions(
private fun Parameters.toTransactionRequest() = TransactionRequest(
title = get("title"),
description = get("description"),
- amount = decimalFormat.parse(get("amount"))?.toDouble()?.toBigDecimal()?.times(BigDecimal(100))?.toLong() ?: 0L,
+ amount = getAmount(),
expense = false,
date = get("date"),
categoryId = get("categoryId"),
@@ -322,7 +314,3 @@ private fun Parameters.toTransactionRequest() = TransactionRequest(
private fun Instant.toHtmlInputString() = truncatedTo(ChronoUnit.MINUTES).toString().substringBefore(":00Z")
-private fun Long?.toDecimalString(): String {
- if (this == null) return ""
- return decimalFormat.format(toBigDecimal().divide(BigDecimal(100), 2, RoundingMode.HALF_UP))
-}
\ No newline at end of file
diff --git a/web/src/main/resources/static/style.css b/web/src/main/resources/static/style.css
index f6dd800..be659f7 100644
--- a/web/src/main/resources/static/style.css
+++ b/web/src/main/resources/static/style.css
@@ -44,7 +44,7 @@ h1, h2, h3, h4, h5, h6, p, ul {
padding: 0;
}
-h1, h2, h3, h4, h5, h6, p, summary {
+h2, h3, h4, h5, h6, p, summary {
padding: 0.5rem;
}
@@ -186,6 +186,12 @@ input:disabled {
color: var(--color-on-background);
}
+.inline-input {
+ display: flex;
+ flex-direction: row;
+ justify-content: start;
+}
+
a {
color: var(--color-accent);
}
diff --git a/web/src/main/resources/templates/category-details.mustache b/web/src/main/resources/templates/category-details.mustache
index a382870..02f6f7c 100644
--- a/web/src/main/resources/templates/category-details.mustache
+++ b/web/src/main/resources/templates/category-details.mustache
@@ -6,11 +6,18 @@
{{title}}
-
-
- +
-
+
{{category.category.description}}
diff --git a/web/src/main/resources/templates/category-form.mustache b/web/src/main/resources/templates/category-form.mustache new file mode 100644 index 0000000..b3d534b --- /dev/null +++ b/web/src/main/resources/templates/category-form.mustache @@ -0,0 +1,31 @@ +{{> partials/head }} +{{error}}
+ {{/error}} + +