Check Splitting Calculator
This is a simple Shiny app that calculates your portion of a check for when you eat out with friends.
#| standalone: true
#| viewerHeight: 600
library(shiny)
library(bslib)
ui <- fluidPage(
theme = bs_theme(
version = 5,
bootswatch = "flatly",
primary = "#3B7A57"
),
titlePanel("Check Splitting Calculator"),
sidebarLayout(
sidebarPanel(
numericInput("price0",
label = "Item Price ($)",
value = 10, min = 0, step = 0.01),
div(id = "item-list"),
div(
style = "display: flex; gap: 8px; margin-bottom: 15px;",
actionButton(inputId = "add", label = "+ Add item", class = "btn-primary btn-sm"),
actionButton(inputId = "rmv", label = "- Remove item", class = "btn-outline-secondary btn-sm")
),
hr(),
numericInput("tax",
label = "Tax (%)",
value = 6, min = 0, step = 0.1),
numericInput("tip",
label = "Tip (%)",
value = 15, min = 0, step = 1)
),
mainPanel(
uiOutput("summary_card")
)
)
)
server <- function(input, output, session) {
add_rm_counter <- reactiveVal(0)
observeEvent(input$add, {
add_rm_counter(add_rm_counter() + 1)
insertUI(
selector = "#item-list",
where = "beforeEnd",
ui = div(id = paste0("inserttext", add_rm_counter()),
numericInput(paste0("price", add_rm_counter()),
label = paste("Item", add_rm_counter() + 1, "Price ($)"),
value = 0, min = 0, step = 0.01))
)
})
observeEvent(input$rmv, {
req(add_rm_counter() > 0)
removeUI(selector = paste0("#inserttext", add_rm_counter()))
add_rm_counter(add_rm_counter() - 1)
})
total_sum <- reactive({
sum(sapply(0:add_rm_counter(), function(i) {
val <- input[[paste0("price", i)]]
if (is.null(val) || is.na(val)) 0 else val
}))
})
tax_amt <- reactive(total_sum() * (as.numeric(input$tax) / 100))
tip_amt <- reactive(total_sum() * (as.numeric(input$tip) / 100))
total_price <- reactive(total_sum() + tax_amt() + tip_amt())
fmt <- function(x) sprintf("$%.2f", x)
output$summary_card <- renderUI({
if (is.na(total_price())) {
div(class = "alert alert-warning", "Make sure all fields are numeric.")
} else {
div(
class = "card shadow-sm",
style = "max-width: 320px; padding: 20px;",
div(style = "display:flex; justify-content:space-between;",
span("Subtotal"), span(fmt(total_sum()))),
div(style = "display:flex; justify-content:space-between;",
span(paste0("Tax (", input$tax, "%)")), span(fmt(tax_amt()))),
div(style = "display:flex; justify-content:space-between;",
span(paste0("Tip (", input$tip, "%)")), span(fmt(tip_amt()))),
hr(),
div(style = "display:flex; justify-content:space-between; font-weight:700; font-size:1.2rem;",
span("You owe"), span(fmt(total_price())))
)
}
})
}
shinyApp(ui = ui, server = server)