1130 lines
45 KiB
Python
1130 lines
45 KiB
Python
import flet as ft
|
||
import sqlite3
|
||
from typing import List, Callable
|
||
from database.db_manager import DatabaseManager
|
||
|
||
class FaultFinderUI:
|
||
"""고장코드 검색 UI를 관리하는 클래스입니다."""
|
||
|
||
def __init__(self, page: ft.Page, db_manager: DatabaseManager):
|
||
"""
|
||
FaultFinderUI를 초기화합니다.
|
||
|
||
Args:
|
||
page (ft.Page): Flet 페이지 객체
|
||
db_manager (DatabaseManager): 데이터베이스 관리자 인스턴스
|
||
"""
|
||
self.page = page
|
||
self.db_manager = db_manager
|
||
self.current_view = "fault_code" # (fault_code, abbreviation, drawing, signal)
|
||
self.custom_dialog_overlay = None # 커스텀 다이얼로그 오버레이 변수
|
||
|
||
# 디버그 모드 설정
|
||
self.debug = True
|
||
|
||
# 테마 기본: 라이트 모드
|
||
self.theme = "light"
|
||
|
||
# 페이지 리사이즈 이벤트 핸들러 등록 (헤더/행 폭 갱신)
|
||
self.page.on_resize = self._on_page_resize
|
||
# 키보드 이벤트 핸들러 (뒤로가기 키 지원)
|
||
self.page.on_keyboard_event = self._on_keyboard_event
|
||
|
||
# 제작사 목록
|
||
self.manufacturers = self._get_manufacturers()
|
||
default_manufacturer = "우진"
|
||
if default_manufacturer in self.manufacturers:
|
||
default_index = self.manufacturers.index(default_manufacturer)
|
||
else:
|
||
default_index = 0
|
||
|
||
# 설정 버튼
|
||
self.settings_button = ft.IconButton(
|
||
icon=ft.icons.SETTINGS,
|
||
tooltip="설정",
|
||
on_click=self._show_settings_dialog
|
||
)
|
||
|
||
# 제작사 필터
|
||
self.manufacturer_dropdown = ft.Dropdown(
|
||
label="제작사 선택",
|
||
options=[ft.dropdown.Option(manufacturer) for manufacturer in self.manufacturers],
|
||
on_change=self._on_manufacturer_change,
|
||
value=self.manufacturers[default_index] if self.manufacturers else None,
|
||
expand=True
|
||
)
|
||
|
||
# 제작사 필터와 설정 버튼 Row
|
||
self.manufacturer_row = ft.Row([
|
||
self.manufacturer_dropdown,
|
||
self.settings_button
|
||
], spacing=10)
|
||
|
||
# 고장 타입 목록
|
||
self.fault_types = self._get_fault_types()
|
||
if "전체" not in self.fault_types:
|
||
self.fault_types.insert(0, "전체")
|
||
|
||
# 고장 타입 드롭다운
|
||
self.type_dropdown = ft.Dropdown(
|
||
label="장치분류 선택",
|
||
options=[ft.dropdown.Option(type_) for type_ in self.fault_types],
|
||
on_change=self._on_type_change,
|
||
expand=False,
|
||
width=300,
|
||
value="전체"
|
||
)
|
||
|
||
# 뷰 전환 탭
|
||
self.view_tabs = ft.Tabs(
|
||
selected_index=0,
|
||
on_change=self._on_tabs_change,
|
||
tabs=[
|
||
ft.Tab(text="고장", icon=ft.icons.ERROR_OUTLINE),
|
||
ft.Tab(text="약어", icon=ft.icons.MENU_BOOK),
|
||
ft.Tab(text="도면", icon=ft.icons.DOCUMENT_SCANNER),
|
||
ft.Tab(text="신호", icon=ft.icons.SIGNAL_CELLULAR_ALT)
|
||
]
|
||
)
|
||
|
||
# 고장 목록 헤더 (비율: Code 20%, 고장명 60%, Type 20%)
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
self.header_fault = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text("Code", weight=ft.FontWeight.BOLD), width=code_w),
|
||
ft.Container(content=ft.Text("f.Name", weight=ft.FontWeight.BOLD), width=name_w),
|
||
ft.Container(content=ft.Text("Type", weight=ft.FontWeight.BOLD), width=type_w)
|
||
],
|
||
spacing=10
|
||
),
|
||
bgcolor=ft.colors.BLUE_GREY_100,
|
||
padding=10,
|
||
border_radius=5,
|
||
visible=True
|
||
)
|
||
# 고장목록 내용: 세로 스크롤 ListView 사용
|
||
fault_content = ft.Container(
|
||
content=ft.ListView(
|
||
controls=[], # 데이터 행들이 추가됨
|
||
spacing=2,
|
||
auto_scroll=True
|
||
),
|
||
# 높이 조정 - 비율 기반
|
||
height=300,
|
||
expand=False, # 무한 확장 방지
|
||
padding=5,
|
||
visible=True,
|
||
clip_behavior=ft.ClipBehavior.NONE
|
||
)
|
||
fault_code_view = ft.Column(
|
||
controls=[self.header_fault, fault_content],
|
||
visible=True,
|
||
expand=True # 확장 속성 추가
|
||
)
|
||
|
||
# (약어, 도면, 신호 목록은 기존 코드와 동일하게 구성)
|
||
abbreviation_view = ft.Column([
|
||
ft.Container(
|
||
content=ft.Row([
|
||
ft.Text("약어", weight=ft.FontWeight.BOLD, width=150),
|
||
ft.Text("용어설명", weight=ft.FontWeight.BOLD, width=350)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.BLUE_GREY_100,
|
||
padding=10,
|
||
border_radius=5,
|
||
visible=True
|
||
),
|
||
ft.Container(
|
||
content=ft.ListView(
|
||
controls=[],
|
||
spacing=2,
|
||
auto_scroll=True
|
||
),
|
||
height=300, # 높이 조정
|
||
expand=False, # 무한 확장 방지
|
||
padding=5,
|
||
visible=True
|
||
)
|
||
], visible=False, expand=True)
|
||
|
||
drawing_view = ft.Column([
|
||
ft.Container(
|
||
content=ft.Row([
|
||
ft.Text("도면코드", weight=ft.FontWeight.BOLD, width=150),
|
||
ft.Text("도면명", weight=ft.FontWeight.BOLD, width=200),
|
||
ft.Text("관련장치", weight=ft.FontWeight.BOLD, width=150)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.BLUE_GREY_100,
|
||
padding=10,
|
||
border_radius=5,
|
||
visible=True
|
||
),
|
||
ft.Container(
|
||
content=ft.ListView(
|
||
controls=[],
|
||
spacing=2,
|
||
auto_scroll=True
|
||
),
|
||
height=300, # 높이 조정
|
||
expand=False, # 무한 확장 방지
|
||
padding=5,
|
||
visible=True
|
||
)
|
||
], visible=False, expand=True)
|
||
|
||
signal_view = ft.Column([
|
||
ft.Container(
|
||
content=ft.Row([
|
||
ft.Text("신호코드", weight=ft.FontWeight.BOLD, width=150),
|
||
ft.Text("신호명", weight=ft.FontWeight.BOLD, width=200),
|
||
ft.Text("신호타입", weight=ft.FontWeight.BOLD, width=150)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.BLUE_GREY_100,
|
||
padding=10,
|
||
border_radius=5,
|
||
visible=True
|
||
),
|
||
ft.Container(
|
||
content=ft.ListView(
|
||
controls=[],
|
||
spacing=2,
|
||
auto_scroll=True
|
||
),
|
||
height=300, # 높이 조정
|
||
expand=False, # 무한 확장 방지
|
||
padding=5,
|
||
visible=True
|
||
)
|
||
], visible=False, expand=True)
|
||
|
||
# 중앙 리스트 컨테이너 (첫 번째 자식은 고장코드 뷰)
|
||
self.list_container = ft.Container(
|
||
content=ft.Column([
|
||
fault_code_view,
|
||
abbreviation_view,
|
||
drawing_view,
|
||
signal_view
|
||
], expand=True),
|
||
expand=True,
|
||
border=ft.border.all(1, ft.colors.GREY_300),
|
||
border_radius=10,
|
||
padding=10,
|
||
# 높이를 더 작게 제한
|
||
height=350, # 검색 필드 표시를 위해 줄임
|
||
clip_behavior=ft.ClipBehavior.NONE
|
||
)
|
||
|
||
# 검색 필드
|
||
self.search_field = ft.TextField(
|
||
label="검색어 입력",
|
||
hint_text="검색어를 입력하세요",
|
||
expand=True,
|
||
on_change=self._on_search_change,
|
||
prefix_icon=ft.icons.SEARCH
|
||
)
|
||
|
||
# 장치분류 영역
|
||
self.type_container = ft.Container(
|
||
content=self.type_dropdown,
|
||
padding=5,
|
||
bgcolor=ft.colors.BLUE_GREY_50,
|
||
border_radius=10,
|
||
visible=True
|
||
)
|
||
|
||
# 검색 필드 - 별도 컨테이너로 구성
|
||
self.search_container = ft.Container(
|
||
content=self.search_field,
|
||
padding=10,
|
||
bgcolor=ft.colors.BLUE_GREY_50,
|
||
border_radius=10,
|
||
height=50,
|
||
visible=True
|
||
)
|
||
|
||
# 메인 컨테이너 구성 - 레이아웃 단순화
|
||
self.container = ft.Container(
|
||
content=ft.Column([
|
||
# 상단 제작사 선택 영역
|
||
ft.Container(
|
||
content=self.manufacturer_row,
|
||
padding=10,
|
||
bgcolor=ft.colors.BLUE_GREY_50,
|
||
border_radius=10
|
||
),
|
||
# 탭 영역
|
||
ft.Container(
|
||
content=self.view_tabs,
|
||
padding=10,
|
||
bgcolor=ft.colors.BLUE_GREY_50,
|
||
border_radius=10
|
||
),
|
||
# 장치분류 선택
|
||
self.type_container,
|
||
# 리스트 컨테이너
|
||
self.list_container,
|
||
# 검색 필드 - 새 컨테이너 참조
|
||
self.search_container
|
||
]),
|
||
expand=True,
|
||
padding=10
|
||
)
|
||
|
||
# 초기 데이터 로딩
|
||
# 디버그 모드 활성화 (문제 해결을 위해)
|
||
self.debug = True
|
||
|
||
# 기본 뷰를 fault_code로 설정하고 visibility 확인
|
||
self.current_view = "fault_code"
|
||
self.list_container.content.controls[0].visible = True
|
||
self.list_container.content.controls[1].visible = False
|
||
self.list_container.content.controls[2].visible = False
|
||
self.list_container.content.controls[3].visible = False
|
||
|
||
# 페이지 크기에 따라 컴포넌트 크기 최적화 (모든 UI 컴포넌트 초기화 후 호출)
|
||
self._adjust_layout_for_page_size()
|
||
|
||
# 최초 실행 시 모든 고장 코드 로드
|
||
self.load_all_codes()
|
||
|
||
# 디버그 출력
|
||
if self.debug:
|
||
print("UI 초기화 완료")
|
||
print(f"현재 뷰: {self.current_view}")
|
||
print(f"제작사 목록: {self.manufacturers}")
|
||
print(f"fault_code_view 표시 상태: {self.list_container.content.controls[0].visible}")
|
||
|
||
def get_fault_list_column_widths(self):
|
||
"""
|
||
페이지의 width를 기준으로 각 열의 너비를 계산.
|
||
페이지 너비가 0인 경우 기본값 800을 사용.
|
||
"""
|
||
total_width = self.page.width if self.page.width > 0 else 800
|
||
# 약간의 여유 공간(패딩 등)을 뺀 후 계산 (여기서는 40픽셀 가정)
|
||
available_width = total_width - 40
|
||
code_width = int(available_width * 0.2)
|
||
name_width = int(available_width * 0.6)
|
||
type_width = int(available_width * 0.2)
|
||
return code_width, name_width, type_width
|
||
|
||
def _on_page_resize(self, e):
|
||
"""페이지 리사이즈 시 헤더와 각 행의 열 폭 재계산"""
|
||
# 레이아웃을 페이지 크기에 맞게 조정
|
||
self._adjust_layout_for_page_size()
|
||
|
||
# 열 너비 업데이트
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
|
||
# 헤더 업데이트
|
||
header_row = self.header_fault.content
|
||
header_row.controls[0].width = code_w
|
||
header_row.controls[1].width = name_w
|
||
header_row.controls[2].width = type_w
|
||
|
||
# 각 행 업데이트
|
||
fault_list_view = self.list_container.content.controls[0].controls[1].content
|
||
for row in fault_list_view.controls:
|
||
inner_row = row.content
|
||
inner_row.controls[0].width = code_w
|
||
inner_row.controls[1].width = name_w
|
||
inner_row.controls[2].width = type_w
|
||
|
||
self.page.update()
|
||
|
||
def _adjust_layout_for_page_size(self):
|
||
"""페이지 크기에 따라 레이아웃 조정"""
|
||
page_height = self.page.height if self.page.height > 0 else 800
|
||
|
||
# 리스트 컨테이너 높이를 페이지 높이의 60%로 설정
|
||
list_height = min(max(int(page_height * 0.6), 300), 600)
|
||
self.list_container.height = list_height
|
||
|
||
# 데이터 리스트 높이 설정 (리스트 컨테이너 높이의 80%)
|
||
content_height = int(list_height * 0.8)
|
||
|
||
# 각 뷰의 내부 ListView 컨테이너 높이 설정
|
||
for i in range(4): # 4개 뷰: fault_code, abbreviation, drawing, signal
|
||
if i < len(self.list_container.content.controls):
|
||
view = self.list_container.content.controls[i]
|
||
if len(view.controls) > 1: # 헤더 + 콘텐츠
|
||
view.controls[1].height = content_height
|
||
|
||
if self.debug:
|
||
print(f"레이아웃 조정: 페이지 높이={page_height}, 리스트 높이={list_height}, 콘텐츠 높이={content_height}")
|
||
|
||
def _on_keyboard_event(self, e: ft.KeyboardEvent):
|
||
"""키보드 이벤트 처리: Escape 또는 Back 키 입력 시 다이얼로그 닫기"""
|
||
if e.key in ("Escape", "Back") and self.custom_dialog_overlay:
|
||
self._close_custom_dialog()
|
||
|
||
|
||
def _show_custom_dialog(self, title: ft.Control, content: ft.Control, actions: List[ft.Control]):
|
||
"""커스텀 다이얼로그 오버레이 생성, 스타일 적용 및 표시
|
||
- 크기를 400×400으로 줄이고 테두리, 그림자 추가
|
||
- 다이얼로그 영역 외부 클릭 시 닫힘
|
||
"""
|
||
dialog_container = ft.Container(
|
||
content=ft.Column(
|
||
controls=[
|
||
title,
|
||
ft.Divider(),
|
||
content,
|
||
ft.Divider(),
|
||
ft.Row(controls=actions, alignment=ft.MainAxisAlignment.END)
|
||
],
|
||
spacing=10
|
||
),
|
||
width=400,
|
||
height=400,
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=20,
|
||
border_radius=10,
|
||
border=ft.border.all(1, "grey"),
|
||
shadow=ft.BoxShadow(offset=ft.Offset(2, 2), blur_radius=8, color="grey")
|
||
)
|
||
# 내부 다이얼로그 클릭 시 이벤트 전파 차단
|
||
dialog_container.on_click = lambda e: e.stop_propagation()
|
||
|
||
overlay = ft.Container(
|
||
content=dialog_container,
|
||
expand=True,
|
||
alignment=ft.alignment.center,
|
||
bgcolor="rgba(0, 0, 0, 0.5)",
|
||
on_click=lambda e: self._close_custom_dialog()
|
||
)
|
||
self.custom_dialog_overlay = overlay
|
||
self.page.overlay.append(overlay)
|
||
self.page.update()
|
||
|
||
def _close_custom_dialog(self):
|
||
"""커스텀 다이얼로그 오버레이 닫기"""
|
||
if self.custom_dialog_overlay in self.page.overlay:
|
||
self.page.overlay.remove(self.custom_dialog_overlay)
|
||
self.custom_dialog_overlay = None
|
||
self.page.update()
|
||
|
||
def _on_tabs_change(self, e):
|
||
"""탭 변경 시 호출"""
|
||
index = self.view_tabs.selected_index
|
||
if index == 0:
|
||
self._switch_view("fault_code")
|
||
elif index == 1:
|
||
self._switch_view("abbreviation")
|
||
elif index == 2:
|
||
self._switch_view("drawing")
|
||
elif index == 3:
|
||
self._switch_view("signal")
|
||
|
||
def _switch_view(self, view_type: str):
|
||
"""뷰 전환 및 데이터 로드"""
|
||
if self.debug:
|
||
print(f"뷰 전환: {view_type}")
|
||
self.current_view = view_type
|
||
|
||
self.list_container.content.controls[0].visible = (view_type == "fault_code")
|
||
self.list_container.content.controls[1].visible = (view_type == "abbreviation")
|
||
self.list_container.content.controls[2].visible = (view_type == "drawing")
|
||
self.list_container.content.controls[3].visible = (view_type == "signal")
|
||
|
||
self.type_container.visible = (view_type == "fault_code")
|
||
|
||
if view_type == "fault_code":
|
||
self.load_all_codes()
|
||
elif view_type == "abbreviation":
|
||
self.load_abbreviations()
|
||
elif view_type == "drawing":
|
||
self.load_drawings()
|
||
elif view_type == "signal":
|
||
self.load_signals()
|
||
|
||
if self.debug:
|
||
print("뷰 전환 완료")
|
||
self.page.update()
|
||
|
||
def _get_manufacturers(self) -> List[str]:
|
||
"""제작사 목록 반환"""
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("SELECT name FROM manufacturers ORDER BY name")
|
||
manufacturers = [row[0] for row in cursor.fetchall()]
|
||
conn.close()
|
||
return manufacturers
|
||
|
||
def _get_fault_types(self) -> List[str]:
|
||
"""고장 타입 목록 반환"""
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("SELECT DISTINCT fault_type FROM fault_code_list ORDER BY fault_type")
|
||
types = [row[0] for row in cursor.fetchall()]
|
||
conn.close()
|
||
return types
|
||
|
||
def load_all_codes(self):
|
||
"""전체 고장 코드 목록 로드 및 fault 목록 갱신"""
|
||
if self.debug:
|
||
print("고장코드 목록 로딩 시작")
|
||
|
||
# 다른 뷰는 숨기고 고장 코드 뷰만 표시
|
||
self.list_container.content.controls[0].visible = True
|
||
self.list_container.content.controls[1].visible = False
|
||
self.list_container.content.controls[2].visible = False
|
||
self.list_container.content.controls[3].visible = False
|
||
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
ORDER BY fault_code
|
||
""")
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
# fault_content는 이제 ListView를 포함함
|
||
fault_list_view = self.list_container.content.controls[0].controls[1].content
|
||
fault_list_view.controls.clear()
|
||
|
||
# 현재 페이지 너비를 기준으로 열 너비 재계산
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
|
||
if not results or len(results) == 0:
|
||
# 데이터가 없는 경우 메시지 표시
|
||
no_data_row = ft.Container(
|
||
content=ft.Text("데이터가 없습니다. 데이터베이스를 확인해주세요.",
|
||
size=16,
|
||
color=ft.colors.RED),
|
||
padding=20,
|
||
alignment=ft.alignment.center,
|
||
height=30 # 높이 줄임
|
||
)
|
||
fault_list_view.controls.append(no_data_row)
|
||
if self.debug:
|
||
print("데이터가 없습니다!")
|
||
else:
|
||
# 데이터가 있는 경우 목록 추가
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(str(code)), width=code_w),
|
||
ft.Container(content=ft.Text(name), width=name_w),
|
||
ft.Container(content=ft.Text(type_), width=type_w)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_fault_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=5, # 패딩 줄임
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300),
|
||
height=30 # 높이 줄임
|
||
)
|
||
fault_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"고장코드 {len(results)}개 로드됨")
|
||
|
||
# 페이지 리사이즈 이벤트를 한 번 트리거하여 열 너비 업데이트
|
||
if hasattr(self, '_on_page_resize'):
|
||
self._on_page_resize(None)
|
||
|
||
# 화면 갱신
|
||
self.page.update()
|
||
|
||
if self.debug:
|
||
print(f"고장 목록 UI 업데이트 완료, 표시 상태: {self.list_container.content.controls[0].visible}")
|
||
print(f"항목 수: {len(fault_list_view.controls)}")
|
||
|
||
def load_abbreviations(self):
|
||
"""약어 목록 로드"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT abbreviation, description
|
||
FROM abbreviations
|
||
ORDER BY abbreviation
|
||
""")
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
abbr_list_view = self.list_container.content.controls[1].controls[1].content
|
||
abbr_list_view.controls.clear()
|
||
|
||
for abbr, desc in results:
|
||
row = ft.Container(
|
||
content=ft.Row([
|
||
ft.Text(abbr, width=150),
|
||
ft.Text(desc, width=350)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
abbr_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"약어 {len(results)}개 로드됨")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"약어 로드 오류: {e}")
|
||
|
||
def load_drawings(self):
|
||
"""도면 목록 로드"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT drawing_code, drawing_name, related_device
|
||
FROM drawings
|
||
ORDER BY drawing_code
|
||
""")
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
drawing_list_view = self.list_container.content.controls[2].controls[1].content
|
||
drawing_list_view.controls.clear()
|
||
|
||
for code, name, device in results:
|
||
row = ft.Container(
|
||
content=ft.Row([
|
||
ft.Text(code, width=150),
|
||
ft.Text(name, width=200),
|
||
ft.Text(device, width=150)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
drawing_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"도면 {len(results)}개 로드됨")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"도면 로드 오류: {e}")
|
||
|
||
def load_signals(self):
|
||
"""신호 목록 로드"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT signal_code, signal_name, signal_type
|
||
FROM signals
|
||
ORDER BY signal_code
|
||
""")
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
signal_list_view = self.list_container.content.controls[3].controls[1].content
|
||
signal_list_view.controls.clear()
|
||
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(code), width=150),
|
||
ft.Container(content=ft.Text(name), width=200),
|
||
ft.Container(content=ft.Text(type_), width=150)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_signal_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
signal_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"신호 {len(results)}개 로드됨")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"신호 로드 오류: {e}")
|
||
|
||
def _on_manufacturer_change(self, e):
|
||
"""제작사 선택 시 필터 적용"""
|
||
if self.current_view != "fault_code":
|
||
return
|
||
|
||
selected_manufacturer = self.manufacturer_dropdown.value
|
||
selected_type = self.type_dropdown.value
|
||
|
||
if selected_type and "(" in selected_type:
|
||
selected_type = selected_type.split("(")[0].strip()
|
||
|
||
if self.debug:
|
||
print(f"제작사 변경: {selected_manufacturer}, {selected_type}")
|
||
|
||
if selected_manufacturer:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
if not selected_type or selected_type == "전체":
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE manufacturer_id = (SELECT id FROM manufacturers WHERE name = ?)
|
||
ORDER BY fault_code
|
||
""", (selected_manufacturer,))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE LOWER(fault_type) LIKE LOWER(?) AND manufacturer_id = (SELECT id FROM manufacturers WHERE name = ?)
|
||
ORDER BY fault_code
|
||
""", (f"%{selected_type}%", selected_manufacturer))
|
||
|
||
results = cursor.fetchall()
|
||
if self.debug:
|
||
print(f"조회된 고장코드 {len(results)}개")
|
||
conn.close()
|
||
|
||
fault_list_view = self.list_container.content.controls[0].controls[1].content
|
||
fault_list_view.controls.clear()
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(str(code)), width=code_w),
|
||
ft.Container(content=ft.Text(name), width=name_w),
|
||
ft.Container(content=ft.Text(type_), width=type_w)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_fault_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
fault_list_view.controls.append(row)
|
||
|
||
self.page.update()
|
||
else:
|
||
self.load_all_codes()
|
||
|
||
def _on_type_change(self, e):
|
||
"""고장 타입 선택 시 필터 적용"""
|
||
if self.current_view != "fault_code":
|
||
return
|
||
|
||
selected_type = self.type_dropdown.value
|
||
if selected_type and "(" in selected_type:
|
||
selected_type = selected_type.split("(")[0].strip()
|
||
|
||
if self.debug:
|
||
print(f"타입 변경: {selected_type}")
|
||
|
||
selected_manufacturer = self.manufacturer_dropdown.value
|
||
|
||
if selected_type and selected_type != "전체":
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
if not selected_manufacturer:
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE LOWER(fault_type) LIKE LOWER(?)
|
||
ORDER BY fault_code
|
||
""", (f"%{selected_type}%",))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE LOWER(fault_type) LIKE LOWER(?) AND manufacturer_id = (SELECT id FROM manufacturers WHERE name = ?)
|
||
ORDER BY fault_code
|
||
""", (f"%{selected_type}%", selected_manufacturer))
|
||
|
||
results = cursor.fetchall()
|
||
if self.debug:
|
||
print(f"필터링 결과 {len(results)}개")
|
||
conn.close()
|
||
|
||
fault_list_view = self.list_container.content.controls[0].controls[1].content
|
||
fault_list_view.controls.clear()
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(str(code)), width=code_w),
|
||
ft.Container(content=ft.Text(name), width=name_w),
|
||
ft.Container(content=ft.Text(type_), width=type_w)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_fault_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
fault_list_view.controls.append(row)
|
||
|
||
self.page.update()
|
||
else:
|
||
self._on_manufacturer_change(e)
|
||
|
||
def _on_search_change(self, e):
|
||
"""검색어 변경 시 호출"""
|
||
search_term = self.search_field.value
|
||
if not search_term:
|
||
if self.current_view == "fault_code":
|
||
self.load_all_codes()
|
||
elif self.current_view == "abbreviation":
|
||
self.load_abbreviations()
|
||
elif self.current_view == "drawing":
|
||
self.load_drawings()
|
||
elif self.current_view == "signal":
|
||
self.load_signals()
|
||
return
|
||
|
||
if self.current_view == "fault_code":
|
||
self._search_fault_codes(search_term)
|
||
elif self.current_view == "abbreviation":
|
||
self._search_abbreviations(search_term)
|
||
elif self.current_view == "drawing":
|
||
self._search_drawings(search_term)
|
||
elif self.current_view == "signal":
|
||
self._search_signals(search_term)
|
||
|
||
def _search_fault_codes(self, search_term: str):
|
||
"""고장코드 검색"""
|
||
selected_type = self.type_dropdown.value
|
||
if selected_type == "전체":
|
||
selected_type = None
|
||
if selected_type and "(" in selected_type:
|
||
selected_type = selected_type.split("(")[0].strip()
|
||
|
||
selected_manufacturer = self.manufacturer_dropdown.value
|
||
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
if not selected_type:
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE LOWER(fault_code) LIKE LOWER(?) OR LOWER(fault_name) LIKE LOWER(?)
|
||
ORDER BY fault_code
|
||
""", (f'%{search_term}%', f'%{search_term}%'))
|
||
else:
|
||
cursor.execute("""
|
||
SELECT fault_code, fault_name, fault_type
|
||
FROM fault_code_list
|
||
WHERE LOWER(fault_type) LIKE LOWER(?) AND (LOWER(fault_code) LIKE LOWER(?) OR LOWER(fault_name) LIKE LOWER(?))
|
||
ORDER BY fault_code
|
||
""", (f"%{selected_type}%", f'%{search_term}%', f'%{search_term}%'))
|
||
|
||
results = cursor.fetchall()
|
||
if self.debug:
|
||
print(f"검색 결과 {len(results)}개 (검색어: {search_term})")
|
||
conn.close()
|
||
|
||
fault_list_view = self.list_container.content.controls[0].controls[1].content
|
||
fault_list_view.controls.clear()
|
||
code_w, name_w, type_w = self.get_fault_list_column_widths()
|
||
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(str(code)), width=code_w),
|
||
ft.Container(content=ft.Text(name), width=name_w),
|
||
ft.Container(content=ft.Text(type_), width=type_w)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_fault_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
fault_list_view.controls.append(row)
|
||
|
||
self.page.update()
|
||
|
||
def _search_abbreviations(self, search_term: str):
|
||
"""약어 검색"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT abbreviation, description
|
||
FROM abbreviations
|
||
WHERE LOWER(abbreviation) LIKE LOWER(?) OR LOWER(description) LIKE LOWER(?)
|
||
ORDER BY abbreviation
|
||
""", (f'%{search_term}%', f'%{search_term}%'))
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
abbr_list_view = self.list_container.content.controls[1].controls[1].content
|
||
abbr_list_view.controls.clear()
|
||
|
||
for abbr, desc in results:
|
||
row = ft.Container(
|
||
content=ft.Row([
|
||
ft.Text(abbr, width=150),
|
||
ft.Text(desc, width=350)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
abbr_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"약어 검색 결과 {len(results)}개")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"약어 로드 오류: {e}")
|
||
|
||
def _search_drawings(self, search_term: str):
|
||
"""도면 검색"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT drawing_code, drawing_name, related_device
|
||
FROM drawings
|
||
WHERE LOWER(drawing_code) LIKE LOWER(?) OR LOWER(drawing_name) LIKE LOWER(?) OR LOWER(related_device) LIKE LOWER(?)
|
||
ORDER BY drawing_code
|
||
""", (f'%{search_term}%', f'%{search_term}%', f'%{search_term}%'))
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
drawing_list_view = self.list_container.content.controls[2].controls[1].content
|
||
drawing_list_view.controls.clear()
|
||
|
||
for code, name, device in results:
|
||
row = ft.Container(
|
||
content=ft.Row([
|
||
ft.Text(code, width=150),
|
||
ft.Text(name, width=200),
|
||
ft.Text(device, width=150)
|
||
], spacing=10, width=500),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
drawing_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"도면 검색 결과 {len(results)}개")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"도면 로드 오류: {e}")
|
||
|
||
def _search_signals(self, search_term: str):
|
||
"""신호 검색"""
|
||
try:
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT signal_code, signal_name, signal_type
|
||
FROM signals
|
||
WHERE LOWER(signal_code) LIKE LOWER(?) OR LOWER(signal_name) LIKE LOWER(?) OR LOWER(signal_type) LIKE LOWER(?)
|
||
ORDER BY signal_code
|
||
""", (f'%{search_term}%', f'%{search_term}%', f'%{search_term}%'))
|
||
results = cursor.fetchall()
|
||
conn.close()
|
||
|
||
signal_list_view = self.list_container.content.controls[3].controls[1].content
|
||
signal_list_view.controls.clear()
|
||
|
||
for code, name, type_ in results:
|
||
row = ft.Container(
|
||
content=ft.Row(
|
||
controls=[
|
||
ft.Container(content=ft.Text(code), width=150),
|
||
ft.Container(content=ft.Text(name), width=200),
|
||
ft.Container(content=ft.Text(type_), width=150)
|
||
],
|
||
spacing=10
|
||
),
|
||
on_click=lambda e, code=code, name=name, type_=type_: self._show_signal_details(code, name, type_),
|
||
bgcolor=ft.colors.WHITE,
|
||
padding=10,
|
||
border_radius=5,
|
||
border=ft.border.all(1, ft.colors.GREY_300)
|
||
)
|
||
signal_list_view.controls.append(row)
|
||
|
||
if self.debug:
|
||
print(f"신호 검색 결과 {len(results)}개")
|
||
self.page.update()
|
||
except sqlite3.OperationalError as e:
|
||
print(f"신호 로드 오류: {e}")
|
||
|
||
def _show_fault_details(self, code, name, type_):
|
||
"""고장 상세 정보를 커스텀 다이얼로그로 표시"""
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT fault_detail, reaction, detect_condition, clear_condition, action
|
||
FROM fault_code_list
|
||
WHERE fault_code = ?
|
||
""", (code,))
|
||
details = cursor.fetchone()
|
||
conn.close()
|
||
|
||
if details:
|
||
detail, reaction, detect, clear, action = details
|
||
title = ft.Text(f"[{code}] {name}", weight=ft.FontWeight.BOLD)
|
||
content_col = ft.Column([
|
||
ft.Text("고장 타입: " + type_, color=ft.colors.BLUE_GREY_700),
|
||
ft.Divider(),
|
||
ft.Text("고장 상세:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(detail, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("반응:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(reaction, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("검지 조건:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(detect, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("소거 조건:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(clear, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("조치 방법:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(action, selectable=True)
|
||
], width=500, height=500, scroll=ft.ScrollMode.AUTO)
|
||
actions = [ft.TextButton("닫기", on_click=lambda e: self._close_custom_dialog())]
|
||
self._show_custom_dialog(title, content_col, actions)
|
||
|
||
def _show_signal_details(self, code, name, type_):
|
||
"""신호 상세 정보를 커스텀 다이얼로그로 표시"""
|
||
conn = sqlite3.connect('fault_codes.db')
|
||
cursor = conn.cursor()
|
||
cursor.execute("""
|
||
SELECT description, source, destination, unit, value_range
|
||
FROM signals
|
||
WHERE signal_code = ?
|
||
""", (code,))
|
||
details = cursor.fetchone()
|
||
conn.close()
|
||
|
||
if details:
|
||
description, source, destination, unit, value_range = details
|
||
title = ft.Text(f"[{code}] {name}", weight=ft.FontWeight.BOLD)
|
||
content_col = ft.Column([
|
||
ft.Text("신호 타입: " + type_, color=ft.colors.BLUE_GREY_700),
|
||
ft.Divider(),
|
||
ft.Text("설명:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(description, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("소스:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(source, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("목적지:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(destination, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("단위:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(unit, selectable=True),
|
||
ft.Divider(),
|
||
ft.Text("값 범위:", weight=ft.FontWeight.BOLD),
|
||
ft.Text(value_range, selectable=True)
|
||
], width=500, height=500, scroll=ft.ScrollMode.AUTO)
|
||
actions = [ft.TextButton("닫기", on_click=lambda e: self._close_custom_dialog())]
|
||
self._show_custom_dialog(title, content_col, actions)
|
||
|
||
def _show_settings_dialog(self, e):
|
||
"""설정 커스텀 다이얼로그 표시"""
|
||
theme_radio = ft.RadioGroup(
|
||
content=ft.Column(
|
||
controls=[
|
||
ft.Radio(value="light", label="라이트 모드"),
|
||
ft.Radio(value="dark", label="다크 모드")
|
||
]
|
||
),
|
||
value=self.theme,
|
||
on_change=self._on_theme_change
|
||
)
|
||
title = ft.Text("설정", weight=ft.FontWeight.BOLD)
|
||
content_col = ft.Column(
|
||
controls=[
|
||
ft.Text("테마 설정", weight=ft.FontWeight.BOLD),
|
||
theme_radio,
|
||
ft.Divider(),
|
||
ft.Text("제작자 정보", weight=ft.FontWeight.BOLD),
|
||
ft.Text("신평-Choi KyungHwan", color=ft.colors.BLUE_GREY_700)
|
||
],
|
||
scroll=ft.ScrollMode.AUTO
|
||
)
|
||
actions = [ft.TextButton("닫기", on_click=lambda e: self._close_custom_dialog())]
|
||
self._show_custom_dialog(title, content_col, actions)
|
||
|
||
def _on_theme_change(self, e):
|
||
"""테마 변경"""
|
||
self.theme = e.control.value
|
||
if self.theme == "light":
|
||
self.page.theme_mode = ft.ThemeMode.LIGHT
|
||
else:
|
||
self.page.theme_mode = ft.ThemeMode.DARK
|
||
self.page.update()
|
||
|
||
def create_search_field(self, on_change: Callable) -> ft.TextField:
|
||
"""검색 필드 생성"""
|
||
self.search_field = ft.TextField(
|
||
label="검색",
|
||
hint_text="고장코드, 고장명, 분류 등을 입력하세요",
|
||
on_change=on_change,
|
||
expand=True
|
||
)
|
||
return self.search_field
|
||
|
||
def create_data_table(self, data: list) -> ft.DataTable:
|
||
"""데이터 테이블 생성 (검색 결과용)"""
|
||
columns = [
|
||
ft.DataColumn(ft.Text("고장코드")),
|
||
ft.DataColumn(ft.Text("고장명")),
|
||
ft.DataColumn(ft.Text("등급")),
|
||
ft.DataColumn(ft.Text("분류")),
|
||
ft.DataColumn(ft.Text("고장내용")),
|
||
ft.DataColumn(ft.Text("반응")),
|
||
ft.DataColumn(ft.Text("검지조건")),
|
||
ft.DataColumn(ft.Text("소거조건")),
|
||
ft.DataColumn(ft.Text("조치방법"))
|
||
]
|
||
rows = []
|
||
for row in data:
|
||
cells = [ft.DataCell(ft.Text(str(cell))) for cell in row]
|
||
rows.append(ft.DataRow(cells=cells))
|
||
self.data_table = ft.DataTable(
|
||
columns=columns,
|
||
rows=rows,
|
||
border=ft.border.all(1, "grey"),
|
||
border_radius=10,
|
||
vertical_lines=ft.border.BorderSide(1, "grey"),
|
||
horizontal_lines=ft.border.BorderSide(1, "grey"),
|
||
)
|
||
return self.data_table
|
||
|
||
def update_table(self, data: list):
|
||
"""테이블 업데이트"""
|
||
new_table = self.create_data_table(data)
|
||
self.data_table.columns = new_table.columns
|
||
self.data_table.rows = new_table.rows
|
||
|
||
def create_layout(self) -> ft.Column:
|
||
"""메인 레이아웃 생성"""
|
||
return ft.Column([
|
||
ft.Container(content=self.search_field, padding=10),
|
||
ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("검색 결과", size=20, weight=ft.FontWeight.BOLD),
|
||
ft.Container(content=self.data_table, height=500)
|
||
]),
|
||
padding=10,
|
||
expand=True,
|
||
)
|
||
])
|