DEV Community

Cover image for “Save” and “Update” Are Not the Same — How a Former Okonomiyaki Chef Removed Confusing UI from a Flask App
tosane932
tosane932

Posted on • Originally published at qiita.com

“Save” and “Update” Are Not the Same — How a Former Okonomiyaki Chef Removed Confusing UI from a Flask App

Introduction

Hello from Japan! 🇯🇵

This article was originally published in Japanese on Qiita and has been translated and adapted for DEV Community.

I have now completed a total of 143 hours of Python study.

About 11 years ago, I worked as a Hiroshima-style okonomiyaki chef.

I performed live cooking and sales demonstrations at major department stores and event spaces across Japan, including Iwate, the Kanto region, Kansai, and Kyushu.

My responsibilities included:

  • Hiring temporary sales staff
  • Estimating how many products we could sell
  • Ordering ingredients without creating excessive waste
  • Avoiding losses during limited event periods
  • Thinking about profit and cost from a business-owner’s perspective

I had to move quickly while making decisions about both daily operations and profitability.

You can also find my profile and projects here:

https://github.com/tosane932/tosane932

I am currently developing a bakery sales management application with Flask.

The application mainly supports the following workflow:

  1. Register a monthly product menu
  2. Enter the number of products sold each day
  3. View sales rankings and charts
  4. Receive improvement suggestions through the Gemini API

The features were already working.

However, when I used the application while imagining that I was the manager of a real bakery, I noticed several small but important points of confusion.

This work was not simply a color redesign.

I reviewed the wording, colors, navigation, and input-state displays from the following perspective:

Would a store manager hesitate when looking at this screen?

Could the wording cause an incorrect operation or misunderstanding?

Is the next action naturally clear?

When I worked at department-store events, I cooked products myself, planned sales pitches, explained the products to customers, and completed each sale.

I combined that experience with what I had previously learned about web design.

My goal was to improve the application so that it could be used comfortably by people of different ages and levels of technical experience.

Design goal

The purpose of this improvement was not to make the application more decorative.

The goal was to remove causes of confusion before the store manager encountered them.


The Application

The application I improved is a bakery sales management system built with Flask.

The main technologies are:

  • Python
  • Flask
  • PostgreSQL
  • SQLAlchemy
  • HTML
  • CSS
  • JavaScript
  • Gemini API
  • Docker
  • Render

The application has four main screens:

  • Top page
  • Product-registration completion page
  • Daily sales-entry page
  • Sales analysis dashboard

Problems I Noticed Before the Improvement

1. The Top Page Displayed a Future Year

The top page is used to register the product menu for a particular month.

However, when I opened the year-selection menu in 2026, it also displayed 2027.

2027
2026
Enter fullscreen mode Exit fullscreen mode

At first, I wondered whether I had accidentally registered products for 2027 in the database.

After checking the application, I discovered that the database was not the cause.

The year 2027 had been written directly into the HTML as an option.

The product-menu registration page is mainly used to create the current operating year’s menu.

Therefore, I changed the top page so that only the current year can be selected.

The sales dashboard has a different purpose.

It is used to review previous results, so I changed it to display the current year and past years only.

{% for y in range(current_year, current_year - 4, -1) %}
Enter fullscreen mode Exit fullscreen mode

In 2026, the options are:

2026
2025
2024
2023
Enter fullscreen mode Exit fullscreen mode

Future sales results do not exist yet, so there is no reason to show future years on the analysis screen.

This was a small inconsistency, but it could make a user wonder:

What will happen if I select 2027?

Instead of waiting for an error to occur, I removed the contradiction before the user had to think about it.


2. The Button Said “Save” Even Though the Operation Was an Update

On the daily sales-entry page, the user enters how many units of each product were sold that day.

The backend was already implemented like this:

existing = DailySales.query.filter_by(
    product_id=int(product_id),
    date=sale_date,
).first()

if existing:
    existing.quantity = qty_int
else:
    sale = DailySales(
        product_id=int(product_id),
        date=sale_date,
        quantity=qty_int,
    )
    db.session.add(sale)
Enter fullscreen mode Exit fullscreen mode

When data for the same product and date already exists, the registered quantity is replaced with the new value.

For example, when 14 units are already registered and the user enters 17, the result is:

14 → 17
Enter fullscreen mode Exit fullscreen mode

It is not:

14 + 17 = 31
Enter fullscreen mode Exit fullscreen mode

Important

Daily sales entry does not use an additive model.

When 14 units are already registered and the user enters 17, the stored value changes from 14 to 17.

However, the original button text was:

💾 Save
Enter fullscreen mode Exit fullscreen mode

That wording did not tell the user whether the operation would:

  • Add a new quantity
  • Replace the current quantity with the entered value

Even when the backend behavior is correct, a mismatch between the actual operation and the UI wording can cause misunderstanding.

I changed the button to:

<button type="submit" class="btn-submit">
    💾 Update Today's Sales Quantities
</button>
Enter fullscreen mode Exit fullscreen mode

I also changed the success message.

{% if success %}
    <div class="success-msg">
        ✅ Today's sales quantities were updated!
    </div>
{% endif %}
Enter fullscreen mode Exit fullscreen mode

By using the word “update” instead of “save,” the result of the operation became clearer.

UI warning

Correct internal processing is not enough.

When the visible wording does not match the actual behavior, users may misunderstand the operation.

I replaced “Save” with “Update Today's Sales Quantities” to make the overwrite behavior explicit.


3. Users Could Forget Whether They Had Already Entered Today’s Sales

The daily sales-entry page can be opened multiple times in one day.

A store may not enter all data only once after closing.

For example, the workflow might be:

  1. Enter an interim quantity of 14
  2. Sell more products later
  3. Open the entry page again
  4. Correct the final quantity to 17

Before the improvement, the page did not show how many units were already stored in the database.

This could make a manager wonder:

Did I already enter today’s sales for this product?

Was 14 already registered?

If I enter 17 now, will it become 31?

I therefore displayed the current registered quantity below each product name.

Premium White Bread
🟢 Registered today: 14
Enter fullscreen mode Exit fullscreen mode

I also placed the current quantity directly into the input field.

Input field: 14
Enter fullscreen mode Exit fullscreen mode

When the user changes it to 17 and submits the form, the database value is replaced.


Retrieving Today’s Registered Quantities

I added a helper function that converts the selected date’s sales records into a dictionary containing product IDs and quantities.

def _get_today_sales_map(target_date):
    """Return registered sales quantities by product ID."""
    sales = DailySales.query.filter_by(
        date=target_date
    ).all()

    return {
        sale.product_id: sale.quantity
        for sale in sales
    }
Enter fullscreen mode Exit fullscreen mode

The returned value looks like this:

{
    1: 14,
    2: 8,
    3: 0,
}
Enter fullscreen mode Exit fullscreen mode

The dictionary key is the product ID.

The value is the registered sales quantity for that date.


Passing the Current Values to the Template

The daily sales-entry route passes the current sales data to the template for both GET and POST requests.

@app.route("/input", methods=["GET", "POST"])
def input_sales():
    today = datetime.date.today()

    if request.method == "POST":
        date_str = request.form.get("date")
        sale_date = datetime.date.fromisoformat(
            date_str
        )

        product_ids = request.form.getlist(
            "product_id"
        )

        quantities = request.form.getlist(
            "quantity"
        )

        logger.info(
            "Sales data submission received "
            f"for date: {sale_date}"
        )

        for product_id, quantity in zip(
            product_ids,
            quantities,
        ):
            if quantity.strip() == "":
                continue

            try:
                qty_int = int(float(quantity))
            except ValueError:
                logger.warning(
                    "Invalid quantity format skipped: "
                    f"product_id={product_id}, "
                    f"quantity={quantity}"
                )
                continue

            if qty_int < 0:
                logger.warning(
                    "Negative quantity skipped: "
                    f"product_id={product_id}, "
                    f"quantity={qty_int}"
                )
                continue

            existing = DailySales.query.filter_by(
                product_id=int(product_id),
                date=sale_date,
            ).first()

            if existing:
                existing.quantity = qty_int
            else:
                sale = DailySales(
                    product_id=int(product_id),
                    date=sale_date,
                    quantity=qty_int,
                )

                db.session.add(sale)

        db.session.commit()

        logger.info(
            "Sales data successfully committed "
            f"for date: {sale_date}"
        )

        return render_template(
            "input.html",
            success=True,
            products=_get_current_products(),
            today=today,
            today_sales=_get_today_sales_map(
                today
            ),
        )

    return render_template(
        "input.html",
        products=_get_current_products(),
        today=today,
        today_sales=_get_today_sales_map(
            today
        ),
    )
Enter fullscreen mode Exit fullscreen mode

After the POST request, the route retrieves the latest values from the database again.

As a result, the page displayed after submission shows the newly updated quantities.


Displaying Current Values in the Template

I changed the product-entry section in input.html as follows:

{% for product in products %}
    <div class="product-row">
        <input
            type="hidden"
            name="product_id"
            value="{{ product.id }}"
        >

        <div class="product-info">
            <span class="product-name">
                {{ product.name }}
            </span>

            <span class="registered-quantity">
                🟢 Registered today:
                {{ today_sales.get(product.id, 0) }}
            </span>
        </div>

        <span class="product-price">
            ¥{{ product.price }}
        </span>

        <input
            type="number"
            name="quantity"
            class="qty-input"
            min="0"
            value="{{ today_sales.get(product.id, 0) }}"
            required
        >
    </div>
{% endfor %}
Enter fullscreen mode Exit fullscreen mode

When no registered data exists, the second argument of get() displays zero.

{{ today_sales.get(product.id, 0) }}
Enter fullscreen mode Exit fullscreen mode

The same value is used as the input field’s initial value.

value="{{ today_sales.get(product.id, 0) }}"
Enter fullscreen mode Exit fullscreen mode

Now the user can understand the current registration status immediately after opening the page.


Why I Did Not Make the Text Green

At first, I considered displaying the entire “Registered today” message in green.

However, changing the text color could reduce readability depending on the background or the user’s display environment.

I kept the existing dark text color and used only a green circle as a visual marker.

<span class="registered-quantity">
    🟢 Registered today:
    {{ today_sales.get(product.id, 0) }}
</span>
Enter fullscreen mode Exit fullscreen mode

The CSS inherits the normal text color.

.input-page .registered-quantity {
    color: inherit;
    font-size: 13px;
    font-weight: bold;
    line-height: 1.4;
}
Enter fullscreen mode Exit fullscreen mode

I used three signals together:

  • A green circle
  • The words “Registered today”
  • Bold text

A user who has difficulty distinguishing colors can still understand the meaning from the text.

Accessibility note

Colors are used as a supporting cue.

I do not rely on color alone. Icons, specific wording, and button shapes are also used so that the interface is less dependent on color perception or technical experience.


Improving the Spacing Between Products

After adding the registered quantity, each row contained:

  • Product name
  • Current quantity
  • Price
  • Input field

The products began to look visually crowded.

I added spacing and a divider between each product.

.input-page .product-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;

    margin-bottom: 18px;
    padding-bottom: 14px;

    border-bottom: 1px solid #f3ecde;
}
Enter fullscreen mode Exit fullscreen mode

The product name and registered quantity are displayed vertically as one information group.

.input-page .product-info {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 4px;

    min-width: 0;
}
Enter fullscreen mode Exit fullscreen mode

This made the boundary between products easier to recognize.


Improving Navigation Between Screens

Before the improvement, it was not always obvious how to move to the next task.

I added navigation based on the actual store workflow.

Register the product menu
        ↓
Enter daily sales
        ↓
Review sales analysis and rankings
Enter fullscreen mode Exit fullscreen mode

I added a dashboard button to the daily sales-entry page.

<a
    href="{{ url_for('dashboard') }}"
    class="btn-submit btn-dashboard-link"
>
    📊 View the Store Health Report
    (Sales Analysis and Rankings)
</a>
Enter fullscreen mode Exit fullscreen mode

I also replaced the simple text link to the top page with a button-shaped link.

<a
    href="{{ url_for('index') }}"
    class="input-home-link"
>
    🍞 Return to the Top Page
</a>
Enter fullscreen mode Exit fullscreen mode

The dashboard received links to the daily entry page and the top page.

<div class="dashboard-actions">
    <a
        href="{{ url_for('input_sales') }}"
        class="dashboard-action dashboard-input-link"
    >
        📝 Enter Daily Sales
    </a>

    <a
        href="{{ url_for('index') }}"
        class="dashboard-action dashboard-home-link"
    >
        🍞 Return to the Top Page
    </a>
</div>
Enter fullscreen mode Exit fullscreen mode

The next business operation is now easier to reach from every screen.


Assigning a Meaning to Each Button Color

Instead of choosing unrelated colors for each page, I assigned colors according to the type of action.

Green
→ Product-menu registration

Honey
→ Daily sales entry and updates

Strawberry red
→ Sales analysis and rankings

Light cream
→ AI questions

White
→ Return to the top page
Enter fullscreen mode Exit fullscreen mode

Pistachio Green for Product Registration

I used a pistachio- or leaf-inspired green for the product-menu registration button.

.btn-menu-register {
    background-color: #7fa45a;
    color: #ffffff;
}

.btn-menu-register:hover {
    background-color: #688c47;
}
Enter fullscreen mode Exit fullscreen mode

Honey Color for Daily Sales Entry

I used a warm color associated with bread and honey for daily sales entry.

.input-page .btn-submit {
    background-color: #c9a063;
    color: #ffffff;
}
Enter fullscreen mode Exit fullscreen mode

Strawberry Red for Sales Analysis

I used a strawberry-jam red for the sales analysis button.

.input-page .btn-dashboard-link {
    background-color: #e85d5d;
    color: #ffffff;
}

.input-page .btn-dashboard-link:hover {
    background-color: #cf4646;
}
Enter fullscreen mode Exit fullscreen mode

This preserves the bakery theme while clearly separating analysis from daily input.

Light Cream for AI Questions

The “Ask for Today’s Message” button and the dashboard’s “Ask for Detailed Advice” button both call AI features.

I therefore gave them the same light cream color.

.dashboard-page .btn-ai-advice {
    background-color: #f7eed6;
    color: #8b5a1a;
}

.dashboard-page .btn-ai-advice:hover {
    background-color: #ebdcb9;
}
Enter fullscreen mode Exit fullscreen mode

Using a consistent color makes it easier to recognize the same type of action across different pages.

However, the interface does not depend on color alone.

Icons and specific button text are also used.


Moving CSS Out of the HTML Files

As part of this improvement, I moved CSS from the individual HTML files into static/style.css.

sales_data_app/
├── app.py
├── templates/
│   ├── index.html
│   ├── input.html
│   ├── dashboard.html
│   └── success.html
└── static/
    └── style.css
Enter fullscreen mode Exit fullscreen mode

Each HTML file loads the stylesheet as follows:

<link
    rel="stylesheet"
    href="{{ url_for('static', filename='style.css') }}"
>
Enter fullscreen mode Exit fullscreen mode

I also added page-specific classes to each body element.

<body class="input-page">
Enter fullscreen mode Exit fullscreen mode
<body class="dashboard-page">
Enter fullscreen mode Exit fullscreen mode
<body class="success-page">
Enter fullscreen mode Exit fullscreen mode

The CSS selectors begin with the page class.

.input-page .btn-submit {
    /* Applied only to the daily entry page */
}
Enter fullscreen mode Exit fullscreen mode
.dashboard-page .btn-submit {
    /* Applied only to the dashboard */
}
Enter fullscreen mode Exit fullscreen mode

Even when two pages use the same .btn-submit class name, this reduces the risk of one page’s styles affecting another page unexpectedly.


A CSS Side Effect That Actually Occurred

While reorganizing the CSS, the dashboard’s filter button lost its rounded corners and gained a black border.

The cause was the interaction between the shared .btn-submit style and the dashboard-specific style.

I explicitly defined the required appearance for the dashboard.

.dashboard-page .btn-submit {
    width: auto;
    margin-top: 0;
    padding: 10px 15px;

    background-color: #8b5a1a;
    color: #ffffff;
    border: none;
    border-radius: 8px;
    outline: none;

    box-shadow: none;
    cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

Centralizing CSS reduces duplicated code, but it also increases the number of elements that one change can affect.

I learned that shared styles and page-specific styles need clearly defined responsibilities.

CSS warning

Shared CSS can reduce duplication, but changes may also affect other pages using the same class.

Common styles and page-specific styles should be separated carefully, and every screen should be reviewed after a change.


“Easy to Use for Everyone” as the Main Decision Rule

An important concept in my application development is:

An application that is easy to use for people of all ages and backgrounds.

I used that idea as the decision rule for this improvement.

The changes included:

  • Replacing the ambiguous word “Save” with the specific word “Update”
  • Displaying the currently registered quantity
  • Placing the current value in the input field
  • Removing confusing options such as future years
  • Assigning consistent colors based on action type
  • Using icons and text in addition to color
  • Replacing small text links with clearly clickable buttons
  • Adding navigation to the next business task on each page

I prioritized reducing thought and hesitation over making the interface visually flashy.


How My Okonomiyaki Sales Experience Helped the UI Improvement

I previously performed live okonomiyaki cooking and sales demonstrations at department stores.

I made the product myself, prepared the sales pitch, explained it to customers, and comple

Top comments (0)