codeSearch/ui/fault_finder.py

1130 lines
45 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
)
])