import flet as ft import sqlite3 import datetime from typing import List, Dict, Optional async def main(page: ft.Page): # データベース初期化 init_database() page.title = "販売アシスト1号" page.theme_mode = ft.ThemeMode.LIGHT page.window_width = 450 page.window_height = 800 async def refresh_all(): await list_view.refresh() # Viewの生成 input_view = get_input_view(page, refresh_all) list_view = get_list_view(page, input_view) # ナビゲーション制御 async def nav_change(e): idx = e.control.selected_index input_view.visible = (idx == 0) list_view.visible = (idx == 1) if list_view.visible: await list_view.refresh() page.update() page.navigation_bar = ft.NavigationBar( selected_index=1, # 最初はリスト destinations=[ ft.NavigationBarDestination(icon=ft.Icons.EDIT_NOTE, label="売上起票"), ft.NavigationBarDestination(icon=ft.Icons.LIST_ALT, label="一覧"), ], on_change=nav_change ) # 画面への追加 page.add(ft.Container( content=ft.Column([input_view, list_view], expand=True), padding=10, expand=True )) # 初期表示設定 input_view.visible = False list_view.visible = True await list_view.refresh() page.update() def init_database(): """Initialize SQLite database""" conn = sqlite3.connect('sales.db') cursor = conn.cursor() # Create sales table cursor.execute(''' CREATE TABLE IF NOT EXISTS sales ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, customer TEXT NOT NULL, product TEXT NOT NULL, amount REAL NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() conn.close() class InputView(ft.Column): def __init__(self, page: ft.Page, on_success): super().__init__(expand=True, spacing=15, visible=False) self.my_page = page self.on_success = on_success self.edit_id = None # UI部品の定義 self.title = ft.Text("売上伝票の起票", size=24, weight="bold", color=ft.Colors.BLUE_900) self.date_tf = ft.TextField( label="伝票日付", value=datetime.datetime.now().strftime("%Y-%m-%d"), width=200, border_radius=10 ) self.customer_tf = ft.TextField(label="顧客名", border_radius=10) self.product_tf = ft.TextField(label="項目/商品名", border_radius=10) self.amount_tf = ft.TextField( label="金額 (税込)", width=200, keyboard_type=ft.KeyboardType.NUMBER, prefix_icon=ft.Icons.ATTACH_MONEY, border_radius=10 ) self.save_btn = ft.ElevatedButton( "保存", icon=ft.Icons.SAVE, on_click=self.save_data, bgcolor=ft.Colors.BLUE_900, color=ft.Colors.WHITE ) self.controls = [ self.title, ft.Row([self.date_tf, self.customer_tf], spacing=10), self.product_tf, self.amount_tf, self.save_btn ] async def save_data(self, e): try: if not all([self.customer_tf.value, self.product_tf.value, self.amount_tf.value]): self.my_page.snack_bar = ft.SnackBar( content=ft.Text("すべての項目を入力してください"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() return amount = float(self.amount_tf.value) conn = sqlite3.connect('sales.db') cursor = conn.cursor() if self.edit_id: cursor.execute(''' UPDATE sales SET date=?, customer=?, product=?, amount=? WHERE id=? ''', (self.date_tf.value, self.customer_tf.value, self.product_tf.value, amount, self.edit_id)) else: cursor.execute(''' INSERT INTO sales (date, customer, product, amount) VALUES (?, ?, ?, ?) ''', (self.date_tf.value, self.customer_tf.value, self.product_tf.value, amount)) conn.commit() conn.close() # フォームをクリア self.clear_form() # 成功メッセージ self.my_page.snack_bar = ft.SnackBar( content=ft.Text("保存しました"), bgcolor=ft.Colors.GREEN_500 ) self.my_page.snack_bar.open = True self.my_page.update() # 一覧を更新 await self.on_success() except ValueError: self.my_page.snack_bar = ft.SnackBar( content=ft.Text("金額は数値で入力してください"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() except Exception as ex: self.my_page.snack_bar = ft.SnackBar( content=ft.Text(f"エラー: {str(ex)}"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() def clear_form(self): self.customer_tf.value = "" self.product_tf.value = "" self.amount_tf.value = "" self.date_tf.value = datetime.datetime.now().strftime("%Y-%m-%d") self.edit_id = None self.update() class ListView(ft.Column): def __init__(self, page: ft.Page, input_view): super().__init__(expand=True, visible=True) self.my_page = page self.input_view = input_view self.data_list = ft.ListView(expand=True, spacing=5) self.title = ft.Text("売上一覧", size=20, weight="bold") self.controls = [self.title, self.data_list] async def refresh(self): try: conn = sqlite3.connect('sales.db') cursor = conn.cursor() cursor.execute(''' SELECT id, date, customer, product, amount FROM sales ORDER BY date DESC, created_at DESC LIMIT 50 ''') self.data_list.controls.clear() for row in cursor.fetchall(): sale_id, date, customer, product, amount = row card = ft.Card( content=ft.Container( content=ft.ListTile( title=ft.Text(f"{customer} - {product}"), subtitle=ft.Text(f"{date} | ¥{amount:,.0f}"), trailing=ft.Row([ ft.IconButton( icon=ft.Icons.EDIT, on_click=lambda e, id=sale_id: self.edit_data(id) ), ft.IconButton( icon=ft.Icons.DELETE, on_click=lambda e, id=sale_id: self.delete_data(id) ) ]) ), padding=10 ) ) self.data_list.controls.append(card) conn.close() self.update() except Exception as ex: self.my_page.snack_bar = ft.SnackBar( content=ft.Text(f"データ読み込みエラー: {str(ex)}"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() def edit_data(self, sale_id): try: conn = sqlite3.connect('sales.db') cursor = conn.cursor() cursor.execute(''' SELECT date, customer, product, amount FROM sales WHERE id=? ''', (sale_id,)) row = cursor.fetchone() conn.close() if row: date, customer, product, amount = row self.input_view.date_tf.value = date self.input_view.customer_tf.value = customer self.input_view.product_tf.value = product self.input_view.amount_tf.value = str(amount) self.input_view.edit_id = sale_id # 入力画面を表示 self.input_view.visible = True self.visible = False self.my_page.update() except Exception as ex: self.my_page.snack_bar = ft.SnackBar( content=ft.Text(f"データ読み込みエラー: {str(ex)}"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() def delete_data(self, sale_id): try: conn = sqlite3.connect('sales.db') cursor = conn.cursor() cursor.execute("DELETE FROM sales WHERE id=?", (sale_id,)) conn.commit() conn.close() self.my_page.snack_bar = ft.SnackBar( content=ft.Text("削除しました"), bgcolor=ft.Colors.GREEN_500 ) self.my_page.snack_bar.open = True self.my_page.update() # 一覧を更新 self.refresh() except Exception as ex: self.my_page.snack_bar = ft.SnackBar( content=ft.Text(f"削除エラー: {str(ex)}"), bgcolor=ft.Colors.RED_500 ) self.my_page.snack_bar.open = True self.my_page.update() def get_input_view(page: ft.Page, on_success): return InputView(page, on_success) def get_list_view(page: ft.Page, input_view): return ListView(page, input_view) if __name__ == "__main__": ft.app(target=main)