Add transaction form page

This commit is contained in:
William Brawner 2024-04-07 21:39:41 -06:00
parent 5a9b988c63
commit 64e7cb9d52
3 changed files with 88 additions and 17 deletions

View file

@ -22,6 +22,7 @@ data class TransactionDetailsPage(
data class TransactionFormPage( data class TransactionFormPage(
val transaction: TransactionResponse, val transaction: TransactionResponse,
val amountLabel: String,
val budget: BudgetResponse, val budget: BudgetResponse,
val incomeCategories: List<CategoryResponse>, val incomeCategories: List<CategoryResponse>,
val expenseCategories: List<CategoryResponse>, val expenseCategories: List<CategoryResponse>,
@ -29,8 +30,8 @@ data class TransactionFormPage(
override val error: String? = null override val error: String? = null
) : AuthenticatedPage { ) : AuthenticatedPage {
override val title: String = if (transaction.id.isBlank()) { override val title: String = if (transaction.id.isBlank()) {
"New Category" "New Transaction"
} else { } else {
"Edit Category" "Edit Transaction"
} }
} }

View file

@ -20,15 +20,25 @@ import io.ktor.server.request.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.ktor.server.util.* import io.ktor.server.util.*
import java.math.BigDecimal
import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import java.time.Instant import java.time.Instant
import java.time.ZoneOffset.UTC import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
private val numberFormat = NumberFormat.getCurrencyInstance(Locale.US) private val currencyFormat = NumberFormat.getCurrencyInstance(Locale.US)
private val decimalFormat = DecimalFormat.getNumberInstance(Locale.US).apply {
with(this as DecimalFormat) {
decimalFormatSymbols = decimalFormatSymbols.apply {
currencySymbol = ""
}
}
}
fun Application.transactionWebRoutes( fun Application.transactionWebRoutes(
budgetService: BudgetService, budgetService: BudgetService,
@ -60,10 +70,11 @@ fun Application.transactionWebRoutes(
amount = 0, amount = 0,
budgetId = budgetId, budgetId = budgetId,
expense = true, expense = true,
date = dateTimeFormatter.format(Instant.now()), date = Instant.now().toHtmlInputString(),
categoryId = null, categoryId = null,
createdBy = user.id createdBy = user.id
), ),
amountLabel = currencyFormat.format(0L),
budget = budgetService.budget(budgetId = budgetId, userId = user.id), budget = budgetService.budget(budgetId = budgetId, userId = user.id),
incomeCategories = categoryService.categories( incomeCategories = categoryService.categories(
budgetIds = listOf(budgetId), budgetIds = listOf(budgetId),
@ -77,7 +88,7 @@ fun Application.transactionWebRoutes(
expense = true, expense = true,
archived = false archived = false
), ),
user user = user
) )
) )
) )
@ -85,11 +96,21 @@ fun Application.transactionWebRoutes(
post { post {
val user = userService.user(requireSession().userId) val user = userService.user(requireSession().userId)
val budgetId = call.parameters.getOrFail("budgetId") val urlBudgetId = call.parameters.getOrFail("budgetId")
try { try {
val request = call.receiveParameters().toTransactionRequest() val request = call.receiveParameters().toTransactionRequest()
.run {
copy(
date = date + 'Z',
expense = categoryService.category(
categoryId = requireNotNull(categoryId),
userId = user.id
).expense,
budgetId = urlBudgetId
)
}
val transaction = transactionService.save(request, user.id) val transaction = transactionService.save(request, user.id)
call.respondRedirect("/budgets/${transaction.budgetId}/categories/${transaction.id}") call.respondRedirect("/budgets/${transaction.budgetId}/transactions/${transaction.id}")
} catch (e: HttpException) { } catch (e: HttpException) {
call.respond( call.respond(
status = e.statusCode, status = e.statusCode,
@ -100,27 +121,29 @@ fun Application.transactionWebRoutes(
id = "", id = "",
title = call.parameters["title"], title = call.parameters["title"],
description = call.parameters["description"], description = call.parameters["description"],
amount = call.parameters["amount"]?.toLongOrNull(), amount = 0L,
budgetId = budgetId, budgetId = urlBudgetId,
expense = call.parameters["expense"]?.toBoolean() ?: true, expense = call.parameters["expense"]?.toBoolean() ?: true,
date = call.parameters["date"].orEmpty(), date = call.parameters["date"].orEmpty(),
categoryId = call.parameters["categoryId"], categoryId = call.parameters["categoryId"],
createdBy = user.id createdBy = user.id
), ),
budget = budgetService.budget(budgetId = budgetId, userId = user.id), amountLabel = call.parameters["amount"].orEmpty(),
budget = budgetService.budget(budgetId = urlBudgetId, userId = user.id),
incomeCategories = categoryService.categories( incomeCategories = categoryService.categories(
budgetIds = listOf(budgetId), budgetIds = listOf(urlBudgetId),
userId = user.id, userId = user.id,
expense = false, expense = false,
archived = false archived = false
), ),
expenseCategories = categoryService.categories( expenseCategories = categoryService.categories(
budgetIds = listOf(budgetId), budgetIds = listOf(urlBudgetId),
userId = user.id, userId = user.id,
expense = true, expense = true,
archived = false archived = false
), ),
user user = user,
error = e.message
) )
) )
) )
@ -159,7 +182,7 @@ fun Application.transactionWebRoutes(
category = category, category = category,
budget = budget, budget = budget,
budgets = budgets, budgets = budgets,
amountLabel = transaction.amount?.toCurrencyString(numberFormat).orEmpty(), amountLabel = transaction.amount?.toCurrencyString(currencyFormat).orEmpty(),
dateLabel = dateLabel, dateLabel = dateLabel,
createdBy = userService.user(transaction.createdBy), createdBy = userService.user(transaction.createdBy),
user = user user = user
@ -173,6 +196,10 @@ fun Application.transactionWebRoutes(
) )
} }
} }
post {
}
} }
} }
} }
@ -182,9 +209,11 @@ fun Application.transactionWebRoutes(
private fun Parameters.toTransactionRequest() = TransactionRequest( private fun Parameters.toTransactionRequest() = TransactionRequest(
title = get("title"), title = get("title"),
description = get("description"), description = get("description"),
amount = get("amount")?.toLongOrNull(), amount = decimalFormat.parse(get("amount"))?.toDouble()?.toBigDecimal()?.times(BigDecimal(100))?.toLong() ?: 0L,
expense = get("expense")?.toBoolean(), expense = false,
date = get("date"), date = get("date"),
categoryId = get("categoryId"), categoryId = get("categoryId"),
budgetId = get("budgetId"), budgetId = get("budgetId"),
) )
private fun Instant.toHtmlInputString() = truncatedTo(ChronoUnit.SECONDS).toString().substringBefore('Z')

View file

@ -0,0 +1,41 @@
{{> partials/head }}
<div id="app">
<main>
<h1>{{title}}</h1>
<div class="center">
{{#error }}
<p class="error">{{error}}</p>
{{/error}}
<form method="post">
<label for="title">Name</label>
<input id="title" type="text" name="title" value="{{ transaction.title }}" required/>
<label for="description">Description</label>
<textarea id="description" name="description">{{ transaction.description }}</textarea>
<label for="date">Date</label>
<input id="date" type="datetime-local" name="date" value="{{ transaction.date }}" required/>
<label for="amount">Amount</label>
<input id="amount" type="number" name="amount" value="{{ amountLabel }}" required/>
<label for="categoryId">Category</label>
<select id="categoryId" name="categoryId" required>
<option selected disabled>Select a category</option>
<option disabled>Income</option>
{{#incomeCategories}}
<option value="{{id}}">{{title}}</option>
{{/incomeCategories}}
<option disabled>Expense</option>
{{#expenseCategories}}
<option value="{{id}}">{{title}}</option>
{{/expenseCategories}}
</select>
<input id="submit" type="submit" class="button button-primary" value="Save"/>
</form>
</div>
</main>
</div>
<script type="text/javascript">
const dateField = document.querySelector('#date')
let localDateTime = dateField.valueAsDate
const localTimeMs = localDateTime.setMinutes(localDateTime.getMinutes() - localDateTime.getTimezoneOffset())
dateField.value = new Date(localTimeMs).toISOString().slice(0, -1)
</script>
{{>partials/foot}}