diff --git a/.gitignore b/.gitignore index 0cafc1c..1bfaf46 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.venv/ \ No newline at end of file +.venv/ +module/__pycache__ \ No newline at end of file diff --git a/app.log b/app.log index e69de29..950dd25 100644 --- a/app.log +++ b/app.log @@ -0,0 +1,32 @@ +[2025-03-27 17:28:05,955] [DEBUG] Initializing DBManager... +[2025-03-27 17:28:06,333] [DEBUG] DBManager initialized +[2025-03-27 17:28:06,334] [DEBUG] Initializing LoginDialog... +[2025-03-27 17:28:06,335] [DEBUG] Entering LoginDialog.create_dialog() +[2025-03-27 17:28:06,336] [DEBUG] Exiting LoginDialog.create_dialog() +[2025-03-27 17:28:06,337] [DEBUG] LoginDialog.show() 호출됨 +[2025-03-27 17:30:46,802] [DEBUG] Initializing DBManager... +[2025-03-27 17:30:47,172] [DEBUG] DBManager initialized +[2025-03-27 17:30:47,173] [DEBUG] Initializing LoginDialog... +[2025-03-27 17:30:47,174] [DEBUG] Entering LoginDialog.create_dialog() +[2025-03-27 17:30:47,175] [DEBUG] Exiting LoginDialog.create_dialog() +[2025-03-27 17:30:47,176] [DEBUG] LoginDialog.show() 호출됨 +[2025-03-27 17:31:45,862] [DEBUG] Initializing DBManager... +[2025-03-27 17:31:46,229] [DEBUG] DBManager initialized +[2025-03-27 17:31:46,230] [DEBUG] Initializing LoginDialog... +[2025-03-27 17:31:46,230] [DEBUG] Entering LoginDialog.create_dialog() +[2025-03-27 17:31:46,232] [DEBUG] Exiting LoginDialog.create_dialog() +[2025-03-27 17:31:46,232] [DEBUG] LoginDialog.show() 호출됨 +[2025-03-27 17:36:55,275] [DEBUG] Initializing DBManager... +[2025-03-27 17:36:55,656] [DEBUG] DBManager initialized +[2025-03-27 17:37:12,570] [DEBUG] Initializing DBManager... +[2025-03-27 17:37:12,930] [DEBUG] DBManager initialized +[2025-03-27 17:37:12,930] [DEBUG] Initializing LoginDialog... +[2025-03-27 17:37:12,932] [DEBUG] Entering LoginDialog.create_dialog() +[2025-03-27 17:37:12,933] [DEBUG] Exiting LoginDialog.create_dialog() +[2025-03-27 17:37:12,933] [DEBUG] LoginDialog.show() 호출됨 +[2025-03-27 17:38:18,459] [DEBUG] Initializing DBManager... +[2025-03-27 17:38:18,818] [DEBUG] DBManager initialized +[2025-03-27 17:38:18,818] [DEBUG] Initializing LoginDialog... +[2025-03-27 17:38:18,819] [DEBUG] Entering LoginDialog.create_dialog() +[2025-03-27 17:38:18,820] [DEBUG] Exiting LoginDialog.create_dialog() +[2025-03-27 17:38:18,821] [DEBUG] LoginDialog.show() 호출됨 diff --git a/main.py b/main.py index 9c14e1f..ab080b2 100644 --- a/main.py +++ b/main.py @@ -1,203 +1,42 @@ import flet as ft -from modules import logger, login, backend, product_filter, export +from modules import logger, login from modules.setting_manager import SettingsManager -from modules.db_manager import DBeManager -import logging - -# 전역 변수 (데모용 데이터 저장) -market_list = [] -sold_products = [] -filtered_products = [] -sourced_products = [] +from modules.db_manager import DBManager +from modules.main_window import MainWindow def main(page: ft.Page): page.title = "Modern Market and Product Manager" page.window_width = 1000 page.window_height = 700 - # 하단 로그 출력용 텍스트 위젯 - log_display = ft.Text(value="", size=12) + # 로거 콜백 예시 (단순 콘솔 출력) def gui_log_callback(formatted_message: str): - log_display.value += formatted_message + "\n" - page.update() + print(formatted_message) - # 로거, 설정 관리자, SupabaseManager 초기화 + # 로거, 설정 관리자, DBManager 초기화 app_logger = logger.get_logger(gui_callback=gui_log_callback) settings_manager = SettingsManager() - supabase_manager = DBeManager(app_logger) + db_manager = DBManager(app_logger) - # 로그인 다이얼로그 실행 (비동기) - async def do_login(): - login_dialog = login.LoginDialog(page, app_logger, settings_manager, supabase_manager) - logged_in = await login_dialog.show() - if logged_in: - page.controls.clear() - page.add(ft.Text("로그인 성공! 메인 화면입니다."), log_display) - else: - page.add(ft.Text("로그인 실패!"), log_display) - page.async_run(do_login) + # 로그인 성공 시 호출될 콜백 함수 + def on_login_success(): + # 로그인 다이얼로그가 닫힌 뒤 호출됨 + page.controls.clear() + main_win = MainWindow(page) + for ctrl in main_win.controls: + page.add(ctrl) + page.update() - # 마켓 탭 UI 구성 - market_tab_content = ft.Column([ - ft.Row([ - ft.ElevatedButton("마켓목록 가져오기", on_click=lambda e: load_market_list(page)), - ft.ElevatedButton("팔린상품 가져오기", on_click=lambda e: load_sold_products(page)), - ft.ElevatedButton("마켓추가하기", on_click=lambda e: add_market(page)) - ]), - ft.DataTable( - columns=[ - ft.DataColumn(ft.Text("마켓이름")), - ft.DataColumn(ft.Text("마켓 URL")), - ft.DataColumn(ft.Text("메모")) - ], - rows=[], - expand=True, - key="market_table" - ) - ], scroll=ft.ScrollMode.AUTO) - - # 상품 탭 UI 구성 - product_tab_content = ft.Column([ - ft.Row([ - ft.ElevatedButton("금지어필터링", on_click=lambda e: filter_forbidden(page)), - ft.ElevatedButton("카테고리 필터링", on_click=lambda e: filter_category(page)), - ft.Dropdown( - label="소싱몰 목록", - options=[ - ft.dropdown.Option("타오바오"), - ft.dropdown.Option("1688") - ], - key="sourcing_market" - ), - ft.ElevatedButton("소싱하기", on_click=lambda e: sourcing_products(page)), - ft.ElevatedButton("출력", on_click=lambda e: export_products(page)) - ]), - ft.DataTable( - columns=[ - ft.DataColumn(ft.Text("상품명")), - ft.DataColumn(ft.Text("카테고리")), - ft.DataColumn(ft.Text("이미지 URL")), - ft.DataColumn(ft.Text("소싱 URL")) - ], - rows=[], - expand=True, - key="product_table" - ) - ], scroll=ft.ScrollMode.AUTO) - - # 금지어 관리 탭 (추후 구현) - forbidden_tab_content = ft.Column([ - ft.Text("금지어 관리 탭 내용 (추후 구현)") - ]) - - # 카테고리 관리 탭 (추후 구현) - category_tab_content = ft.Column([ - ft.Text("카테고리 관리 탭 내용 (추후 구현)") - ]) - - # 메인 탭 생성 - tabs = ft.Tabs( - selected_index=0, - tabs=[ - ft.Tab(text="마켓", content=market_tab_content), - ft.Tab(text="상품", content=product_tab_content), - ft.Tab(text="금지어 관리", content=forbidden_tab_content), - ft.Tab(text="카테고리 관리", content=category_tab_content) - ], - key="main_tabs" + # 로그인 다이얼로그 생성 + login_dialog = login.LoginDialog( + page=page, + logger=app_logger, + settings_manager=settings_manager, + db_manager=db_manager, + on_login_success=on_login_success ) - # 페이지 레이아웃 구성 - page.add(tabs, log_display) + # 로그인 다이얼로그 표시 + login_dialog.show() - # 로그 추가 함수 (각 모듈에서 호출 가능하도록 page.session에 저장) - def append_log(message: str): - current = log_display.value - log_display.value = current + message + "\n" - page.update() - page.session.set("append_log", append_log) - -def load_market_list(page: ft.Page): - global market_list - page.session.get("append_log")("Fetching market list...") - market_list = backend.get_market_list() - market_rows = [] - for m in market_list: - row = ft.DataRow(cells=[ - ft.DataCell(ft.Text(m.get("name", ""))), - ft.DataCell(ft.Text(m.get("url", ""))), - ft.DataCell(ft.Text(m.get("memo", ""))) - ]) - market_rows.append(row) - market_table: ft.DataTable = page.get_control("market_table") - market_table.rows = market_rows - page.session.get("append_log")("Market list loaded.") - page.update() - -def load_sold_products(page: ft.Page): - global sold_products, filtered_products, sourced_products - page.session.get("append_log")("Fetching sold products for each market...") - sold_products = backend.get_sold_products(market_list) - filtered_products = sold_products.copy() - page.session.get("append_log")("Sold products loaded. Switching to 상품 탭.") - update_product_table(page, filtered_products) - tabs: ft.Tabs = page.get_control("main_tabs") - tabs.selected_index = 1 - page.update() - -def update_product_table(page: ft.Page, products): - product_rows = [] - for p in products: - row = ft.DataRow(cells=[ - ft.DataCell(ft.Text(p.get("name", ""))), - ft.DataCell(ft.Text(p.get("category", ""))), - ft.DataCell(ft.Text(p.get("image_url", ""))), - ft.DataCell(ft.Text(p.get("sourcing_url", ""))) - ]) - product_rows.append(row) - product_table: ft.DataTable = page.get_control("product_table") - product_table.rows = product_rows - page.update() - -def add_market(page: ft.Page): - page.session.get("append_log")("Add market functionality not implemented yet.") - page.update() - -def filter_forbidden(page: ft.Page): - global filtered_products - page.session.get("append_log")("Filtering products with forbidden words...") - filtered_products = product_filter.filter_forbidden_words(filtered_products) - update_product_table(page, filtered_products) - page.session.get("append_log")("Forbidden words filtering applied.") - page.update() - -def filter_category(page: ft.Page): - global filtered_products - page.session.get("append_log")("Filtering products with forbidden categories...") - filtered_products = product_filter.filter_forbidden_categories(filtered_products) - update_product_table(page, filtered_products) - page.session.get("append_log")("Category filtering applied.") - page.update() - -def sourcing_products(page: ft.Page): - global sourced_products, filtered_products - sourcing_market: ft.Dropdown = page.get_control("sourcing_market") - selected_market = sourcing_market.value - page.session.get("append_log")(f"Starting sourcing using {selected_market}...") - sourced_products = [] - for product in filtered_products: - sourcing_url = backend.sourcing_product(product.get("image_url", ""), selected_market) - product["sourcing_url"] = sourcing_url - sourced_products.append(product) - update_product_table(page, sourced_products) - page.session.get("append_log")("Sourcing completed.") - page.update() - -def export_products(page: ft.Page): - page.session.get("append_log")("Exporting products to Excel...") - export.export_to_excel(sourced_products) - page.session.get("append_log")("Products exported and folder opened.") - page.update() - -if __name__ == "__main__": - ft.app(target=main) +ft.app(target=main) diff --git a/modules/__pycache__/backend.cpython-311.pyc b/modules/__pycache__/backend.cpython-311.pyc index db643eb..f3f14c1 100644 Binary files a/modules/__pycache__/backend.cpython-311.pyc and b/modules/__pycache__/backend.cpython-311.pyc differ diff --git a/modules/__pycache__/db_manager.cpython-311.pyc b/modules/__pycache__/db_manager.cpython-311.pyc index f9814b0..2b7b751 100644 Binary files a/modules/__pycache__/db_manager.cpython-311.pyc and b/modules/__pycache__/db_manager.cpython-311.pyc differ diff --git a/modules/__pycache__/export.cpython-311.pyc b/modules/__pycache__/export.cpython-311.pyc index b097e2d..556993b 100644 Binary files a/modules/__pycache__/export.cpython-311.pyc and b/modules/__pycache__/export.cpython-311.pyc differ diff --git a/modules/__pycache__/login.cpython-311.pyc b/modules/__pycache__/login.cpython-311.pyc index ca9fad1..f6165a1 100644 Binary files a/modules/__pycache__/login.cpython-311.pyc and b/modules/__pycache__/login.cpython-311.pyc differ diff --git a/modules/__pycache__/main_window.cpython-311.pyc b/modules/__pycache__/main_window.cpython-311.pyc new file mode 100644 index 0000000..85891b5 Binary files /dev/null and b/modules/__pycache__/main_window.cpython-311.pyc differ diff --git a/modules/__pycache__/product_filter.cpython-311.pyc b/modules/__pycache__/product_filter.cpython-311.pyc index 22c2359..043ab33 100644 Binary files a/modules/__pycache__/product_filter.cpython-311.pyc and b/modules/__pycache__/product_filter.cpython-311.pyc differ diff --git a/modules/__pycache__/setting_manager.cpython-311.pyc b/modules/__pycache__/setting_manager.cpython-311.pyc index 1ff1f1b..ace6abf 100644 Binary files a/modules/__pycache__/setting_manager.cpython-311.pyc and b/modules/__pycache__/setting_manager.cpython-311.pyc differ diff --git a/modules/backend.py b/modules/backend.py index 4ecafd1..68352cb 100644 --- a/modules/backend.py +++ b/modules/backend.py @@ -1,40 +1,42 @@ import time import random -import requests -from modules import logger - -app_logger = logger.get_logger() +import logging def get_market_list(): - # Supabase에서 마켓 목록을 가져오는 기능 (시뮬레이션) - app_logger.info("Fetching market list from supabase...") - # 데모용 더미 데이터 + logger = logging.getLogger("FletLogger") + logger.debug("Entering get_market_list()") time.sleep(1) - return [ + data = [ {"name": "Market A", "url": "https://market-a.com", "memo": "Memo A"}, {"name": "Market B", "url": "https://market-b.com", "memo": "Memo B"}, ] + logger.debug(f"Market list retrieved: {data}") + logger.debug("Exiting get_market_list()") + return data def get_sold_products(markets): - # 각 마켓의 판매 상품 목록을 가져오는 기능 (시뮬레이션) - app_logger.info("Fetching sold products from markets...") + logger = logging.getLogger("FletLogger") + logger.debug("Entering get_sold_products()") products = [] for market in markets: - time.sleep(0.5) # 네트워크 딜레이 시뮬레이션 - # 더미 상품 데이터 + time.sleep(0.5) for i in range(3): - products.append({ + prod = { "name": f"Product {i} from {market['name']}", "category": random.choice(["Electronics", "Clothing", "Forbidden Category"]), "image_url": "https://via.placeholder.com/150", "sourcing_url": "" - }) + } + products.append(prod) + logger.debug(f"Sold products retrieved: {products}") + logger.debug("Exiting get_sold_products()") return products def sourcing_product(image_url, sourcing_market): - # RapidAPI를 이용한 소싱 기능 (시뮬레이션) - app_logger.info(f"Sourcing product for image {image_url} from {sourcing_market}...") - # 실제 구현 시 requests를 통해 API 호출 + logger = logging.getLogger("FletLogger") + logger.debug("Entering sourcing_product()") time.sleep(0.3) - # 더미 소싱 URL 반환 - return f"https://sourcing.example.com/{sourcing_market}/" + image_url.split("/")[-1] + url = f"https://sourcing.example.com/{sourcing_market}/" + image_url.split("/")[-1] + logger.debug(f"Sourcing URL: {url}") + logger.debug("Exiting sourcing_product()") + return url diff --git a/modules/db_manager.py b/modules/db_manager.py index b86a4b5..45b9622 100644 --- a/modules/db_manager.py +++ b/modules/db_manager.py @@ -1,36 +1,38 @@ + from supabase import create_client, Client from supabase.lib.client_options import ClientOptions from datetime import datetime, timezone import logging import traceback -class DBeManager: +class DBManager: """ - SupabaseManager는 Supabase 클라이언트를 래핑하여 로그인, 사용자 정보 조회, + DBManager는 Supabase 클라이언트를 래핑하여 로그인, 사용자 정보 조회, 마지막 로그인 시간 업데이트 등 여러 API 호출을 수행합니다. """ def __init__(self, logger: logging.Logger): self.logger = logger + self.logger.log("Initializing DBManager...", level=logging.DEBUG) + # 실제 Supabase URL과 KEY로 수정하세요. self.url: str = "http://146.56.101.199:8000" self.key: str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE" - self.client: Client = create_client(self.url, self.key) self.access_token = None self.refresh_token = None + self.logger.log("DBManager initialized", level=logging.DEBUG) def update_client_with_token(self, access_token: str): + self.logger.log("Entering update_client_with_token()", level=logging.DEBUG) try: options = ClientOptions(headers={"Authorization": f"Bearer {access_token}"}) self.client = create_client(self.url, self.key, options=options) self.logger.log("Client updated with JWT token", level=logging.DEBUG) except Exception as ex: self.logger.log(f"update_client_with_token error: {ex}", level=logging.ERROR, exc_info=True) + self.logger.log("Exiting update_client_with_token()", level=logging.DEBUG) def login(self, email: str, password: str) -> dict: - """ - Supabase Auth를 사용해 로그인합니다. - 로그인 성공 시 사용자 정보를 담은 dict를, 실패 시 error 키를 포함한 dict를 반환합니다. - """ + self.logger.log("Entering DBManager.login()", level=logging.DEBUG) try: response = self.client.auth.sign_in_with_password({"email": email, "password": password}) if response.session: @@ -46,6 +48,7 @@ class DBeManager: "nickname": response.user.user_metadata.get("nickname", "Unknown") } self.logger.log(f"로그인 성공: {user_info}", level=logging.DEBUG) + self.logger.log("Exiting DBManager.login()", level=logging.DEBUG) return user_info else: self.logger.log("로그인 실패: 세션 없음", level=logging.WARNING) @@ -55,12 +58,11 @@ class DBeManager: return {"error": str(e)} def get_auth_user_info(self, user_id: str) -> dict: - """ - auth.users 테이블(인증 스키마)에서 사용자 정보를 조회합니다. - """ + self.logger.log(f"Entering get_auth_user_info(user_id={user_id})", level=logging.DEBUG) try: response = self.client.from_("users").select("email_confirmed_at").eq("id", user_id).execute() if response.data and len(response.data) > 0: + self.logger.log(f"Retrieved auth user info: {response.data[0]}", level=logging.DEBUG) return response.data[0] else: self.logger.log("auth.users에서 사용자 정보를 찾지 못했습니다.", level=logging.WARNING) @@ -70,10 +72,7 @@ class DBeManager: return {} def get_full_user_info(self, user_id: str) -> dict: - """ - public.users 테이블과 membership_levels 테이블을 조합하여 - 전체 사용자 정보를 반환합니다. - """ + self.logger.log(f"Entering get_full_user_info(user_id={user_id})", level=logging.DEBUG) try: user_resp = self.client.table("users").select("*").eq("id", user_id).execute() if not user_resp.data: @@ -84,18 +83,18 @@ class DBeManager: membership_resp = self.client.table("membership_levels").select("*").eq("level", membership_level).execute() membership_info = membership_resp.data[0] if membership_resp.data else {} full_info = {**user_info, "membership_level_data": membership_info} + self.logger.log(f"Full user info retrieved: {full_info}", level=logging.DEBUG) return full_info except Exception as e: self.logger.log(f"get_full_user_info 에러: {e}", level=logging.ERROR, exc_info=True) return {} def update_last_login(self, user_id: str): - """ - 사용자의 마지막 로그인 시간을 현재 시간(UTC)으로 업데이트합니다. - """ + self.logger.log(f"Entering update_last_login(user_id={user_id})", level=logging.DEBUG) try: now_iso = datetime.now(timezone.utc).isoformat() self.client.table("users").update({"last_login": now_iso}).eq("id", user_id).execute() self.logger.log(f"Last login updated for user {user_id}", level=logging.INFO) except Exception as e: self.logger.log(f"update_last_login 에러: {e}", level=logging.ERROR, exc_info=True) + self.logger.log("Exiting update_last_login()", level=logging.DEBUG) diff --git a/modules/export.py b/modules/export.py index 71e963e..4217f24 100644 --- a/modules/export.py +++ b/modules/export.py @@ -2,11 +2,14 @@ import pandas as pd import os from datetime import datetime import webbrowser +import logging def export_to_excel(products): + logger = logging.getLogger("FletLogger") + logger.debug("Entering export_to_excel()") if not products: + logger.debug("No products to export.") return - # 상품을 50개씩 분할 batches = [products[i:i+50] for i in range(0, len(products), 50)] export_folder = "exported_products" os.makedirs(export_folder, exist_ok=True) @@ -14,5 +17,6 @@ def export_to_excel(products): df = pd.DataFrame(batch) filename = os.path.join(export_folder, f"products_batch_{idx+1}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx") df.to_excel(filename, index=False) - # 저장 폴더 열기 + logger.debug(f"Exported batch {idx+1} to {filename}") webbrowser.open(os.path.abspath(export_folder)) + logger.debug("Exiting export_to_excel()") diff --git a/modules/login.py b/modules/login.py index cd520d5..3c2b35e 100644 --- a/modules/login.py +++ b/modules/login.py @@ -1,30 +1,36 @@ import flet as ft -from flet import ( - AlertDialog, TextField, Checkbox, ElevatedButton, Text, Column, Row, - MainAxisAlignment -) -import asyncio +from flet import AlertDialog, TextField, Checkbox, ElevatedButton, Text, Column, Row, MainAxisAlignment import logging class LoginDialog: """ - flet 기반 로그인 다이얼로그. - - - 이메일, 비밀번호 입력란 - - "정보 저장" 체크박스 (체크 시, SettingsManager를 통해 사용자 정보를 저장) - - "비밀번호 보기" 체크박스 (비밀번호 입력란 에코 모드 전환) - - 로그인 및 비밀번호 찾기 버튼 + - 로그인 성공 시 on_login_success 콜백 호출 + - 로그인 실패 시 메시지 표시 + - 비밀번호 보기 토글, 정보 저장 체크박스 등 """ - def __init__(self, page: ft.Page, logger: logging.Logger, settings_manager, supabase_manager): + def __init__( + self, + page: ft.Page, + logger: logging.Logger, + settings_manager, + db_manager, + on_login_success + ): + """ + on_login_success: 로그인 성공 시 호출할 콜백 함수 (메인 윈도우로 전환 등) + """ self.page = page self.logger = logger self.settings_manager = settings_manager - self.supabase_manager = supabase_manager - self.result = None # 로그인 성공 여부 (True/False) + self.db_manager = db_manager + self.on_login_success = on_login_success + self.result = None # True/False + + self.logger.log("Initializing LoginDialog...", level=logging.DEBUG) self.create_dialog() def create_dialog(self): - # 입력 필드 및 체크박스 + self.logger.log("Entering LoginDialog.create_dialog()", level=logging.DEBUG) self.email_field = TextField(label="이메일", width=300) self.password_field = TextField(label="비밀번호", width=300, password=True) self.remember_checkbox = Checkbox(label="정보 저장") @@ -33,64 +39,69 @@ class LoginDialog: self.show_password_checkbox.on_change = self.toggle_password - # 버튼들 self.login_button = ElevatedButton("로그인", on_click=self.on_login) self.reset_button = ElevatedButton("비밀번호 찾기", on_click=self.on_reset) - content = Column([ - self.email_field, - self.password_field, - Row([self.remember_checkbox, self.show_password_checkbox]), - self.error_text, - Row([self.login_button, self.reset_button], alignment=MainAxisAlignment.CENTER) - ]) - + # AlertDialog 구성 self.dialog = AlertDialog( title=Text("로그인"), - content=content, + content=Column([ + self.email_field, + self.password_field, + Row([self.remember_checkbox, self.show_password_checkbox]), + self.error_text, + Row([self.login_button, self.reset_button], alignment=MainAxisAlignment.CENTER) + ]), actions_alignment=MainAxisAlignment.CENTER ) + self.logger.log("Exiting LoginDialog.create_dialog()", level=logging.DEBUG) + + def show(self): + """ + 로그인 다이얼로그를 표시한다. (비동기 루프 없이, 콜백 방식) + """ + self.logger.log("LoginDialog.show() 호출됨", level=logging.DEBUG) self.page.dialog = self.dialog self.dialog.open = True self.page.update() def toggle_password(self, e: ft.ControlEvent): + self.logger.log("toggle_password() 호출됨", level=logging.DEBUG) self.password_field.password = not self.show_password_checkbox.value self.page.update() def on_login(self, e: ft.ControlEvent): + self.logger.log("on_login() 호출됨", level=logging.DEBUG) email = self.email_field.value.strip() password = self.password_field.value.strip() if not email or not password: self.error_text.value = "이메일과 비밀번호를 모두 입력하세요." + self.logger.log("on_login(): 입력값 부족", level=logging.WARNING) self.page.update() return - # 실제 Supabase 로그인을 시도합니다. - result = self.supabase_manager.login(email, password) + + result = self.db_manager.login(email, password) if "error" not in result: self.logger.log(f"로그인 성공: {result}", level=logging.DEBUG) - # 마지막 로그인 시간 업데이트 - self.supabase_manager.update_last_login(result["id"]) + self.db_manager.update_last_login(result["id"]) if self.remember_checkbox.value: user_info = {"email": email, "password": password, "id": result["id"]} self.settings_manager.save_user_info(user_info) - self.result = True + + # 로그인 성공 => 다이얼로그 닫고 콜백 호출 self.dialog.open = False + self.page.dialog = None self.page.update() + + # 메인윈도우로 전환 + if self.on_login_success: + self.on_login_success() else: self.error_text.value = f"로그인 실패: {result['error']}" self.logger.log(f"로그인 실패: {result['error']}", level=logging.WARNING) self.page.update() def on_reset(self, e: ft.ControlEvent): + self.logger.log("on_reset() 호출됨", level=logging.DEBUG) self.error_text.value = "비밀번호 찾기 기능은 구현되지 않았습니다." self.page.update() - - async def show(self) -> bool: - """ - 로그인 다이얼로그를 보여주고, 로그인 성공 시 True를 반환합니다. - (비동기적으로 result가 채워질 때까지 기다립니다.) - """ - while self.result is None: - await asyncio.sleep(0.1) - return self.result diff --git a/modules/main_window.py b/modules/main_window.py new file mode 100644 index 0000000..22fe5b5 --- /dev/null +++ b/modules/main_window.py @@ -0,0 +1,159 @@ +import flet as ft +import logging +from modules import backend, product_filter, export + +class MainWindow: + def __init__(self, page: ft.Page): + self.page = page + self.logger = logging.getLogger("FletLogger") + self.logger.debug("MainWindow initialized") + self.market_list = [] + self.sold_products = [] + self.filtered_products = [] + self.sourced_products = [] + self.controls = self.build_layout() + + def build_layout(self): + self.logger.debug("Building main window layout") + # 마켓 탭 + self.market_table = ft.DataTable( + columns=[ + ft.DataColumn(ft.Text("마켓이름")), + ft.DataColumn(ft.Text("마켓 URL")), + ft.DataColumn(ft.Text("메모")) + ], + rows=[], + expand=True, + key="market_table" + ) + market_tab_content = ft.Column([ + ft.Row([ + ft.ElevatedButton("마켓목록 가져오기", on_click=self.load_market_list), + ft.ElevatedButton("팔린상품 가져오기", on_click=self.load_sold_products), + ft.ElevatedButton("마켓추가하기", on_click=self.add_market) + ]), + self.market_table + ], scroll=ft.ScrollMode.AUTO) + + # 상품 탭 + self.product_table = ft.DataTable( + columns=[ + ft.DataColumn(ft.Text("상품명")), + ft.DataColumn(ft.Text("카테고리")), + ft.DataColumn(ft.Text("이미지 URL")), + ft.DataColumn(ft.Text("소싱 URL")) + ], + rows=[], + expand=True, + key="product_table" + ) + self.sourcing_market_dropdown = ft.Dropdown( + label="소싱몰 목록", + options=[ + ft.dropdown.Option("타오바오"), + ft.dropdown.Option("1688") + ], + key="sourcing_market" + ) + product_tab_content = ft.Column([ + ft.Row([ + ft.ElevatedButton("금지어필터링", on_click=self.filter_forbidden), + ft.ElevatedButton("카테고리 필터링", on_click=self.filter_category), + self.sourcing_market_dropdown, + ft.ElevatedButton("소싱하기", on_click=self.sourcing_products), + ft.ElevatedButton("출력", on_click=self.export_products) + ]), + self.product_table + ], scroll=ft.ScrollMode.AUTO) + + # 기타 탭 + forbidden_tab_content = ft.Column([ft.Text("금지어 관리 탭 (추후 구현)")]) + category_tab_content = ft.Column([ft.Text("카테고리 관리 탭 (추후 구현)")]) + + self.tabs = ft.Tabs( + selected_index=0, + tabs=[ + ft.Tab(text="마켓", content=market_tab_content), + ft.Tab(text="상품", content=product_tab_content), + ft.Tab(text="금지어 관리", content=forbidden_tab_content), + ft.Tab(text="카테고리 관리", content=category_tab_content) + ], + key="main_tabs" + ) + + # 로그 출력 + self.log_display = ft.Text(value="", size=12) + def append_log(message: str): + self.log_display.value += message + "\n" + self.page.update() + self.page.session.set("append_log", append_log) + + layout = [self.tabs, self.log_display] + self.logger.debug("Main window layout built") + return layout + + # 이하 메서드들은 모두 디버그 로깅 + 기능 수행 + def load_market_list(self, e): + self.logger.debug("load_market_list() 호출됨") + self.page.session.get("append_log")("Fetching market list...") + self.market_list = backend.get_market_list() + rows = [] + for m in self.market_list: + row = ft.DataRow(cells=[ + ft.DataCell(ft.Text(m.get("name", ""))), + ft.DataCell(ft.Text(m.get("url", ""))), + ft.DataCell(ft.Text(m.get("memo", ""))) + ]) + rows.append(row) + self.market_table.rows = rows + self.page.session.get("append_log")("Market list loaded.") + self.page.update() + + def load_sold_products(self, e): + self.logger.debug("load_sold_products() 호출됨") + self.page.session.get("append_log")("Fetching sold products for each market...") + self.sold_products = backend.get_sold_products(self.market_list) + self.filtered_products = self.sold_products.copy() + self.page.session.get("append_log")("Sold products loaded. Switching to 상품 탭.") + self.update_product_table(self.filtered_products) + self.tabs.selected_index = 1 + self.page.update() + + def update_product_table(self, products): + self.logger.debug("update_product_table() 호출됨") + rows = [] + for p in products: + row = ft.DataRow(cells=[ + ft.DataCell(ft.Text(p.get("name", ""))), + ft.DataCell(ft.Text(p.get("category", ""))), + ft.DataCell(ft.Text(p.get("image_url", ""))), + ft.DataCell(ft.Text(p.get("sourcing_url", ""))) + ]) + rows.append(row) + self.product_table.rows = rows + self.page.update() + + def add_market(self, e): + self.logger.debug("add_market() 호출됨") + self.page.session.get("append_log")("Add market functionality not implemented yet.") + self.page.update() + + def filter_forbidden(self, e): + self.logger.debug("filter_forbidden() 호출됨") + self.page.session.get("append_log")("Filtering products with forbidden words...") + self.filtered_products = product_filter.filter_forbidden_words(self.filtered_products) + self.update_product_table(self.filtered_products) + self.page.session.get("append_log")("Forbidden words filtering applied.") + self.page.update() + + def filter_category(self, e): + self.logger.debug("filter_category() 호출됨") + self.page.session.get("append_log")("Filtering products with forbidden categories...") + self.filtered_products = product_filter.filter_forbidden_categories(self.filtered_products) + self.update_product_table(self.filtered_products) + self.page.session.get("append_log")("Category filtering applied.") + self.page.update() + + def sourcing_products(self, e): + self.logger.debug("sourcing_products() 호출됨") + self diff --git a/modules/product_filter.py b/modules/product_filter.py index 557e63d..631da71 100644 --- a/modules/product_filter.py +++ b/modules/product_filter.py @@ -1,18 +1,25 @@ def filter_forbidden_words(products): - # 데모용 금지어 리스트 + import logging + logger = logging.getLogger("FletLogger") + logger.debug("Entering filter_forbidden_words()") forbidden_words = ["bad", "illegal", "금지어"] filtered = [] for product in products: - # 상품명이 금지어를 포함하지 않을 경우에만 추가 if not any(word in product.get("name", "").lower() for word in forbidden_words): filtered.append(product) + logger.debug(f"Filtered products (forbidden words): {filtered}") + logger.debug("Exiting filter_forbidden_words()") return filtered def filter_forbidden_categories(products): - # 데모용 금지 카테고리 리스트 + import logging + logger = logging.getLogger("FletLogger") + logger.debug("Entering filter_forbidden_categories()") forbidden_categories = ["Forbidden Category"] filtered = [] for product in products: if product.get("category", "") not in forbidden_categories: filtered.append(product) + logger.debug(f"Filtered products (forbidden categories): {filtered}") + logger.debug("Exiting filter_forbidden_categories()") return filtered diff --git a/modules/setting_manager.py b/modules/setting_manager.py index 9ee2fe5..865d35a 100644 --- a/modules/setting_manager.py +++ b/modules/setting_manager.py @@ -3,7 +3,7 @@ import os class SettingsManager: """ - 로컬 JSON 파일("settings.json")을 이용해 사용자 설정(예, 로그인 정보, GUI 설정 등)을 저장/불러옵니다. + 로컬 JSON 파일("settings.json")을 이용해 사용자 설정(로그인 정보, GUI 설정 등)을 저장/불러옵니다. """ def __init__(self, filename="settings.json"): self.filename = filename @@ -11,14 +11,14 @@ class SettingsManager: self.load_settings() def load_settings(self): - if os.path.exists(self.filename): - try: + try: + if os.path.exists(self.filename): with open(self.filename, "r", encoding="utf-8") as f: self.settings = json.load(f) - except Exception as e: - print("설정 불러오기 오류:", e) + else: self.settings = {} - else: + except Exception as e: + print("설정 불러오기 오류:", e) self.settings = {} def save_settings(self):