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:
- Register a monthly product menu
- Enter the number of products sold each day
- View sales rankings and charts
- 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
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) %}
In 2026, the options are:
2026
2025
2024
2023
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)
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
It is not:
14 + 17 = 31
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
14to17.
However, the original button text was:
💾 Save
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>
I also changed the success message.
{% if success %}
<div class="success-msg">
✅ Today's sales quantities were updated!
</div>
{% endif %}
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:
- Enter an interim quantity of 14
- Sell more products later
- Open the entry page again
- 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
I also placed the current quantity directly into the input field.
Input field: 14
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
}
The returned value looks like this:
{
1: 14,
2: 8,
3: 0,
}
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
),
)
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 %}
When no registered data exists, the second argument of get() displays zero.
{{ today_sales.get(product.id, 0) }}
The same value is used as the input field’s initial value.
value="{{ today_sales.get(product.id, 0) }}"
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>
The CSS inherits the normal text color.
.input-page .registered-quantity {
color: inherit;
font-size: 13px;
font-weight: bold;
line-height: 1.4;
}
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;
}
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;
}
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
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>
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>
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>
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
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;
}
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;
}
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;
}
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;
}
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
Each HTML file loads the stylesheet as follows:
<link
rel="stylesheet"
href="{{ url_for('static', filename='style.css') }}"
>
I also added page-specific classes to each body element.
<body class="input-page">
<body class="dashboard-page">
<body class="success-page">
The CSS selectors begin with the page class.
.input-page .btn-submit {
/* Applied only to the daily entry page */
}
.dashboard-page .btn-submit {
/* Applied only to the dashboard */
}
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;
}
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)