""" 販売アシスト・ダッシュボード(テンプレート) 統合ハブとして全機能へのアクセスを提供 """ import flet as ft import sqlite3 import signal import sys import logging from datetime import datetime from typing import List, Dict, Optional # ロギング設定 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) class DashboardTemplate: """ダッシュボードテンプレート""" def __init__(self, page: ft.Page): self.page = page self.setup_page() self.setup_database() self.setup_ui() def setup_page(self): """ページ設定""" self.page.title = "販売アシスト・ダッシュボード" self.page.window.width = 420 self.page.window.height = 900 self.page.window.resizable = False self.page.window_center = True self.page.theme_mode = ft.ThemeMode.LIGHT # シグナルハンドラ def signal_handler(signum, frame): logging.info("アプリケーション正常終了") sys.exit(0) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def setup_database(self): """データベース初期化""" try: self.conn = sqlite3.connect('sales_assist.db') self.cursor = self.conn.cursor() # テーブル作成(必要に応じて) self.cursor.execute(''' CREATE TABLE IF NOT EXISTS sales_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, amount REAL, product TEXT, customer TEXT ) ''') self.conn.commit() logging.info("データベース接続完了") except Exception as e: logging.error(f"データベースエラー: {e}") self.conn = None def setup_ui(self): """UI構築""" # ヘッダー header = ft.Container( content=ft.Column([ ft.Text( "🏠 販売アシスト", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE, text_align=ft.TextAlign.CENTER ), ft.Text( f"今日: {datetime.now().strftime('%m/%d %H:%M')}", size=14, color=ft.Colors.WHITE, text_align=ft.TextAlign.CENTER ) ], spacing=5), padding=20, bgcolor=ft.Colors.BLUE_600, border_radius=15, margin=ft.Margin.only(bottom=20) ) # ツールバー(コンパクト) toolbar = ft.Container( content=ft.Row([ ft.IconButton(ft.Icons.ADD, tooltip="新規下書き", icon_size=20), ft.IconButton(ft.Icons.SEARCH, tooltip="検索", icon_size=20), ft.IconButton(ft.Icons.FILTER_LIST, tooltip="フィルター", icon_size=20), ft.Container(expand=True), ft.IconButton(ft.Icons.SYNC, tooltip="更新", icon_size=20), ], spacing=5), padding=10, bgcolor=ft.Colors.BLUE_50 ) # 検索バー search_bar = ft.Container( content=ft.TextField( hint_text="下書き・顧客・商品を検索...", prefix_icon=ft.Icons.SEARCH, filled=True, dense=True ), padding=ft.Padding.symmetric(horizontal=15, vertical=5) ) # メイン機能グリッド(参考デザイン風) main_grid = ft.Container( content=ft.Column([ ft.Text("🏠 メニュー", size=16, weight=ft.FontWeight.BOLD), ft.GridView( runs_count=2, # 2列グリッド spacing=10, run_spacing=10, controls=[ self.create_grid_card("💰", "売上", "売上管理", ft.Colors.GREEN, "app_simple_working.py"), self.create_grid_card("👥", "顧客", "顧客管理", ft.Colors.ORANGE, "app_master_management.py"), self.create_grid_card("📦", "商品", "商品管理", ft.Colors.PURPLE, "app_hierarchical_product_master.py"), self.create_grid_card("📋", "伝票", "伝票管理", ft.Colors.RED, "app_slip_framework_demo.py"), self.create_grid_card("🔍", "検索", "伝票検索", ft.Colors.BLUE, "app_slip_explorer.py"), self.create_grid_card("📱", "操作", "インタラクティブ", ft.Colors.CYAN, "app_slip_interactive.py"), ] ) ], spacing=10), padding=15, bgcolor=ft.Colors.WHITE, border_radius=15, shadow=ft.BoxShadow( spread_radius=1, blur_radius=5, color=ft.Colors.GREY_300, offset=ft.Offset(0, 2) ), margin=ft.Margin.only(bottom=15) ) # 下書き一覧(コンパクト) draft_section = ft.Container( content=ft.Column([ ft.Text("📝 最近の下書き", size=16, weight=ft.FontWeight.BOLD), ft.Divider(height=1), self.create_draft_item("売上メモ", "顧客: 田中様", "2分前"), self.create_draft_item("商品リスト", "新商品10件", "1時間前"), ft.Container( content=ft.Button("+ もっと見る", bgcolor=ft.Colors.BLUE_50, color=ft.Colors.BLUE_700), alignment=ft.alignment.Alignment(0, 0) ) ], spacing=8), padding=15, bgcolor=ft.Colors.WHITE, border_radius=15, margin=ft.Margin.only(bottom=15) ) # 設定セクション settings_section = ft.Container( content=ft.Row([ ft.IconButton(ft.Icons.SETTINGS, icon_size=20), ft.Text("設定", size=14), ft.Container(expand=True), ft.Icon(ft.Icons.CHEVRON_RIGHT, size=16, color=ft.Colors.GREY_400) ]), padding=15, bgcolor=ft.Colors.WHITE, border_radius=10, border=ft.Border.all(1, ft.Colors.GREY_200) ) # 状態サマリー status_summary = ft.Container( content=ft.Column([ ft.Text("📈 状態サマリー", size=18, weight=ft.FontWeight.BOLD), ft.Divider(height=1, thickness=1), self.get_status_info() ], spacing=10), padding=20, bgcolor=ft.Colors.BLUE_50, border_radius=15, margin=ft.Margin.only(bottom=20) ) # Debug情報 debug_info = ft.Container( content=ft.Column([ ft.Text("🔧 Debug情報", size=16, weight=ft.FontWeight.BOLD), ft.Text(f"DB接続: {'✅' if self.conn else '❌'}", size=12), ft.Text(f"ウィンドウ: 420x900", size=12), ft.Text(f"テーマ: ライト", size=12), ], spacing=5), padding=15, bgcolor=ft.Colors.GREY_100, border_radius=10 ) # メインコンテナ(再構成) self.main_container = ft.Column([ header, search_bar, main_grid, draft_section, settings_section ], spacing=5, scroll=ft.ScrollMode.AUTO) # ページに追加 self.page.add( ft.Container( content=self.main_container, padding=10, # 20 → 10に縮小 bgcolor=ft.Colors.GREY_50, expand=True ) ) def create_grid_card(self, icon: str, title: str, subtitle: str, color: ft.Colors, app_file: str) -> ft.Container: """グリッドカード作成(参考デザイン風)""" return ft.Container( content=ft.Column([ ft.Container( content=ft.Text(icon, size=32), width=60, height=60, bgcolor=color, alignment=ft.alignment.Alignment(0, 0), border_radius=15, margin=ft.Margin.only(bottom=10) ), ft.Text(title, size=14, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER), ft.Text(subtitle, size=10, color=ft.Colors.GREY_600, text_align=ft.TextAlign.CENTER) ], spacing=5), padding=15, bgcolor=ft.Colors.WHITE, border_radius=15, border=ft.Border.all(1, ft.Colors.GREY_200), on_click=lambda _: self.launch_app(app_file, title) ) def create_draft_item(self, title: str, description: str, time: str) -> ft.Container: """下書きアイテム作成""" return ft.Container( content=ft.Row([ ft.Column([ ft.Text(title, size=14, weight=ft.FontWeight.BOLD), ft.Text(description, size=12, color=ft.Colors.GREY_600) ], expand=True), ft.Text(time, size=10, color=ft.Colors.GREY_500) ], spacing=10), padding=10, bgcolor=ft.Colors.GREY_50, border_radius=8, border=ft.Border.all(1, ft.Colors.GREY_200) ) def get_status_info(self) -> ft.Column: """状態情報取得""" if not self.conn: return ft.Column([ ft.Text("データベース未接続", size=12, color=ft.Colors.RED), ft.Text("機能制限中", size=12, color=ft.Colors.ORANGE) ], spacing=5) try: # 今日の売上件数 today = datetime.now().strftime('%Y-%m-%d') self.cursor.execute("SELECT COUNT(*) FROM sales_log WHERE date = ?", (today,)) today_sales = self.cursor.fetchone()[0] return ft.Column([ ft.Text(f"今日の売上: {today_sales}件", size=14, color=ft.Colors.BLUE_700), ft.Text("システム状態: 正常", size=14, color=ft.Colors.GREEN_600), ft.Text("最終更新: 剛剣", size=12, color=ft.Colors.GREY_600) ], spacing=5) except Exception as e: return ft.Column([ ft.Text("状態取得エラー", size=12, color=ft.Colors.RED), ft.Text(str(e), size=10, color=ft.Colors.GREY_600) ], spacing=5) def launch_app(self, app_file: str, app_name: str): """アプリ起動""" import subprocess try: # SnackBarで通知 self.page.snack_bar = ft.SnackBar( content=ft.Text(f"{app_name}を起動中..."), bgcolor=ft.Colors.BLUE ) self.page.snack_bar.open = True self.page.update() # サブプロセスで起動 subprocess.Popen(['python', app_file]) logging.info(f"{app_name}起動: {app_file}") except Exception as e: logging.error(f"{app_name}起動エラー: {e}") self.page.snack_bar = ft.SnackBar( content=ft.Text(f"起動エラー: {e}"), bgcolor=ft.Colors.RED ) self.page.snack_bar.open = True self.page.update() def main(page: ft.Page): """メイン関数""" try: dashboard = DashboardTemplate(page) logging.info("ダッシュボード起動完了") except Exception as e: logging.error(f"ダッシュボード起動エラー: {e}") page.snack_bar = ft.SnackBar( content=ft.Text(f"起動エラー: {e}"), bgcolor=ft.Colors.RED ) page.snack_bar.open = True page.update() if __name__ == "__main__": ft.run(main)