タイトルバーに新規ボタンを
This commit is contained in:
parent
27fb3d7286
commit
a1e82e0714
1 changed files with 122 additions and 34 deletions
156
main.py
156
main.py
|
|
@ -7,6 +7,8 @@ import flet as ft
|
|||
import signal
|
||||
import sys
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Optional
|
||||
|
|
@ -72,7 +74,7 @@ class AppBar(ft.Container):
|
|||
|
||||
def __init__(self, title: str, show_back: bool = False, show_edit: bool = False,
|
||||
on_back=None, on_edit=None, page=None, action_icon=None, action_tooltip: str = "編集",
|
||||
bottom: Optional[ft.Control] = None):
|
||||
bottom: Optional[ft.Control] = None, trailing_controls: Optional[list] = None):
|
||||
super().__init__()
|
||||
self.title = title
|
||||
self.show_back = show_back
|
||||
|
|
@ -83,6 +85,7 @@ class AppBar(ft.Container):
|
|||
self.action_icon = action_icon or ft.Icons.EDIT
|
||||
self.action_tooltip = action_tooltip
|
||||
self.bottom = bottom
|
||||
self.trailing_controls = trailing_controls or []
|
||||
|
||||
self.bgcolor = ft.Colors.BLUE_GREY_50
|
||||
self.padding = ft.Padding.symmetric(horizontal=16, vertical=8)
|
||||
|
|
@ -130,6 +133,10 @@ class AppBar(ft.Container):
|
|||
on_click=self.on_edit if self.on_edit else None
|
||||
)
|
||||
)
|
||||
elif self.trailing_controls:
|
||||
controls.append(
|
||||
ft.Row(self.trailing_controls, spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER)
|
||||
)
|
||||
else:
|
||||
controls.append(ft.Container(width=48)) # スペーサー
|
||||
|
||||
|
|
@ -162,6 +169,22 @@ class FlutterStyleDashboard:
|
|||
self.is_customer_picker_open = False
|
||||
self.customer_search_query = ""
|
||||
self.show_offsets = False
|
||||
self._suppress_tap_after_long_press = False
|
||||
self._toast_text = ft.Text("", color=ft.Colors.WHITE, size=12)
|
||||
self._toast = ft.Container(
|
||||
opacity=0,
|
||||
visible=False,
|
||||
animate_opacity=300,
|
||||
content=ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Icon(ft.Icons.INFO, size=16, color=ft.Colors.WHITE70),
|
||||
self._toast_text,
|
||||
], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER),
|
||||
bgcolor=ft.Colors.BLUE_GREY_800,
|
||||
padding=ft.Padding.symmetric(horizontal=12, vertical=8),
|
||||
border_radius=8,
|
||||
),
|
||||
)
|
||||
self.chain_verify_result = None
|
||||
self.is_new_customer_form_open = False
|
||||
self.master_editor: Optional[UniversalMasterEditor] = None
|
||||
|
|
@ -351,7 +374,18 @@ class FlutterStyleDashboard:
|
|||
app_bar = AppBar(
|
||||
title="伝票一覧",
|
||||
show_back=False,
|
||||
show_edit=False
|
||||
show_edit=False,
|
||||
trailing_controls=[
|
||||
ft.Button(
|
||||
content=ft.Row([
|
||||
ft.Icon(ft.Icons.ADD),
|
||||
ft.Text("新規伝票"),
|
||||
], spacing=6),
|
||||
on_click=lambda _: self.start_new_invoice(),
|
||||
height=36,
|
||||
style=ft.ButtonStyle(padding=ft.Padding.symmetric(horizontal=10, vertical=0)),
|
||||
)
|
||||
],
|
||||
)
|
||||
logging.info("_build_invoice_list_screen: AppBar作成完了")
|
||||
|
||||
|
|
@ -361,11 +395,23 @@ class FlutterStyleDashboard:
|
|||
|
||||
result = ft.Column([
|
||||
app_bar,
|
||||
ft.Container(content=self._toast, padding=ft.Padding.symmetric(horizontal=16, vertical=4)),
|
||||
ft.Container(
|
||||
content=invoice_list,
|
||||
expand=True,
|
||||
padding=ft.Padding.all(16)
|
||||
),
|
||||
# デバッグ用: スナック表示テストボタン(一覧画面下部)
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Button(
|
||||
content=ft.Text("スナックテストを表示"),
|
||||
on_click=lambda _: self._show_snack("テストスナック"),
|
||||
),
|
||||
ft.Text("コンソールに 'show_snack: テストスナック' が出ればハンドラは動作", size=12, color=ft.Colors.BLUE_GREY_600),
|
||||
], spacing=12),
|
||||
padding=ft.Padding.symmetric(horizontal=16, vertical=8),
|
||||
),
|
||||
], expand=True)
|
||||
|
||||
logging.info("_build_invoice_list_screen: Column作成完了")
|
||||
|
|
@ -377,6 +423,10 @@ class FlutterStyleDashboard:
|
|||
if not self.editing_invoice:
|
||||
return ft.Column([ft.Text("伝票が選択されていません")])
|
||||
|
||||
# is_edit_mode が立っている場合は強制的に編集モードにする
|
||||
if getattr(self, 'is_edit_mode', False):
|
||||
self.is_detail_edit_mode = True
|
||||
|
||||
is_locked = getattr(self.editing_invoice, 'final_locked', False)
|
||||
is_view_mode = not getattr(self, 'is_detail_edit_mode', False)
|
||||
app_bar = AppBar(
|
||||
|
|
@ -394,6 +444,7 @@ class FlutterStyleDashboard:
|
|||
|
||||
return ft.Column([
|
||||
app_bar,
|
||||
ft.Container(content=self._toast, padding=ft.Padding.symmetric(horizontal=16, vertical=4)),
|
||||
body,
|
||||
], expand=True)
|
||||
|
||||
|
|
@ -715,6 +766,9 @@ class FlutterStyleDashboard:
|
|||
display_amount = -abs(amount) if isinstance(slip, Invoice) and getattr(slip, "is_offset", False) else amount
|
||||
|
||||
def on_single_tap(_):
|
||||
if self._suppress_tap_after_long_press:
|
||||
self._suppress_tap_after_long_press = False
|
||||
return
|
||||
if isinstance(slip, Invoice):
|
||||
self.open_invoice_detail(slip)
|
||||
|
||||
|
|
@ -725,6 +779,7 @@ class FlutterStyleDashboard:
|
|||
def on_long_press(_):
|
||||
# 長押しは直接編集画面へ(ビューワではなく編集)
|
||||
logging.info(f"long_press -> open_invoice_edit: {getattr(slip, 'invoice_number', 'unknown')}")
|
||||
self._suppress_tap_after_long_press = True
|
||||
self.open_invoice_edit(slip)
|
||||
|
||||
show_offset_button = isinstance(slip, Invoice) and self.can_create_offset_invoice(slip)
|
||||
|
|
@ -967,48 +1022,49 @@ class FlutterStyleDashboard:
|
|||
border_radius=8,
|
||||
)
|
||||
|
||||
def open_invoice_edit(self, invoice: Invoice):
|
||||
"""伝票編集画面を開く"""
|
||||
self.editing_invoice = invoice
|
||||
self.is_edit_mode = True
|
||||
self.selected_customer = invoice.customer
|
||||
self.selected_document_type = invoice.document_type
|
||||
self.amount_value = str(invoice.items[0].unit_price if invoice.items else "0")
|
||||
self.is_detail_edit_mode = True # 編集モードで開く
|
||||
self.is_customer_picker_open = False
|
||||
self.is_new_customer_form_open = False
|
||||
self.current_tab = 1 # 詳細編集タブに切り替え
|
||||
self.update_main_content()
|
||||
|
||||
def open_new_customer_form(self):
|
||||
"""新規顧客フォームを開く(画面内遷移)"""
|
||||
self.is_new_customer_form_open = True
|
||||
self.update_main_content()
|
||||
|
||||
def start_new_invoice(self, _=None):
|
||||
"""新規伝票作成ボタンから呼ばれる入口"""
|
||||
self.editing_invoice = None
|
||||
self._init_new_invoice()
|
||||
|
||||
def create_invoice_edit_screen(self) -> ft.Container:
|
||||
"""伝票編集画面(新規・編集統合)"""
|
||||
# 常に詳細編集画面を使用
|
||||
if not self.editing_invoice:
|
||||
# 新規伝票の場合は空のInvoiceを作成
|
||||
from models.invoice_models import Invoice, Customer, DocumentType
|
||||
default_customer = Customer(
|
||||
id=0,
|
||||
name="選択してください",
|
||||
formal_name="選択してください",
|
||||
address="",
|
||||
phone=""
|
||||
)
|
||||
self.editing_invoice = Invoice(
|
||||
customer=default_customer,
|
||||
date=datetime.now(),
|
||||
items=[],
|
||||
document_type=DocumentType.SALES,
|
||||
invoice_number="NEW-" + str(int(datetime.now().timestamp())) # 新規伝票番号
|
||||
)
|
||||
self.is_detail_edit_mode = True # 新規作成モード
|
||||
self._init_new_invoice()
|
||||
|
||||
# 既存・新規共通で詳細編集画面を返す
|
||||
return self._create_edit_existing_screen()
|
||||
|
||||
def _init_new_invoice(self):
|
||||
"""新規伝票オブジェクトを準備"""
|
||||
default_customer = Customer(
|
||||
id=0,
|
||||
name="選択してください",
|
||||
formal_name="選択してください",
|
||||
address="",
|
||||
phone=""
|
||||
)
|
||||
self.editing_invoice = Invoice(
|
||||
customer=default_customer,
|
||||
date=datetime.now(),
|
||||
items=[],
|
||||
document_type=DocumentType.DRAFT,
|
||||
invoice_number="NEW-" + str(int(datetime.now().timestamp()))
|
||||
)
|
||||
self.selected_customer = default_customer
|
||||
self.selected_document_type = DocumentType.DRAFT
|
||||
self.is_detail_edit_mode = True
|
||||
self.is_edit_mode = True
|
||||
self.is_customer_picker_open = False
|
||||
self.is_new_customer_form_open = False
|
||||
self.current_tab = 1
|
||||
self.update_main_content()
|
||||
|
||||
def _create_edit_existing_screen(self) -> ft.Container:
|
||||
"""既存伝票の編集画面(新規・編集共通)"""
|
||||
|
|
@ -1286,6 +1342,8 @@ class FlutterStyleDashboard:
|
|||
self._show_snack("伝票を更新しました", ft.Colors.GREEN_600)
|
||||
# 一覧データは更新
|
||||
self.invoices = self.app_service.invoice.get_recent_invoices(20)
|
||||
# 保存後の顧客選択状態を保持
|
||||
self.selected_customer = getattr(self.editing_invoice, "customer", None)
|
||||
# 設定により遷移先を変更
|
||||
if not self.stay_on_detail_after_save:
|
||||
self.editing_invoice = None
|
||||
|
|
@ -1627,16 +1685,41 @@ class FlutterStyleDashboard:
|
|||
|
||||
def _show_snack(self, message: str, color=ft.Colors.BLUE_GREY_800):
|
||||
try:
|
||||
logging.info(f"show_snack: {message}")
|
||||
snack = ft.SnackBar(content=ft.Text(message), bgcolor=color)
|
||||
# attach to page to ensure it's rendered even if show_snack_bar is ignored
|
||||
self.page.snack_bar = snack
|
||||
# prefer show_snack_bar API if available (more reliable on web)
|
||||
if hasattr(self.page, "show_snack_bar"):
|
||||
self.page.show_snack_bar(snack)
|
||||
else:
|
||||
self.page.snack_bar = snack
|
||||
self.page.snack_bar.open = True
|
||||
self.page.update()
|
||||
# ensure open flag is set for both paths
|
||||
if hasattr(self.page, "snack_bar"):
|
||||
self.page.snack_bar.open = True
|
||||
# in-app toast fallback (必ず画面に表示される)
|
||||
if self._toast is not None:
|
||||
self._toast_text.value = message
|
||||
self._toast.content.bgcolor = color if color else ft.Colors.BLUE_GREY_800
|
||||
self._toast.visible = True
|
||||
self._toast.opacity = 1
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.call_later(3, self._hide_toast)
|
||||
except RuntimeError:
|
||||
threading.Timer(3, self._hide_toast).start()
|
||||
self.page.update()
|
||||
except Exception as e:
|
||||
logging.warning(f"snack_bar表示失敗: {e}")
|
||||
|
||||
def _hide_toast(self):
|
||||
try:
|
||||
if self._toast is not None:
|
||||
self._toast.opacity = 0
|
||||
self._toast.visible = False
|
||||
self.page.update()
|
||||
except Exception as e:
|
||||
logging.warning(f"toast hide failed: {e}")
|
||||
def create_new_customer_screen(self) -> ft.Container:
|
||||
"""新規/既存顧客登録・編集画面"""
|
||||
editing_customer = getattr(self, "editing_customer_for_form", None)
|
||||
|
|
@ -1667,6 +1750,11 @@ class FlutterStyleDashboard:
|
|||
self.selected_customer = editing_customer
|
||||
if self.editing_invoice:
|
||||
self.editing_invoice.customer = editing_customer
|
||||
# 既存伝票一覧も更新しておく(顧客名がすぐ反映されるように)
|
||||
try:
|
||||
self.invoices = self.app_service.invoice.get_recent_invoices(20)
|
||||
except Exception:
|
||||
pass
|
||||
self._show_snack("顧客を更新しました", ft.Colors.GREEN_600)
|
||||
self.editing_customer_for_form = None
|
||||
self.is_customer_picker_open = False
|
||||
|
|
|
|||
Loading…
Reference in a new issue