h-1.flet.3/app_dashboard_template.py
2026-02-20 23:24:01 +09:00

333 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
販売アシスト・ダッシュボード(テンプレート)
統合ハブとして全機能へのアクセスを提供
"""
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)