diff --git a/components/editor_framework.py b/components/editor_framework.py index c215c59..0f09a53 100644 --- a/components/editor_framework.py +++ b/components/editor_framework.py @@ -111,10 +111,13 @@ def build_invoice_items_edit_table( products: List[Product], on_product_select: Callable[[int], None] | None = None, row_refs: Optional[dict] = None, + enable_reorder: bool = False, + on_reorder: Optional[Callable[[int, int], None]] = None, ) -> ft.Column: """編集モードの明細テーブル。""" header_row = ft.Row( [ + ft.Container(width=28), ft.Text("商品", size=12, weight=ft.FontWeight.BOLD, width=180), ft.Text("数", size=12, weight=ft.FontWeight.BOLD, width=35), ft.Text("単価", size=12, weight=ft.FontWeight.BOLD, width=70), @@ -190,20 +193,40 @@ def build_invoice_items_edit_table( "subtotal": subtotal_text, } - data_rows.append( - ft.Row( - [ - product_field, - quantity_field, - unit_price_field, - subtotal_text, - delete_button, - ], - key=f"row-{i}-{item.description}", - ) + handle = ft.Icon( + ft.Icons.DRAG_HANDLE, + size=18, + color=ft.Colors.BLUE_GREY_400, + visible=enable_reorder and not is_locked, ) - list_control: ft.Control = ft.Column(data_rows, spacing=4) + row_control = ft.Row( + [ + handle, + product_field, + quantity_field, + unit_price_field, + subtotal_text, + delete_button, + ], + vertical_alignment=ft.CrossAxisAlignment.CENTER, + key=f"row-{i}-{item.description}", + ) + + data_rows.append(row_control) + + if enable_reorder and on_reorder and not is_locked: + reorder_items = [ + ft.ReorderableListViewItem(key=str(idx), content=row) + for idx, row in enumerate(data_rows) + ] + list_control = ft.ReorderableListView( + controls=reorder_items, + on_reorder=lambda e: on_reorder(e.old_index, e.new_index), + shrink_wrap=True, + ) + else: + list_control = ft.Column(data_rows, spacing=4) return ft.Column( [ diff --git a/main.py b/main.py index c575d58..7682ef8 100644 --- a/main.py +++ b/main.py @@ -205,48 +205,9 @@ class FlutterStyleDashboard: # 保存後遷移設定(True: 詳細に留まる, False: 一覧へ戻る) self.stay_on_detail_after_save = True - self.invoice_card_theme = { - "page_bg": "#F3F2FB", - "card_bg": ft.Colors.WHITE, - "card_radius": 18, - "shadow": ft.BoxShadow( - blur_radius=16, - spread_radius=0, - color="#D5D8F0", - offset=ft.Offset(0, 6), - ), - "icon_fg": ft.Colors.WHITE, - "icon_default_bg": "#5C6BC0", - "title_color": ft.Colors.BLUE_GREY_900, - "subtitle_color": ft.Colors.BLUE_GREY_500, - "amount_color": "#2F3C7E", - "tag_text_color": "#4B4F67", - "tag_bg": "#E7E9FB", - "draft_card_bg": "#F5EEE4", - "draft_border": "#D7C4AF", - "draft_badge_bg": "#A7743A", - "draft_shadow_highlight": ft.BoxShadow( - blur_radius=8, - spread_radius=0, - color="#FFFFFF", - offset=ft.Offset(-2, -2), - ), - "draft_shadow_depth": ft.BoxShadow( - blur_radius=14, - spread_radius=2, - color="#C3A88C", - offset=ft.Offset(4, 6), - ), - "badge_bg": "#35C46B", - } - self.doc_type_palette = { - DocumentType.INVOICE.value: "#5C6BC0", - DocumentType.ESTIMATE.value: "#7E57C2", - DocumentType.DELIVERY.value: "#26A69A", - DocumentType.RECEIPT.value: "#FF7043", - DocumentType.SALES.value: "#42A5F5", - DocumentType.DRAFT.value: "#90A4AE", - } + self.current_theme = "light" + self.theme_presets = self._build_theme_presets() + self.apply_theme(self.current_theme) # ビジネスロジックサービス self.app_service = AppService() @@ -254,6 +215,8 @@ class FlutterStyleDashboard: self.customers = [] self._item_row_refs: Dict[int, Dict[str, ft.Control]] = {} self._total_amount_text: Optional[ft.Text] = None + self._tax_amount_text: Optional[ft.Text] = None + self._subtotal_text: Optional[ft.Text] = None self.setup_page() self.setup_database() @@ -1657,6 +1620,31 @@ class FlutterStyleDashboard: else: draft_control = ft.Container() + date_time_row = ft.Row( + [ + date_button if date_button else ft.Text( + self.editing_invoice.date.strftime("%Y/%m/%d"), + size=12, + color=ft.Colors.BLUE_GREY_600, + ), + time_button if time_button else ft.Text( + self.editing_invoice.date.strftime("%H:%M"), + size=12, + color=ft.Colors.BLUE_GREY_600, + ), + ], + spacing=8, + ) + + customer_block = ft.Column( + [ + customer_control, + date_time_row, + ], + spacing=4, + alignment=ft.MainAxisAlignment.START, + ) + summary_card = ft.Container( content=ft.Column( [ @@ -1690,25 +1678,11 @@ class FlutterStyleDashboard: ), ft.Row( [ - customer_control, - self._build_total_amount_text(), + customer_block, + self._build_totals_row(), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, - ), - ft.Row( - [ - date_button if date_button else ft.Text( - self.editing_invoice.date.strftime("%Y/%m/%d"), - size=12, - color=ft.Colors.BLUE_GREY_600, - ), - time_button if time_button else ft.Text( - self.editing_invoice.date.strftime("%H:%M"), - size=12, - color=ft.Colors.BLUE_GREY_600, - ), - ], - spacing=12, + vertical_alignment=ft.CrossAxisAlignment.CENTER, ), summary_badges, ], @@ -1968,6 +1942,8 @@ class FlutterStyleDashboard: products=self.app_service.product.get_all_products(), on_product_select=self._open_product_picker_for_row, row_refs=self._ensure_item_row_refs(), + enable_reorder=not is_locked, + on_reorder=lambda old, new: self._reorder_item_row(old, new), ) def _open_product_picker_for_row(self, item_index: int): @@ -2085,21 +2061,33 @@ class FlutterStyleDashboard: self._close_dialog() self.update_main_content() - def _build_total_amount_text(self) -> ft.Text: - total = self.editing_invoice.total_amount if self.editing_invoice else 0 + def _build_totals_row(self) -> ft.Column: + subtotal = self.editing_invoice.subtotal if self.editing_invoice else 0 + tax = self.editing_invoice.tax if self.editing_invoice else 0 + total = subtotal + tax + + self._tax_amount_text = ft.Text(f"消費税 ¥{tax:,}", size=12, color=ft.Colors.BLUE_GREY_700) self._total_amount_text = ft.Text( - f"¥{total:,} (税込)", - size=15, - weight=ft.FontWeight.BOLD, - color=ft.Colors.BLUE_700, + f"合計 ¥{total:,}", size=15, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_700 + ) + + return ft.Column( + [ + self._tax_amount_text, + self._total_amount_text, + ], + spacing=2, + alignment=ft.MainAxisAlignment.END, ) - return self._total_amount_text def _refresh_total_amount_display(self): - if not self._total_amount_text: + if not self.editing_invoice: return - total = self.editing_invoice.total_amount if self.editing_invoice else 0 - self._total_amount_text.value = f"¥{total:,} (税込)" + if self._tax_amount_text: + self._tax_amount_text.value = f"消費税 ¥{self.editing_invoice.tax:,}" + if self._total_amount_text: + total = self.editing_invoice.total_amount + self._total_amount_text.value = f"合計 ¥{total:,}" def _ensure_item_row_refs(self) -> Dict[int, Dict[str, ft.Control]]: self._item_row_refs = {}