# customer_app.py — 고객 관리 CRUD 앱 (최적화 적용)
import streamlit as st
import psycopg2
import psycopg2.extras
import psycopg2.pool
import pandas as pd
st.set_page_config(page_title="고객 관리", page_icon="👥", layout="wide")
@st.cache_resource
def get_pool():
"""앱 수명 동안 커넥션 풀을 한 번만 생성합니다."""
return psycopg2.pool.ThreadedConnectionPool(
minconn=2, maxconn=10,
host=st.secrets["lakebase"]["host"],
port=5432,
dbname=st.secrets["lakebase"]["dbname"],
user=st.secrets["lakebase"]["user"],
password=st.secrets["lakebase"]["password"],
sslmode="require"
)
def get_conn():
return get_pool().getconn()
def release_conn(conn):
get_pool().putconn(conn)
@st.cache_data(ttl=300)
def get_tier_list():
"""등급 목록 — 5분 캐시."""
conn = get_conn()
try:
return pd.read_sql("SELECT DISTINCT tier FROM customers ORDER BY 1", conn)["tier"].tolist()
finally:
release_conn(conn)
st.title("👥 고객 관리 시스템")
tab_list, tab_create, tab_edit = st.tabs(["📋 고객 목록", "➕ 신규 등록", "✏️ 정보 수정"])
# --- 조회 (Read) ---
with tab_list:
search = st.text_input("🔍 고객 검색 (이름 또는 이메일)")
conn = get_conn()
try:
if search:
df = pd.read_sql(
"SELECT id, name, email, tier, created_at FROM customers "
"WHERE name ILIKE %s OR email ILIKE %s ORDER BY created_at DESC",
conn, params=(f"%{search}%", f"%{search}%")
)
else:
df = pd.read_sql(
"SELECT id, name, email, tier, created_at FROM customers "
"ORDER BY created_at DESC LIMIT 100",
conn
)
finally:
release_conn(conn)
st.dataframe(df, use_container_width=True)
st.caption(f"총 {len(df)}명 표시 중")
# --- 생성 (Create) ---
with tab_create:
with st.form("create_customer"):
name = st.text_input("이름")
email = st.text_input("이메일")
tier = st.selectbox("등급", get_tier_list())
if st.form_submit_button("등록", type="primary"):
conn = get_conn()
try:
with conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO customers (name, email, tier) VALUES (%s, %s, %s)",
(name, email, tier)
)
st.success(f"✅ {name}님이 등록되었습니다!")
except psycopg2.errors.UniqueViolation:
st.error("❌ 이미 등록된 이메일입니다.")
finally:
release_conn(conn)
# --- 수정 / 삭제 (Update / Delete) ---
with tab_edit:
customer_id = st.number_input("고객 ID", min_value=1, step=1)
conn = get_conn()
try:
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
"SELECT id, name, email, tier FROM customers WHERE id = %s",
(customer_id,)
)
customer = cur.fetchone()
finally:
release_conn(conn)
if customer:
with st.form("edit_customer"):
new_name = st.text_input("이름", value=customer["name"])
tier_opts = get_tier_list()
new_tier = st.selectbox(
"등급", tier_opts,
index=tier_opts.index(customer["tier"]) if customer["tier"] in tier_opts else 0
)
col1, col2 = st.columns(2)
with col1:
if st.form_submit_button("수정 저장", type="primary"):
conn = get_conn()
try:
with conn:
with conn.cursor() as cur:
cur.execute(
"UPDATE customers SET name=%s, tier=%s, updated_at=NOW() WHERE id=%s",
(new_name, new_tier, customer_id)
)
st.success("✅ 수정되었습니다!")
finally:
release_conn(conn)
with col2:
if st.form_submit_button("삭제 (소프트)", type="secondary"):
conn = get_conn()
try:
with conn:
with conn.cursor() as cur:
cur.execute(
"UPDATE customers SET is_deleted=TRUE, deleted_at=NOW() WHERE id=%s",
(customer_id,)
)
st.warning("🗑️ 삭제 처리되었습니다.")
finally:
release_conn(conn)
else:
st.info("조회된 고객이 없습니다. ID를 확인하십시오.")