diff --git a/main.py b/main.py index 2822eee..c85943b 100644 --- a/main.py +++ b/main.py @@ -243,6 +243,9 @@ class FlutterStyleDashboard: self._pending_stamp_target: Optional[str] = None self._stamp_picker: Optional[ft.FilePicker] = None self.max_active_bank_accounts = 2 + self.is_explorer_controls_visible = False + self.is_search_overlay_visible = False + self._search_field: Optional[ft.TextField] = None self.edit_button_style = ft.ButtonStyle( bgcolor=ft.Colors.WHITE, color=ft.Colors.BLUE_GREY_800, @@ -366,6 +369,16 @@ class FlutterStyleDashboard: visible=False, ) self.page.overlay.append(self.settings_drawer_overlay) + self._search_field = ft.TextField( + value="", + hint_text="顧客名・伝票番号・備考を検索", + border=ft.InputBorder.NONE, + text_style=ft.TextStyle(size=16, color=ft.Colors.BLUE_GREY_900), + cursor_color=ft.Colors.BLUE_GREY_700, + on_submit=lambda e: self._apply_search_query(e.control.value), + ) + self.search_overlay = self._build_search_overlay() + self.page.overlay.append(self.search_overlay) # 初期表示 self.update_main_content() @@ -788,7 +801,9 @@ class FlutterStyleDashboard: show_back=False, show_edit=False, trailing_controls=[ - ft.IconButton(ft.Icons.REFRESH, on_click=lambda _: self.refresh_invoices()), + ft.IconButton(ft.Icons.TUNE, tooltip="詳細フィルタ", on_click=self.toggle_explorer_controls), + ft.IconButton(ft.Icons.SEARCH, tooltip="検索", on_click=self.open_search_overlay), + ft.IconButton(ft.Icons.REFRESH, tooltip="再読込", on_click=lambda _: self.refresh_invoices()), ], leading_control=settings_button, ) @@ -806,21 +821,55 @@ class FlutterStyleDashboard: 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作成完了") return result + + def refresh_invoices(self): + logging.info("refresh_invoices") + try: + self.setup_database() + self.explorer_state.offset = 0 + self.update_main_content() + self._show_snack("一覧を更新しました", ft.Colors.BLUE_GREY_600) + except Exception as e: + logging.error(f"refresh_invoices error: {e}") + self._show_snack("再読込に失敗しました", ft.Colors.RED_200) + + def toggle_explorer_controls(self, _=None): + self.is_explorer_controls_visible = not self.is_explorer_controls_visible + self.update_main_content() + + def open_search_overlay(self, _=None): + if not hasattr(self, "search_overlay"): + return + self.is_search_overlay_visible = True + self._search_field.value = self.explorer_state.query + self.search_overlay.visible = True + self.page.update() + self.page.set_focus(self._search_field) + + def close_search_overlay(self, _=None): + if not hasattr(self, "search_overlay"): + return + self.is_search_overlay_visible = False + self.search_overlay.visible = False + self.page.update() + + def _apply_search_query(self, value: Optional[str] = None): + query = (value if value is not None else self._search_field.value or "").strip() + self.explorer_state.query = query + self.explorer_state.offset = 0 + self.close_search_overlay() + self.update_main_content() + + def _prefill_search_keyword(self, keyword: str): + if not self._search_field: + return + self._search_field.value = keyword + self.page.update() + self.page.set_focus(self._search_field) @log_wrap("_build_invoice_detail_screen") def _build_invoice_detail_screen(self) -> ft.Column: @@ -1034,82 +1083,61 @@ class FlutterStyleDashboard: logging.error(f"チェーン検証エラー: {e}") explorer_controls = ft.Container( - content=ft.Column( + content=ft.Row( [ - ft.Row( - [ - ft.TextField( - label="検索", - hint_text="伝票番号 / 顧客名 / 種別 / 備考", - value=self.explorer_state.query, - prefix_icon=ft.Icons.SEARCH, - on_change=on_query_change, - expand=True, - dense=True, - ), - ft.Dropdown( - label="期間", - value=self.explorer_state.period_key, - options=[ - ft.dropdown.Option(k, v) - for k, v in EXPLORER_PERIODS.items() - ], - on_select=on_period_change, - width=140, - dense=True, - ), - ft.Dropdown( - label="ソート", - value=self.explorer_state.sort_key, - options=[ - ft.dropdown.Option(k, v) - for k, v in EXPLORER_SORTS.items() - ], - on_select=on_sort_change, - width=150, - dense=True, - ), - ft.IconButton( - icon=ft.Icons.ARROW_DOWNWARD if self.explorer_state.sort_desc else ft.Icons.ARROW_UPWARD, - tooltip="並び順切替", - on_click=on_sort_direction_toggle, - ), - ], - spacing=8, + ft.TextField( + value=self.explorer_state.query, + hint_text="", + prefix_icon=ft.Icons.SEARCH, + on_change=on_query_change, + expand=True, + dense=True, + height=38, + border=ft.InputBorder.OUTLINE, + text_style=ft.TextStyle(size=12), + content_padding=ft.Padding.symmetric(horizontal=8, vertical=0), ), - ft.Row( - [ - ft.Text("赤伝", size=10, color=ft.Colors.WHITE), - ft.Switch( - value=self.explorer_state.include_offsets, - on_change=on_toggle_offsets, - ), - ft.Container(width=10), - ft.Text("保存後詳細に留まる", size=10, color=ft.Colors.WHITE), - ft.Switch( - value=self.stay_on_detail_after_save, - on_change=lambda e: setattr(self, 'stay_on_detail_after_save', bool(e.control.value)), - ), - ft.Text( - f"表示中: {len(slips)}件 / offset={self.explorer_state.offset}", - size=12, - color=ft.Colors.BLUE_GREY_700, - ), - ft.Container(expand=True), - ft.TextButton("◀ 前", on_click=on_prev_page), - ft.TextButton("次 ▶", on_click=on_next_page), - ft.OutlinedButton("マスタ編集", on_click=self.open_master_editor), - ft.OutlinedButton("チェーン検証", on_click=on_verify_chain), + ft.Dropdown( + value=self.explorer_state.period_key, + options=[ + ft.dropdown.Option(k, v) + for k, v in EXPLORER_PERIODS.items() ], - alignment=ft.MainAxisAlignment.START, - vertical_alignment=ft.CrossAxisAlignment.CENTER, + on_select=on_period_change, + width=120, + dense=True, + border=ft.InputBorder.OUTLINE, ), + ft.Dropdown( + value=self.explorer_state.sort_key, + options=[ + ft.dropdown.Option(k, v) + for k, v in EXPLORER_SORTS.items() + ], + on_select=on_sort_change, + width=130, + dense=True, + border=ft.InputBorder.OUTLINE, + ), + ft.IconButton( + icon=ft.Icons.ARROW_DOWNWARD if self.explorer_state.sort_desc else ft.Icons.ARROW_UPWARD, + tooltip="並び順切替", + on_click=on_sort_direction_toggle, + ), + ft.Text( + f"{len(slips)}件", size=12, color=ft.Colors.BLUE_GREY_600, + ), + ft.TextButton("◀", on_click=on_prev_page, tooltip="前へ"), + ft.TextButton("▶", on_click=on_next_page, tooltip="次へ"), + ft.IconButton(ft.Icons.VERIFIED, tooltip="チェーン検証", on_click=on_verify_chain), ], - spacing=6, + spacing=8, + vertical_alignment=ft.CrossAxisAlignment.CENTER, ), - padding=ft.Padding.all(10), + padding=ft.Padding.symmetric(horizontal=10, vertical=8), bgcolor=ft.Colors.BLUE_GREY_50, border_radius=8, + visible=self.is_explorer_controls_visible, ) if not slips: