Giai đoạn 3 · Tháng 3 · B17–B24

Từ code chạy trong Terminal đến Mini-CRM nằm gọn trên điện thoại.

Tám buổi học không tạo thêm một demo xa lạ. Bạn nâng cấp chính Tool Báo Giá Lát Sàn, Hệ Thống Tiến Độ và dữ liệu khách hàng thành giao diện Web trực quan, có báo cáo, bộ lọc và đường ống lưu trữ thật.

Nguyên tắc của chương trình: thực dụng, dùng ngôn ngữ xây dựng, code dưới 15 dòng khi có thể — mỗi buổi phải tạo ra một mảnh dùng được trong công việc.

Mockup giao diện Streamlit báo giá lát sàn trên màn hình điện thoại, có chiều dài, chiều rộng, đơn giá USD và nút tính báo giá

Mục tiêu giai đoạn

Biến logic thành một mặt tiền dễ dùng.

8 buổi · 4 tuần

Người giám sát cần thấy gì?

Không phải một màn hình đầy thuật ngữ. Họ cần nhập số đo nhanh, nhận báo giá rõ, nhìn thấy công trình đang ở bước nào và biết bản ghi khách hàng đã đi vào đâu.

  • 01Nhập liệu có chủ đích. Ô nhập chiều dài, chiều rộng và đơn giá thay cho việc sửa code bằng tay.
  • 02Kết quả có phản hồi. Nút bấm kích hoạt logic Python khi người dùng thực sự yêu cầu tính toán.
  • 03Báo cáo để quyết định. Table, biểu đồ và bộ lọc đưa danh sách dự án ra khỏi file rời rạc.
  • 04Dữ liệu không thất lạc. SQLite nhận bản ghi từ Web để Mini-CRM có thể báo cho chủ thầu.
B17–B19Mặt tiền Showroom
B20–B22Bảng Báo Cáo Visual
B23–B24Ghép nối hệ thống

Đường đi của một bản ghi tốt: input → Python logic → output → lưu trữ.

Hãy kiểm tra từng mũi tên, thay vì chỉ nhìn thấy một giao diện đẹp.

Bản vẽ thi công

Lộ trình 8 buổi: mỗi tuần dựng một lớp.

Từ widget nhỏ đến hệ thống có dữ liệu. Các sản phẩm của Giai đoạn 1 và Giai đoạn 2 là vật liệu đầu vào, không bị bỏ lại phía sau.

BUỔI 17

Khởi tạo giao diện Streamlit (st.title & st.number_input)

Đưa ứng dụng ra khỏi Terminal đen ngòm để bước lên trình duyệt Web. Sử dụng Streamlit để tạo ô nhập chiều dài, chiều rộng, đơn giá USD trực quan mà không cần viết mã HTML/CSS phức tạp.

Sản phẩm tại chỗ

Trang Web đầu tiên chạy cục bộ tại địa chỉ localhost:8501 có các ô nhập liệu số đo phòng.

📋 Giáo án Giảng viên & Thực hành B17 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Thầu phụ không biết mở Terminal gõ lệnh ⏱️ 25' Live Code: pip install streamlit, streamlit run, st.title, st.number_input ⏱️ 20' Thực hành: Tạo form nhập số đo công trình ⏱️ 5' Nghiệm thu & Khắc phục lỗi cổng mạng

Mục tiêu: Học viên cài đặt thư viện Streamlit, chạy lệnh streamlit run app.py, nắm được các widget nhập liệu: st.text_input, st.number_input(min_value=0.0, step=0.5).

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Chạy sai lệnh: Học viên hay quen tay gõ python app.py thay vì streamlit run app.py khiến web không bật lên. Giảng viên nhắc: "Streamlit cần lệnh streamlit run để khởi động máy chủ web".
  • Nguyên lý Re-run: Giải thích khi người dùng thay đổi giá trị trong ô nhập, toàn bộ script Python sẽ tự động chạy lại từ trên xuống dưới.

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Tạo file app_b17.py. Dùng st.title("Dự Toán Lát Sàn Remodeling") và tạo 3 ô nhập số st.number_input cho: Chiều dài (ft), Chiều rộng (ft), Đơn giá (USD/sqft).
Bài 2 (Thực chiến nâng cao): Thiết lập giá trị mặc định cho ô chiều dài là 20.0, chiều rộng là 15.0 và giá sàn tối thiểu không được âm (min_value=0.0).
Python 3 (Streamlit) · Source Code chuẩn B17app_b17.py
# BUỔI 17: GIAO DIỆN WEB STREAMLIT ĐẦU TIÊN
import streamlit as st

# Cấu hình tiêu đề trang web
st.set_page_config(page_title="Báo Giá Remodeling", page_icon="🏗️")

st.title("🏗️ Công Cụ Báo Giá Sàn Công Trường")
st.write("Nhập thông số kích thước phòng để tính toán khối lượng thi công.")

# Các ô nhập liệu tương tác
ten_phong = st.text_input("Tên khu vực thi công:", value="Phòng khách Master")
chieu_dai = st.number_input("Chiều dài phòng (feet):", min_value=0.0, value=20.0, step=0.5)
chieu_rong = st.number_input("Chiều rộng phòng (feet):", min_value=0.0, value=15.0, step=0.5)
don_gia = st.number_input("Đơn giá lát sàn (USD/sqft):", min_value=0.0, value=4.5, step=0.25)

dien_tich = chieu_dai * chieu_rong
st.info(f"📐 Diện tích dự kiến tính được: **{dien_tich:,.1f} sqft**")
# Cách chạy ứng dụng: Terminal: streamlit run app_b17.py Trình duyệt tự động mở: http://localhost:8501
BUỔI 18

Bố cục cột (st.columns) & Nút bấm kích hoạt (st.button)

Bố trí các ô nhập liệu thành 2 hoặc 3 cột song song giúp màn hình gọn gàng, vừa vặn điện thoại. Sử dụng nút bấm st.button("Tính Báo Giá") để kiểm soát thời điểm xuất kết quả, không để màn hình nhảy số liên tục.

Sản phẩm tại chỗ

Giao diện form báo giá chuyên nghiệp có bố cục chia cột và vùng phản hồi rõ ràng.

📋 Giáo án Giảng viên & Thực hành B18 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Giao diện dài ngoằng khó nhìn trên điện thoại ⏱️ 25' Live Code: col1, col2 = st.columns(2), if st.button() ⏱️ 20' Thực hành: Bố cục form báo giá đa cột ⏱️ 5' Nghiệm thu & Giải đáp

Mục tiêu: Dùng st.columns([1, 1]) để phân chia layout, đặt nút bấm tính toán và dùng st.success(), st.warning() để phản hồi người dùng.

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Khối lệnh trong if button: Giải thích khi bấm nút thì if st.button(): trả về True trong một chu kỳ re-run đó. Mọi logic tính toán hiển thị bảng giá phải nằm thụt lề bên trong if.

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Chia form thành 2 cột: Cột trái nhập Chiều dài & Chiều rộng; Cột phải nhập Đơn giá & Phí vận chuyển.
Bài 2 (Thực chiến nâng cao): Đặt nút bấm "Tính tổng chi phí". Khi bấm nút, hiển thị hộp màu xanh lá st.success() in số tiền; nếu diện tích bằng 0, hiển thị cảnh báo màu vàng st.warning().
Python 3 (Streamlit) · Source Code chuẩn B18app_b18.py
# BUỔI 18: BỐ CỤC CHIA CỘT VÀ NÚT TÍNH TOÁN
import streamlit as st

st.subheader("📐 Nhập Kích Thước & Đơn Giá Thi Công")

# Chia thành 2 cột cân đối
col_trai, col_phai = st.columns(2)

with col_trai:
    chieu_dai = st.number_input("Chiều dài (feet):", value=22.0, min_value=0.0)
    chieu_rong = st.number_input("Chiều rộng (feet):", value=16.0, min_value=0.0)

with col_phai:
    don_gia = st.number_input("Đơn giá vật tư (USD/sqft):", value=5.0, min_value=0.0)
    phi_van_chuyen = st.number_input("Phí giao hàng (USD):", value=80.0, min_value=0.0)

# Nút bấm hành động
if st.button("🚀 TÍNH TOÁN BÁO GIÁ NGAY", use_container_width=True):
    dien_tich = chieu_dai * chieu_rong
    if dien_tich > 0:
        tong_tien = (dien_tich * don_gia) + phi_van_chuyen
        st.success(f"🎉 Tổng dự toán công trình: **${tong_tien:,.2f} USD**")
        st.caption(f"Chi tiết: {dien_tich:,.1f} sqft × ${don_gia:.2f} + ${phi_van_chuyen:.2f} vận chuyển.")
    else:
        st.warning("⚠️ Chiều dài hoặc chiều rộng phải lớn hơn 0!")
# Trải nghiệm giao diện: [Cột 1: Dài / Rộng] | [Cột 2: Đơn giá / Vận chuyển] [ Nút bấm full-width: TÍNH TOÁN BÁO GIÁ NGAY ] Box xanh hiển thị: $1,840.00 USD
BUỔI 19

Tích hợp logic Báo Giá Lát Sàn (st.metric & Checkbox)

Đưa toàn bộ hàm tính toán nghiệp vụ từ Giai đoạn 1 lên giao diện Web: Kiểm tra giảm giá 5% cho dự án > 1,000 sqft, bổ sung checkbox "Nhà cũ (Phụ thu 10% dọn xà bần)" và hiển thị thẻ KPI st.metric.

Sản phẩm tại chỗ

Web App Báo Giá Lát Sàn hoàn chỉnh chạy mượt mà trên trình duyệt điện thoại.

📋 Giáo án Giảng viên & Thực hành B19 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Báo giá trực tiếp trước mặt chủ nhà ⏱️ 25' Live Code: st.checkbox, st.metric, tích hợp hàm Python ⏱️ 20' Thực hành: Hoàn thiện Web Báo Giá ⏱️ 5' Nghiệm thu & Đánh giá

Mục tiêu: Tái sử dụng hàm logic Python sạch sẽ, kết hợp st.checkbox và hiển thị kết quả bằng thẻ chỉ số st.metric("Tổng tiền", "$...", delta="-5% Ưu đãi").

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Định dạng tham số delta của st.metric: Dạy học viên cách dùng delta để làm nổi bật mức giảm giá màu xanh lá hoặc phụ phí màu đỏ, tạo sự tin cậy và minh bạch với khách hàng.

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Viết web app có checkbox "Hiện trạng công trình: Nhà cũ cần đục dọn sàn cũ (+10%)". Tính toán và hiển thị tổng tiền qua st.metric.
Bài 2 (Thực chiến nâng cao): Tạo 3 thẻ chỉ số cạnh nhau bằng 3 cột: Cột 1 (Diện tích sqft), Cột 2 (Đơn giá áp dụng), Cột 3 (Tổng thanh toán).
Python 3 (Streamlit) · Source Code chuẩn B19app_b19_bao_gia_web.py
# BUỔI 19: WEB BÁO GIÁ LÁT SÀN HOÀN CHỈNH
import streamlit as st

st.title("🏡 Dự Toán Lát Sàn Nhà Cải Tạo")

col1, col2 = st.columns(2)
with col1:
    sqft = st.number_input("Tổng diện tích lát sàn (sqft):", value=1200.0, step=50.0)
with col2:
    don_gia_goc = st.number_input("Đơn giá niêm yết (USD/sqft):", value=5.0, step=0.25)

is_nha_cu = st.checkbox("🏚️ Công trình là nhà cũ (cộng 10% phí dọn xà bần)")

chi_phi_goc = sqft * don_gia_goc
rate_discount = 0.05 if sqft >= 1000 else 0.0
tien_giam = chi_phi_goc * rate_discount
phi_don_rac = (chi_phi_goc * 0.10) if is_nha_cu else 0.0
tong_cong = chi_phi_goc - tien_giam + phi_don_rac

st.divider()

# Hiển thị 3 chỉ số chính bằng st.metric
m1, m2, m3 = st.columns(3)
m1.metric("Diện tích", f"{sqft:,.0f} sqft")
m2.metric("Chiết khấu", f"-${tien_giam:,.2f}", delta=f"-{rate_discount*100:.0f}%" if rate_discount > 0 else "0%")
m3.metric("TỔNG DỰ TOÁN", f"${tong_cong:,.2f}", delta="+10% Dọn rác" if is_nha_cu else "Chuẩn")

st.caption("Báo giá dự toán có giá trị trong 30 ngày kể từ ngày khảo sát.")
# Output giao diện hiển thị: [ Thẻ 1: 1,200 sqft ] | [ Thẻ 2: -$300.00 (-5%) ] | [ Thẻ 3: $6,300.00 (+10% Dọn rác) ]
BUỔI 20

Hiển thị bảng dữ liệu (st.dataframe & st.table)

Thay vì đọc từng dòng log trong Terminal, ta đưa danh sách 50 công trình lên web bằng st.dataframe. Cho phép người giám sát tìm kiếm, nhấp chuột sắp xếp cột và co giãn bảng linh hoạt.

Sản phẩm tại chỗ

Bảng theo dõi dự án tương tác có thanh cuộn và ô tìm kiếm tích hợp.

📋 Giáo án Giảng viên & Thực hành B20 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Chủ thầu cần xem tổng quan 20 công trình đang chạy ⏱️ 25' Live Code: pd.read_csv kết hợp st.dataframe(use_container_width=True) ⏱️ 20' Thực hành: Bảng theo dõi tiến độ thi công ⏱️ 5' Nghiệm thu & Giải đáp

Mục tiêu: Kết nối Pandas với Streamlit, hiển thị DataFrame lên giao diện web với st.dataframe(..., use_container_width=True).

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Tràn ngang trên mobile: Trên màn hình điện thoại hẹp, chỉ nên hiển thị 4-5 cột quan trọng nhất (ID, Tên khách, SĐT, Trạng thái) thay vì để bảng có 15 cột làm người dùng phải vuốt ngang mệt mỏi.

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Viết script đọc file khach_hang.csv và hiển thị bảng bằng st.dataframe. Đếm và in tổng số bản ghi bằng st.caption.
Bài 2 (Thực chiến nâng cao): Tô màu nổi bật các công trình có trạng thái "Chưa gọi" bằng định dạng Pandas Style nếu có thể.
Python 3 (Streamlit) · Source Code chuẩn B20app_b20_bang_du_an.py
# BUỔI 20: HIỂN THỊ BẢNG THEO DÕI CÔNG TRÌNH
import streamlit as st
import pandas as pd

st.title("📋 Sổ Theo Dõi Tiến Độ Công Trình")

try:
    df = pd.read_csv("khach_hang.csv", dtype={"Zip Code": str})
    
    st.write(f"Hiện có **{len(df)}** hồ sơ dự án trong hệ thống.")
    
    # Hiển thị bảng dữ liệu tương tác full-width
    st.dataframe(
        df[["ID", "Tên khách", "SĐT", "Zip Code", "Trạng thái liên hệ"]],
        use_container_width=True,
        hide_index=True
    )
except FileNotFoundError:
    st.error("Không tìm thấy file khach_hang.csv! Hãy kiểm tra thư mục.")
# Bảng web Streamlit: Có tính năng search tên khách, click header để sort, kéo dãn độ rộng cột.
BUỔI 21

Trực quan hóa dữ liệu bằng biểu đồ (st.bar_chart)

Biến các con số khô khan thành biểu đồ cột trực quan: Thống kê số lượng công trình theo từng trạng thái (Khảo sát, Thi công, Bàn giao) để chỉ huy trưởng nhìn lướt 3 giây là nắm trọn khối lượng công việc.

Sản phẩm tại chỗ

Báo cáo trực quan Dashboard gồm bảng dữ liệu và biểu đồ phân bổ trạng thái.

📋 Giáo án Giảng viên & Thực hành B21 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Báo cáo giao ban đầu tuần với ban giám đốc ⏱️ 25' Live Code: df['Trạng thái'].value_counts() & st.bar_chart() ⏱️ 20' Thực hành: Vẽ biểu đồ phân bố công trình ⏱️ 5' Nghiệm thu & Thảo luận

Mục tiêu: Dùng hàm .value_counts() gom nhóm dữ liệu và hiển thị biểu đồ thanh bằng st.bar_chart().

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Định dạng đầu vào của st.bar_chart: Streamlit nhận Series hoặc DataFrame có index là nhãn trục hoành. Giảng viên hướng dẫn: counts = df['Cột'].value_counts() sau đó truyền thẳng vào st.bar_chart(counts).

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Vẽ biểu đồ cột hiển thị số lượng công trình theo từng Zip Code khu vực.
Bài 2 (Thực chiến nâng cao): Bố cục màn hình: Cột trái hiện biểu đồ cột, Cột phải hiện bảng số liệu chi tiết.
Python 3 (Streamlit) · Source Code chuẩn B21app_b21_bieu_do.py
# BUỔI 21: TRỰC QUAN HÓA TIẾN ĐỘ BẰNG BIỂU ĐỒ
import streamlit as st
import pandas as pd

st.title("📊 Báo Cáo Tiến Độ Công Trình Tuần Này")

df = pd.read_csv("khach_hang.csv")

# Thống kê số lượng theo trạng thái
thong_ke_trang_thai = df["Trạng thái liên hệ"].value_counts()

c1, c2 = st.columns([1.2, 0.8])

with c1:
    st.subheader("Biểu đồ phân bổ tiến độ")
    st.bar_chart(thong_ke_trang_thai, color="#2D625D")

with c2:
    st.subheader("Số liệu chi tiết")
    st.dataframe(thong_ke_trang_thai.rename("Số lượng"), use_container_width=True)
# Biểu đồ hiển thị sắc nét: Cột "Chưa gọi": 14 | Cột "Hẹn khảo sát": 8 | Cột "Đang thi công": 18
BUỔI 22

Lọc dữ liệu trên Web bằng Sidebar (st.sidebar)

Đưa bộ lọc tìm kiếm ra thanh trượt bên hông (Sidebar) với st.sidebar.selectbox và st.sidebar.multiselect. Giúp giám sát lọc nhanh các công trình đang kẹt tiến độ hoặc thuộc tổ thợ cụ thể.

Sản phẩm tại chỗ

Dashboard Quản Lý có bộ lọc sidebar thông minh, tự động cập nhật bảng số liệu.

📋 Giáo án Giảng viên & Thực hành B22 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Tìm gấp công trình đang gặp trục trặc tại Zip 77001 ⏱️ 25' Live Code: st.sidebar.selectbox(), lọc DataFrame động ⏱️ 20' Thực hành: Dựng sidebar bộ lọc quản trị ⏱️ 5' Nghiệm thu & Giải đáp

Mục tiêu: Thành thạo đưa widget vào thanh bên st.sidebar, lọc dữ liệu động theo lựa chọn người dùng và hiển thị kết quả tương ứng.

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Tùy chọn 'Tất cả': Nhớ thêm lựa chọn "Tất cả" vào danh sách selectbox để khi người dùng không muốn lọc thì bảng vẫn hiển thị toàn bộ hồ sơ.

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Tạo thanh bên Sidebar có hộp chọn st.sidebar.selectbox("Chọn Trạng Thái", ["Tất cả", "Chưa gọi", "Đã liên hệ", "Hẹn khảo sát"]). Lọc và cập nhật bảng chính theo lựa chọn.
Bài 2 (Thực chiến nâng cao): Thêm bộ lọc thứ 2 trên sidebar: Lọc theo Zip Code. Kết hợp đồng thời cả 2 bộ lọc.
Python 3 (Streamlit) · Source Code chuẩn B22app_b22_sidebar_filter.py
# BUỔI 22: BỘ LỌC ĐỘNG BẰNG THANH BÊN SIDEBAR
import streamlit as st
import pandas as pd

st.title("🔎 Tra Cứu Dự Án Theo Bộ Lọc")

df = pd.read_csv("khach_hang.csv", dtype={"Zip Code": str})

# 1. Bộ lọc trên Sidebar
st.sidebar.header("⚙️ BỘ LỌC TÌM KIẾM")

danh_sach_tt = ["Tất cả"] + list(df["Trạng thái liên hệ"].dropna().unique())
chon_tt = st.sidebar.selectbox("Trạng thái công trình:", danh_sach_tt)

danh_sach_zip = ["Tất cả"] + list(df["Zip Code"].dropna().unique())
chon_zip = st.sidebar.selectbox("Khu vực (Zip Code):", danh_sach_zip)

# 2. Xử lý lọc dữ liệu
df_hien_thi = df.copy()
if chon_tt != "Tất cả":
    df_hien_thi = df_hien_thi[df_hien_thi["Trạng thái liên hệ"] == chon_tt]
if chon_zip != "Tất cả":
    df_hien_thi = df_hien_thi[df_hien_thi["Zip Code"] == chon_zip]

# 3. Hiển thị kết quả lọc
st.write(f"Kết quả: Tìm thấy **{len(df_hien_thi)}** công trình phù hợp.")
st.dataframe(df_hien_thi, use_container_width=True, hide_index=True)
# Trải nghiệm: Bên trái: Menu chọn Trạng thái & Zip Code Bên phải: Bảng số liệu tự động co lại đúng danh sách cần tìm.
BUỔI 23

Kết nối Web Streamlit với SQLite

Tạo cầu nối giữa giao diện Web và Cơ sở dữ liệu: Dựng Form nhập khách hàng mới trên Web, khi bấm "Lưu Khách Hàng", Python sẽ thực hiện câu lệnh INSERT INTO trực tiếp vào file cong_trinh.db.

Sản phẩm tại chỗ

Trang Web có chức năng thêm khách và lưu vĩnh viễn vào SQLite mà không cần mở tool quản lý database.

📋 Giáo án Giảng viên & Thực hành B23 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tình huống: Giám sát đi gặp khách ngoài công trường nhập data ⏱️ 25' Live Code: st.form(), kết nối sqlite3 trong Streamlit ⏱️ 20' Thực hành: Form nhập khách hàng lưu DB ⏱️ 5' Nghiệm thu & Giải đáp

Mục tiêu: Dùng st.form() gom nhóm input tránh re-run thừa, viết hàm lưu dữ liệu vào SQLite và dùng st.experimental_rerun() cập nhật lại danh sách ngay sau khi lưu.

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Lợi ích của st.form: Nếu không dùng form, mỗi lần học viên gõ 1 ký tự tên khách hàng là trang web lại load lại 1 lần! Giảng viên dặn: "Dùng st.form như điền xong toàn bộ phiếu nghiệm thu rồi mới ký tên nộp một thể".

🛠️ Bài tập thực hành tại lớp

Bài 1 (Cơ bản): Tạo form thêm khách hàng gồm Họ tên, Số điện thoại, Zip Code. Khi bấm nút submit, lưu vào bảng KhachHang trong cong_trinh.db.
Bài 2 (Thực chiến nâng cao): Đọc và hiển thị danh sách khách hàng từ SQLite ra bảng bên dưới để kiểm chứng dữ liệu vừa nhập đã vào database hay chưa.
Python 3 (Streamlit + SQLite) · Source Code B23app_b23_form_sqlite.py
# BUỔI 23: FORM NHẬP KHÁCH LƯU TRỰC TIẾP VÀO SQLITE
import streamlit as st
import sqlite3
import pandas as pd

st.title("📝 Thêm Hồ Sơ Khách Hàng Mới")

# 1. Form nhập liệu an toàn
with st.form("form_them_khach"):
    ho_ten = st.text_input("Họ và tên chủ nhà:")
    sdt = st.text_input("Số điện thoại liên hệ:")
    zip_code = st.text_input("Mã bưu điện (Zip Code):", value="77001")
    nut_luu = st.form_submit_button("💾 LƯU VÀO CƠ SỞ DỮ LIỆU")

if nut_luu:
    if ho_ten.strip() != "":
        with sqlite3.connect("cong_trinh.db") as conn:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO KhachHang (ho_ten, sdt, zip_code) VALUES (?, ?, ?)",
                (ho_ten.strip(), sdt.strip(), zip_code.strip())
            )
            conn.commit()
        st.success(f"🎉 Đã lưu thành công khách hàng: **{ho_ten}**!")
    else:
        st.error("Vui lòng nhập họ và tên khách hàng!")

# 2. Đọc lại danh sách khách hàng từ SQLite
st.divider()
st.subheader("Danh sách khách hàng hiện có trong Database")
with sqlite3.connect("cong_trinh.db") as conn:
    df_db = pd.read_sql_query("SELECT id, ho_ten, sdt, zip_code, ngay_tao FROM KhachHang ORDER BY id DESC", conn)
st.dataframe(df_db, use_container_width=True, hide_index=True)
# Output kiểm tra: Điền thông tin → Bấm Lưu → Bản ghi lập tức xuất hiện ở dòng đầu tiên của bảng bên dưới!
BUỔI 24

Đóng gói hoàn thiện Mini-CRM Remodeling

Ghép nối toàn bộ chuỗi mắt xích thành một ứng dụng Mini-CRM hoàn chỉnh với hệ thống Tab điều hướng: Tab 1 (Báo Giá Nhanh) → Tab 2 (Quản Lý Dự Án & Tiến Độ) → Tab 3 (Thêm Khách Hàng Mới). Sẵn sàng sử dụng trên điện thoại công trường.

Sản phẩm hoàn tất Giai đoạn 3

Hệ thống Mini-CRM độc lập, có thể mở bằng iPhone/Android, lưu trữ SQLite bền vững.

📋 Giáo án Giảng viên & Thực hành B24 (60 phút)Instructor Kit

🎯 Mục tiêu bài dạy & Kế hoạch 60 phút

⏱️ 10' Tổng kết mục tiêu Mini-CRM phục vụ thực tế ⏱️ 25' Live Code: st.tabs(["Báo Giá", "Tiến Độ", "Thêm Khách"]) ⏱️ 20' Học viên chạy thử Mini-CRM toàn diện ⏱️ 5' Nghiệm thu mốc Hoàn Thiện Tháng 3

Mục tiêu: Học viên làm chủ cấu trúc chia tab st.tabs(), tích hợp toàn bộ logic tính báo giá và luồng SQLite vào một file duy nhất mini_crm_app.py chạy mượt mà.

💡 Bẫy lỗi thường gặp & Mẹo sư phạm

  • Kiểm thử trên di động qua mạng nội bộ: Hướng dẫn học viên lấy địa chỉ Network URL: http://192.168.x.x:8501 và mở trực tiếp trên điện thoại cùng mạng Wifi để thấy ngay thành quả!

🛠️ Đồ án tốt nghiệp Giai đoạn 3 tại lớp

Yêu cầu đồ án: Hoàn thiện file mini_crm_app.py gồm 3 Tab: (1) Tính toán báo giá sàn có giảm giá và phí dọn rác, (2) Bảng tiến độ và biểu đồ cột công trình, (3) Form thêm khách hàng lưu trực tiếp vào SQLite.
Python 3 (Streamlit + SQLite) · Source Code Mini-CRM B24mini_crm_app.py
# BUỔI 24: ĐỒ ÁN GIAI ĐOẠN 3 - MINI-CRM HOÀN CHỈNH CHO CÔNG TRƯỜNG
import streamlit as st
import sqlite3
import pandas as pd

st.set_page_config(page_title="Mini-CRM Công Trường", page_icon="🏗️", layout="wide")

st.title("🏗️ MINI-CRM GIÁM SÁT CÔNG TRƯỜNG")

# Điều hướng bằng 3 Tab chức năng
tab_bao_gia, tab_tien_do, tab_them_khach = st.tabs([
    "💰 Báo Giá Lát Sàn", 
    "📊 Theo Dõi Tiến Độ", 
    "➕ Thêm Khách Hàng"
])

# --- TAB 1: BÁO GIÁ NHANH ---
with tab_bao_gia:
    st.subheader("Công Cụ Báo Giá Nhanh")
    c1, c2 = st.columns(2)
    with c1:
        dai = st.number_input("Chiều dài phòng (ft):", value=20.0, min_value=0.0)
        rong = st.number_input("Chiều rộng phòng (ft):", value=15.0, min_value=0.0)
    with c2:
        gia = st.number_input("Đơn giá thi công (USD/sqft):", value=5.0, min_value=0.0)
        nha_cu = st.checkbox("Nhà cũ (+10% phí dọn xà bần)")
    
    sqft = dai * rong
    giam = 0.05 if sqft >= 1000 else 0.0
    tien_goc = sqft * gia
    tong = (tien_goc * (1 - giam)) + ((tien_goc * 0.1) if nha_cu else 0.0)
    
    st.metric("TỔNG DỰ TOÁN BÁO GIÁ", f"${tong:,.2f} USD", delta=f"{sqft:,.0f} sqft")

# --- TAB 2: TIẾN ĐỘ DỰ ÁN ---
with tab_tien_do:
    st.subheader("Tiến Độ Thi Công Các Công Trình")
    with sqlite3.connect("cong_trinh.db") as conn:
        df_khach = pd.read_sql_query("SELECT * FROM KhachHang", conn)
    
    col_chart, col_data = st.columns([1, 2])
    with col_chart:
        st.write("**Phân bố theo Zip Code:**")
        if not df_khach.empty and "zip_code" in df_khach.columns:
            st.bar_chart(df_khach["zip_code"].value_counts())
    with col_data:
        st.write("**Danh sách hồ sơ:**")
        st.dataframe(df_khach, use_container_width=True, hide_index=True)

# --- TAB 3: THÊM KHÁCH HÀNG MỚI ---
with tab_them_khach:
    st.subheader("Nhập Hồ Sơ Khách Hàng Mới")
    with st.form("form_crm"):
        ten = st.text_input("Tên chủ nhà:")
        sdt = st.text_input("Số điện thoại:")
        zip_cd = st.text_input("Zip Code:", value="77001")
        if st.form_submit_button("LƯU HỒ SƠ"):
            if ten.strip():
                with sqlite3.connect("cong_trinh.db") as conn:
                    conn.execute("INSERT INTO KhachHang (ho_ten, sdt, zip_code) VALUES (?, ?, ?)", (ten, sdt, zip_cd))
                    conn.commit()
                st.success(f"✅ Đã thêm hồ sơ khách hàng: {ten}")
            else:
                st.error("Vui lòng điền tên khách hàng!")
# Ứng dụng Mini-CRM sẵn sàng đứng lớp: Chạy lệnh: streamlit run mini_crm_app.py Đầy đủ chức năng Báo giá + Thống kê + Lưu trữ SQLite!

Worked example 01 · B17–B19

Cùng một logic, hai mặt tiền.

Trong Terminal, người học nhập số đo rồi chương trình tính Sqft và nhân đơn giá USD. Khi chuyển sang Streamlit, logic tính toán không bị viết lại thành một “mẹo giao diện”: nó được đặt sau sự kiện của nút Tính báo giá.

InputChiều dài · chiều rộng · đơn giá
LogicSqft × đơn giá USD
OutputBáo giá đọc được trên điện thoại
Giao diện Web báo giá lát sàn trên điện thoại
Mặt tiền mới, logic cũ đã được kiểm chứng ở Giai đoạn 1.
Input → logic → output

Web Báo Giá Lát Sàn

Ô nhập nhận số đo; nút bấm là thời điểm chạy logic; vùng kết quả cho người dùng thấy diện tích và báo giá. Kiểm tra dữ liệu nhập trước khi tính để tránh kết quả vô nghĩa.

length = st.number_input("Chiều dài")
width = st.number_input("Chiều rộng")
price = st.number_input("Đơn giá USD")
if st.button("Tính báo giá"):
    sqft = length * width
    st.write(sqft * price)
Hiển thị → lọc → quyết định

Dashboard Quản Lý

Table dành cho danh sách. Biểu đồ dành cho phân bố trạng thái. Bộ lọc giúp chủ thầu tập trung vào các công trình đang kẹt tiến độ mà không làm thay đổi dữ liệu gốc.

st.dataframe(projects)
st.bar_chart(status_counts)
status = st.selectbox(
    "Lọc trạng thái", statuses
)
st.dataframe(projects[
    projects["status"] == status
])
Web → SQLite

Thêm một bản ghi có trách nhiệm

Ở B23–B24, form không chỉ hiện thông báo thành công. Dữ liệu phải đi qua thao tác ghi vào SQLite, sau đó có thể đọc lại để danh sách phản ánh đúng trạng thái.

customer = {
    "name": name,
    "phone": phone,
    "status": "Khảo sát"
}
save_to_sqlite(customer)
st.success("Đã lưu khách hàng")
Đèn cảnh báo

Đọc dashboard như người quản lý

Vùng đánh dấu công trình kẹt tiến độ là tín hiệu hành động: chọn điều kiện lọc, xem bản ghi liên quan, rồi cập nhật trạng thái qua luồng dữ liệu đã thiết kế.

st.metric("Đang kẹt",
          stalled_count)
st.warning(
    "Cần theo dõi hôm nay"
)

Worked example 02 · B23–B24

Mini-CRM là một đường ống, không phải một nút “Lưu”.

Khi khách để lại thông tin, bản ghi cần đi qua đủ ba điểm: biểu mẫu Web nhận dữ liệu, SQLite lưu dữ liệu, dashboard đọc lại và báo cho chủ thầu.

01 · Nhận

Biểu mẫu Web

Tên khách, thông tin liên hệ và trạng thái ban đầu “Khảo sát”.

02 · Lưu

SQLite

Bản ghi đi vào bảng KhachHang hoặc DuAn thay vì nằm tạm trong màn hình.

03 · Báo

Mini-CRM

Danh sách, bộ lọc và tín hiệu kẹt tiến độ giúp chủ thầu biết việc cần làm.

Sơ đồ luồng Mini-CRM từ biểu mẫu Web qua SQLite đến danh sách và trạng thái dự án
Đường ống dữ liệu: nhập trên Web → lưu SQLite → hiển thị trạng thái.

Bản xem trước sản phẩm

Dashboard phải làm lộ ra việc cần làm.

Ở B20–B22, bảng và biểu đồ bổ sung cho nhau: bảng giữ chi tiết từng công trình; biểu đồ cho thấy phân bố trạng thái; bộ lọc biến một màn hình tổng quát thành danh sách hành động.

Đèn cảnh báo không thay thế phán đoán của người giám sát. Nó chỉ rút ngắn khoảng cách từ dữ liệu đến câu hỏi: công trình nào đang kẹt, và bản ghi nào cần được cập nhật?

Dashboard quản lý công trình có bảng dự án, biểu đồ trạng thái và vùng đánh dấu công trình đang kẹt tiến độ
Bảng để tra cứu, biểu đồ để nhìn xu hướng, bộ lọc để tập trung.
Mốc Nghiệm Thu Giai Đoạn 3 Hoàn Tất

Sẵn Sàng Cất Nóc & Đưa App Lên Internet?

Bạn đã hoàn thành ứng dụng Mini-CRM chạy trên máy cục bộ với đầy đủ Báo giá, Tiến độ và SQLite. Ở Giai đoạn 4, chúng ta sẽ đưa ứng dụng lên Streamlit Cloud / VPS và xây dựng Landing Page tiếp thị hoàn chỉnh.

Sang Giai đoạn 4: Deploy & Marketing →
Zalo 📞