CodingNic

Final Capstone Project

Dashboard Layout

Final Capstone Project 65 min read

Dashboard Layout

Dashboard Layout

In the previous lesson you built the login screen.

Now you will build the dashboard layout that the user sees after logging in.

The dashboard will not be a simple stack of labels. It will use a real layout:

  • a header bar at the top
  • a row of colored stat cards showing summary numbers
  • two side-by-side panels for tasks and expenses

You are still not connecting backend logic — you are building the structure first.


Goal of This Lesson

By the end of this lesson, you will:

  • understand grid() for two-column layouts
  • build a header bar
  • build colored stat cards
  • build reusable panel containers
  • render the full dashboard structure

Final Layout

text
+---------------------------------------------------+
| Header (app title, welcome, logout)               |
+---------------------------------------------------+
| [Total Tasks] [Completed] [Expenses]   <- cards   |
+---------------------------------------------------+
| Tasks panel          | Expenses panel             |
|                      |                            |
|                      |                            |
+---------------------------------------------------+

Step 1: Add More Theme Colors

The stat cards each use a soft accent color.

Open app_ui.py and extend THEME:

python
THEME = {
    # ... existing entries ...
    "accent_blue":     "#dbeafe",
    "accent_green":    "#dcfce7",
    "accent_amber":    "#fef3c7",
    "accent_blue_fg":  "#1e40af",
    "accent_green_fg": "#166534",
    "accent_amber_fg": "#92400e",
}

Also add two more font constants:

python
FONT_STAT     = ("Segoe UI", 22, "bold")
FONT_STAT_LBL = ("Segoe UI", 10)

Step 2: Build a Stat Card Helper

A stat card shows a big number and a small label, with an accent background.

python
def _build_stat_card(parent, label, value, accent_bg, accent_fg):
    card = tk.Frame(
        parent, bg=accent_bg,
        highlightbackground=THEME["border"], highlightthickness=1,
    )
    inner = tk.Frame(card, bg=accent_bg)
    inner.pack(fill="both", expand=True, padx=20, pady=18)

    value_lbl = tk.Label(
        inner, text=value, font=FONT_STAT,
        bg=accent_bg, fg=accent_fg, anchor="w",
    )
    value_lbl.pack(anchor="w")

    tk.Label(
        inner, text=label, font=FONT_STAT_LBL,
        bg=accent_bg, fg=accent_fg, anchor="w",
    ).pack(anchor="w", pady=(2, 0))

    return {"card": card, "value": value_lbl}

The function returns a dictionary with two things:

  • card — the outer frame, so you can place it on the dashboard
  • value — the big number label, so you can update it later

This pattern lets you change the stat value later without rebuilding the whole card.


Step 3: Build a Panel Helper

Panels are white containers with a title and a body area.

python
def _build_panel(parent, title):
    card = tk.Frame(
        parent, bg=THEME["card"],
        highlightbackground=THEME["border"], highlightthickness=1,
    )
    title_bar = tk.Frame(card, bg=THEME["card"])
    title_bar.pack(fill="x", padx=16, pady=(14, 6))
    tk.Label(
        title_bar, text=title, font=FONT_HEAD,
        bg=THEME["card"], fg=THEME["text"],
    ).pack(side="left")

    body = tk.Frame(card, bg=THEME["card"])
    body.pack(fill="both", expand=True, padx=16, pady=(0, 14))

    return {"card": card, "body": body}

Returns:

  • card — the outer panel
  • body — the inner frame where task/expense widgets will go

Step 4: Build the Dashboard

python
def show_dashboard(window, user_id, username=""):
    _clear(window)

    root = tk.Frame(window, bg=THEME["bg"])
    root.pack(fill="both", expand=True)

    # ---- Header bar ---------------------------------------------------
    header = tk.Frame(
        root, bg=THEME["card"], height=64,
        highlightbackground=THEME["border"], highlightthickness=1,
    )
    header.pack(fill="x")
    header.pack_propagate(False)

    tk.Label(
        header, text="📊  Productivity App", font=FONT_HEAD,
        bg=THEME["card"], fg=THEME["text"],
    ).pack(side="left", padx=20)

    welcome_text = f"Welcome, {username}" if username else "Welcome"
    tk.Label(
        header, text=welcome_text, font=FONT_LABEL,
        bg=THEME["card"], fg=THEME["muted"],
    ).pack(side="left", padx=10)

    _styled_button(
        header, "Logout", lambda: show_login(window), kind="ghost"
    ).pack(side="right", padx=20, pady=14)

    # ---- Content area -------------------------------------------------
    content = tk.Frame(root, bg=THEME["bg"])
    content.pack(fill="both", expand=True, padx=20, pady=20)

    # ---- Stat cards row -----------------------------------------------
    stats_row = tk.Frame(content, bg=THEME["bg"])
    stats_row.pack(fill="x", pady=(0, 16))

    # Make all three columns equal width.
    for i in range(3):
        stats_row.columnconfigure(i, weight=1, uniform="stats")

    stat_total   = _build_stat_card(stats_row, "Total Tasks",     "0",
                                    THEME["accent_blue"],  THEME["accent_blue_fg"])
    stat_done    = _build_stat_card(stats_row, "Completed Tasks", "0",
                                    THEME["accent_green"], THEME["accent_green_fg"])
    stat_expense = _build_stat_card(stats_row, "Total Expenses",  "$0",
                                    THEME["accent_amber"], THEME["accent_amber_fg"])

    stat_total["card"].grid(  row=0, column=0, sticky="nsew", padx=(0, 8))
    stat_done["card"].grid(   row=0, column=1, sticky="nsew", padx=8)
    stat_expense["card"].grid(row=0, column=2, sticky="nsew", padx=(8, 0))

    # ---- Two-column panels --------------------------------------------
    panels = tk.Frame(content, bg=THEME["bg"])
    panels.pack(fill="both", expand=True)
    panels.columnconfigure(0, weight=1, uniform="panel")
    panels.columnconfigure(1, weight=1, uniform="panel")
    panels.rowconfigure(0, weight=1)

    task_panel = _build_panel(panels, title="Tasks")
    task_panel["card"].grid(row=0, column=0, sticky="nsew", padx=(0, 8))

    expense_panel = _build_panel(panels, title="Expenses")
    expense_panel["card"].grid(row=0, column=1, sticky="nsew", padx=(8, 0))

    return root

How grid() Works Here

Two key ideas:

1. columnconfigure(..., weight=1, uniform="...")

This says: “all columns with this uniform group should share width equally.”

Without it, columns would size based on their content.

2. sticky="nsew"

This makes a widget stretch to fill its grid cell in north, south, east, west directions.

Without it, widgets shrink to their natural size.


Step 5: Test the Dashboard

For now, your dashboard is empty — the panels have titles but no content.

Update main.py temporarily so you can see the dashboard directly:

python
from app.services.database import create_tables
from app.ui.app_ui import create_app, show_dashboard

def main():
    create_tables()

    window = create_app()
    show_dashboard(window, user_id=1, username="demo")

    window.mainloop()

if __name__ == "__main__":
    main()

Run it. You should see:

  • white header bar at the top
  • three colored stat cards (blue, green, amber)
  • two empty white panels labeled “Tasks” and “Expenses”

Step 6: Restore main.py

Once you’ve confirmed the layout works, restore main.py:

python
from app.services.database import create_tables
from app.ui.app_ui import create_app, show_login

def main():
    create_tables()

    window = create_app()
    show_login(window)

    window.mainloop()

if __name__ == "__main__":
    main()

What You Built

You now have:

  • a full dashboard layout
  • reusable helpers (_build_stat_card, _build_panel)
  • a header bar with logout button
  • a structure ready for tasks and expenses

The panels are empty for now. You will fill them in the next lesson.


Why This Step Matters

This is the difference between:

text
hobby project   →   real product layout

You used:

  • pack() for top-to-bottom flow
  • grid() for rows and columns
  • place() (in the previous lesson) for centering
  • helper functions for reuse

These are the same techniques real Tkinter apps use.


Important Notes

  • Do not write logic inside layout functions
  • Keep helpers (_build_stat_card, _build_panel) small and reusable
  • Always use sticky="nsew" when you want a widget to fill its grid cell
  • Always call columnconfigure(..., weight=1) when you want columns to share space
  • Keep the theme dictionary as the only place colors live

Current Project Structure

text
app/
    services/
        database.py
        user_service.py
        task_service.py
        expense_service.py
        dashboard_service.py
    ui/
        app_ui.py

Summary

You built the full dashboard layout with a header bar, colored stat cards, and two-column panels using grid(). Your application now has a real, modern UI structure ready to be filled with functionality.