""" GPS対応顧客マスタ編集コンポーネント """ import flet as ft import sqlite3 import logging from datetime import datetime from typing import List, Dict, Optional class CustomerMasterEditorWithGPS: """GPS対応顧客マスタ編集""" def __init__(self, page: ft.Page): self.page = page # UI部品 self.title = ft.Text("GPS対応顧客マスタ", size=24, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900) # GPS情報表示 self.gps_info = ft.Text("", size=12, color=ft.Colors.GREY_600) # フォーム self.form_fields = ft.Column([], spacing=10) # ボタン群 self.save_btn = ft.Button("保存", on_click=self.save_data, bgcolor=ft.Colors.GREEN, color=ft.Colors.WHITE) self.add_btn = ft.Button("追加", on_click=self.add_new, bgcolor=ft.Colors.BLUE, color=ft.Colors.WHITE) self.delete_btn = ft.Button("削除", on_click=self.delete_selected, bgcolor=ft.Colors.RED, color=ft.Colors.WHITE) self.clear_btn = ft.Button("クリア", on_click=self.clear_form, bgcolor=ft.Colors.ORANGE, color=ft.Colors.WHITE) # GPSボタン self.gps_btn = ft.Button("GPS情報取得", on_click=self.get_gps_info, bgcolor=ft.Colors.PURPLE, color=ft.Colors.WHITE) # データリスト self.data_list = ft.Column([], scroll=ft.ScrollMode.AUTO, height=250) # 操作説明 self.instructions = ft.Text( "操作方法: 顧客情報を入力・GPS情報取得・保存・削除", size=12, color=ft.Colors.GREY_600 ) # データ読み込み self._load_data() def _build_form(self): """入力フォームを構築""" self.form_fields.controls.clear() # 基本フィールド basic_fields = [ {'name': 'name', 'label': '顧客名', 'width': 200, 'required': True}, {'name': 'phone', 'label': '電話番号', 'width': 200}, {'name': 'email', 'label': 'メールアドレス', 'width': 250}, {'name': 'address', 'label': '住所', 'width': 300} ] # GPSフィールド gps_fields = [ {'name': 'latitude', 'label': '緯度', 'width': 150}, {'name': 'longitude', 'label': '経度', 'width': 150}, {'name': 'address_gps', 'label': 'GPS住所', 'width': 300}, {'name': 'gps_timestamp', 'label': 'GPS取得日時', 'width': 200} ] # 全てのフィールドを結合 all_fields = basic_fields + gps_fields # フィールドを動的に追加 for field in all_fields: field_control = ft.TextField( label=field['label'], value='', width=field['width'] ) self.form_fields.controls.append(field_control) self.page.update() def save_data(self, e): """データを保存""" try: # フォームデータを収集 form_data = {} for i, field in enumerate(self.form_fields.controls): field_name = f"field_{i}" if i < len(basic_fields): # 基本フィールド form_data[all_fields[i]['name']] = field_control.value else: # GPSフィールド if i < len(basic_fields) + len(gps_fields): gps_field = self.form_fields.controls[i] form_data[gps_field['name']] = gps_field.value # データベースに保存 conn = sqlite3.connect('sales.db') cursor = conn.cursor() if self.editing_id: # 更新 columns = ', '.join([f"field_{i}" for i in range(len(basic_fields))]) cursor.execute(f''' UPDATE customers SET {columns} WHERE id = ? ''', tuple(form_data.values()) + (self.editing_id,)) logging.info(f"顧客データ更新完了: ID={self.editing_id}") else: # 新規追加 columns = ', '.join([f"field_{i}" for i in range(len(basic_fields))]) cursor.execute(f''' INSERT INTO customers ({columns}) VALUES ({placeholders}) ''', tuple(form_data.values())) logging.info(f"顧客データ追加完了") conn.commit() conn.close() # 成功メッセージ self._show_snackbar("顧客情報を保存しました", ft.Colors.GREEN) logging.info(f"顧客情報: {form_data}") # データ再読み込み self._load_data() except Exception as ex: logging.error(f"顧客データ保存エラー: {ex}") self._show_snackbar("保存エラー", ft.Colors.RED) def add_new(self, e): """新規顧客を追加""" self.editing_id = None self.clear_form() self.form_fields.controls[0].focus() logging.info("新規顧客追加モード") def delete_selected(self, e): """選択中の顧客を削除""" if self.editing_id: try: conn = sqlite3.connect('sales.db') cursor = conn.cursor() cursor.execute(''' DELETE FROM customers WHERE id = ? ''', (self.editing_id,)) conn.commit() conn.close() self._show_snackbar("顧客を削除しました", ft.Colors.GREEN) logging.info(f"顧客削除完了: ID={self.editing_id}") # データ再読み込み self._load_data() self.editing_id = None self.clear_form() except Exception as ex: logging.error(f"顧客削除エラー: {ex}") self._show_snackbar("削除エラー", ft.Colors.RED) def clear_form(self, e): """フォームをクリア""" for field_control in self.form_fields.controls: field_control.value = '' self.editing_id = None self.page.update() logging.info("顧客フォームをクリア") def get_gps_info(self, e): """GPS情報を取得""" try: # GPS情報を取得(モックアップ) location = geocoder.Nominatim(user_agent="MyApp/1.0") location = "東京駅, 日本" # 住所を取得 address = geocoder.geocode(location, exactly_one=True) if address: self.gps_info.value = f"緯度: {address.lat}, 経度: {address.lng}" self.gps_info.color = ft.Colors.GREEN logging.info(f"GPS情報取得成功: {address.address}") else: self.gps_info.value = "GPS情報取得失敗" self.gps_info.color = ft.Colors.RED logging.error("GPS情報取得失敗") except Exception as ex: logging.error(f"GPS情報取得エラー: {ex}") self._show_snackbar("GPSエラー", ft.Colors.RED) def _load_data(self): """顧客データを読み込む""" try: conn = sqlite3.connect('sales.db') cursor = conn.cursor() cursor.execute(''' SELECT * FROM customers ORDER BY id ''') self.current_data = cursor.fetchall() conn.close() # データリストを更新 self.data_list.controls.clear() for item in self.current_data: item_id, name, phone, email, address, latitude, longitude, address_gps, gps_timestamp, created_at = item # データ行 row_controls = [] # 基本情報 for i, field_name in enumerate(['name', 'phone', 'email', 'address']): if i < 4: # 基本フィールド value = item[i+1] if i < len(item) else '' else: # GPSフィールド if i == 4: # address_gps value = item[i+1] else: value = '' else: value = item[i+1] row_controls.append( ft.Text(f"{['name']}: {value}", size=12) ) # GPS情報 if item.get('latitude') and item.get('longitude'): gps_text = f"GPS: {item.get('latitude', '')}, {item.get('longitude', '')}" gps_color = ft.Colors.GREEN row_controls.append( ft.Text(gps_text, size=10, color=gps_color) ) # 操作ボタン row_controls.extend([ ft.Button( "編集", on_click=lambda _, did=item_id: self.edit_item(item_id), bgcolor=ft.Colors.ORANGE, color=ft.Colors.WHITE, width=60 ), ft.Button( "削除", on_click=lambda _, did=item_id: self.delete_item(item_id), bgcolor=ft.Colors.RED, color=ft.Colors.WHITE, width=60 ) ]) # データカード data_card = ft.Card( content=ft.Container( content=ft.Column(row_controls), padding=10 ), margin=ft.margin.only(bottom=5) ) self.data_list.controls.append(data_card) self.page.update() except Exception as e: logging.error(f"顧客データ読込エラー: {e}") def _show_snackbar(self, message: str, color: ft.Colors): """SnackBarを表示""" try: self.page.snack_bar = ft.SnackBar( content=ft.Text(message), bgcolor=color ) self.page.snack_bar.open = True self.page.update() except: pass def build(self): """UIを構築して返す""" return ft.Column([ self.title, self.gps_info, ft.Divider(), ft.Text("入力フォーム", size=18, weight=ft.FontWeight.BOLD), self.form_fields, ft.Row([self.save_btn, self.add_btn, self.delete_btn, self.clear_btn, self.gps_btn], spacing=10), ft.Divider(), ft.Text("顧客一覧", size=18, weight=ft.FontWeight.BOLD), self.instructions, self.data_list ], expand=True, spacing=15) # 使用例 def create_customer_master_with_gps(page: ft.Page) -> CustomerMasterEditorWithGPS: """GPS対応顧客マスタ編集画面を作成""" return CustomerMasterEditorWithGPS(page)