import flet as ft from datetime import datetime def main(page: ft.Page): # Состояние формы state = { "current_step": 0, "operators_name": "", "factory": "", "line": "", "error_zone": "", "problems": set(), # будем накапливать выбранные значения "downtime_reasons": set(), "custom_problem": "", "custom_reason": "", "fix_method": "", "start_at": "", "end_at": "", # Зависимые опции: "line_options": [], "zone_options": [], "problem_options": [], "reason_options": [], } # Справочники (аналогичные примерам в React/Ionic) factory_line_map = { "Славда": ["ПЭТ", "19л", "5л"], "Скит": ["Sipa", "Devin", "JR", "19 Литров", "5 Литров"] } line_zone_map = { "Sipa": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"], "Devin": ["Сериализация", "Упаковка", "Сборка палеты"], "JR": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"], "19 Литров": ["Валидация", "Тонкий клиент"], "5 Литров": ["Валидация", "Тонкий клиент"], "ПЭТ": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"], "5л": ["Сериализация", "Упаковка", "Сборка палеты", "Закрытие палеты"], "19л": ["Валидация", "Тонкий клиент"], } common_problems = { "Славда": ["Не печатает код", "Камера не считывает код", "Сбой принтера"], "Скит": ["Не печатает код", "Камера не считывает код", "Сбой принтера"], } common_downtime_reasons = { "Славда": ["Сбой камеры", "Ошибка валидации", "Проблема с печатью"], "Скит": ["Сбой камеры", "Ошибка валидации", "Проблема с печатью"], } # Элементы UI, которые будут обновляться operators_name_field = ft.TextField(label="Имя оператора *", width=300) factory_dropdown = ft.Dropdown( label="Завод *", options=[ ft.dropdown.Option("Славда"), ft.dropdown.Option("Скит") ], width=300 ) line_dropdown = ft.Dropdown(label="Линия *", width=300) zone_dropdown = ft.Dropdown(label="Зона *", width=300) fix_method_field = ft.TextField(label="Как решили проблему? *", width=300) start_at_field = ft.TextField(label="Начало простоя (YYYY-MM-DD HH:MM) *", width=300) end_at_field = ft.TextField(label="Окончание простоя (YYYY-MM-DD HH:MM) *", width=300) custom_problem_field = ft.TextField(label="Своя проблема (необязательно)", width=300) custom_reason_field = ft.TextField(label="Своя причина (необязательно)", width=300) # Для выбора нескольких вариантов – используем колонки с чекбоксами problems_checkboxes = ft.Column() reasons_checkboxes = ft.Column() # Функция для показа уведомлений (SnackBar) def show_snackbar(message, bgcolor): page.snack_bar = ft.SnackBar(ft.Text(message), bgcolor=bgcolor) page.snack_bar.open = True page.update() # Обновление зависимых списков при выборе завода def update_factory_dependent(): factory = state["factory"] state["line_options"] = factory_line_map.get(factory, []) line_dropdown.options = [ft.dropdown.Option(opt) for opt in state["line_options"]] line_dropdown.value = None state["line"] = "" state["problem_options"] = common_problems.get(factory, []) state["reason_options"] = common_downtime_reasons.get(factory, []) update_problems_checkboxes() update_reasons_checkboxes() page.update() # Обновление списка зон при выборе линии def update_line_dependent(): line = state["line"] state["zone_options"] = line_zone_map.get(line, []) zone_dropdown.options = [ft.dropdown.Option(opt) for opt in state["zone_options"]] zone_dropdown.value = None state["error_zone"] = "" page.update() def update_problems_checkboxes(): problems_checkboxes.controls.clear() for prob in state["problem_options"]: chk = ft.Checkbox(label=prob, value=False) def on_change(e, label=prob): if e.control.value: state["problems"].add(label) else: state["problems"].discard(label) chk.on_change = on_change problems_checkboxes.controls.append(chk) page.update() def update_reasons_checkboxes(): reasons_checkboxes.controls.clear() for reason in state["reason_options"]: chk = ft.Checkbox(label=reason, value=False) def on_change(e, label=reason): if e.control.value: state["downtime_reasons"].add(label) else: state["downtime_reasons"].discard(label) chk.on_change = on_change reasons_checkboxes.controls.append(chk) page.update() # Функция валидации этапов def validate_step(): step = state["current_step"] if step == 0: if not operators_name_field.value.strip() or not factory_dropdown.value: show_snackbar("Введите имя и выберите завод", ft.colors.RED) return False elif step == 1: if not line_dropdown.value or not zone_dropdown.value: show_snackbar("Выберите линию и зону", ft.colors.RED) return False elif step == 2: total_problems = set(state["problems"]) if custom_problem_field.value and custom_problem_field.value.strip(): total_problems.add(custom_problem_field.value.strip()) total_reasons = set(state["downtime_reasons"]) if custom_reason_field.value and custom_reason_field.value.strip(): total_reasons.add(custom_reason_field.value.strip()) if not total_problems: show_snackbar("Укажите хотя бы одну проблему", ft.colors.RED) return False if not total_reasons: show_snackbar("Укажите хотя бы одну причину простоя", ft.colors.RED) return False elif step == 3: if not fix_method_field.value.strip(): show_snackbar("Опишите, как решили проблему", ft.colors.RED) return False if not start_at_field.value.strip() or not end_at_field.value.strip(): show_snackbar("Укажите время начала и окончания", ft.colors.RED) return False try: datetime.strptime(start_at_field.value.strip(), "%Y-%m-%d %H:%M") datetime.strptime(end_at_field.value.strip(), "%Y-%m-%d %H:%M") except ValueError: show_snackbar("Неверный формат даты/времени", ft.colors.RED) return False return True # Контейнер для отображения содержимого этапа content_column = ft.Column() # Прогресс-бар (значение от 0 до 1) progress_bar = ft.ProgressBar(value=(state["current_step"]+1)/4) # Кнопки навигации next_button = ft.ElevatedButton(text="Далее") back_button = ft.ElevatedButton(text="Назад") submit_button = ft.ElevatedButton(text="Отправить") # Обновление UI в зависимости от этапа def update_ui(): content_column.controls.clear() progress_bar.value = (state["current_step"]+1)/4 if state["current_step"] == 0: content_column.controls.extend([ operators_name_field, factory_dropdown, ]) elif state["current_step"] == 1: content_column.controls.extend([ line_dropdown, zone_dropdown, ]) elif state["current_step"] == 2: content_column.controls.extend([ ft.Text("Проблемы (можно выбрать несколько):"), problems_checkboxes, custom_problem_field, ft.Text("Причины простоя (можно выбрать несколько):"), reasons_checkboxes, custom_reason_field, ]) elif state["current_step"] == 3: content_column.controls.extend([ fix_method_field, start_at_field, end_at_field, ]) # Кнопки навигации nav_buttons = [] if state["current_step"] > 0: nav_buttons.append(back_button) if state["current_step"] < 3: nav_buttons.append(next_button) if state["current_step"] == 3: nav_buttons.append(submit_button) content_column.controls.append(ft.Row(controls=nav_buttons, spacing=20)) page.update() # Обработчики кнопок def on_next(e): if validate_step(): if state["current_step"] == 0: state["operators_name"] = operators_name_field.value state["factory"] = factory_dropdown.value update_factory_dependent() elif state["current_step"] == 1: state["line"] = line_dropdown.value state["error_zone"] = zone_dropdown.value state["current_step"] += 1 update_ui() def on_back(e): state["current_step"] -= 1 update_ui() def on_submit(e): if not validate_step(): return state["fix_method"] = fix_method_field.value state["start_at"] = start_at_field.value state["end_at"] = end_at_field.value total_problems = list(state["problems"]) if custom_problem_field.value and custom_problem_field.value.strip(): total_problems.append(custom_problem_field.value.strip()) total_reasons = list(state["downtime_reasons"]) if custom_reason_field.value and custom_reason_field.value.strip(): total_reasons.append(custom_reason_field.value.strip()) payload = { "factory": state["factory"], "line": state["line"], "operators_name": state["operators_name"], "error_zone": state["error_zone"], "problem": ", ".join(total_problems), "downtime_reason": ", ".join(total_reasons), "fix_method": state["fix_method"], "start_at": state["start_at"], "end_at": state["end_at"], } print("[DEBUG] Отправляем JSON:", payload) show_snackbar("Данные успешно отправлены!", ft.colors.GREEN) # Сброс формы state.update({ "current_step": 0, "operators_name": "", "factory": "", "line": "", "error_zone": "", "problems": set(), "downtime_reasons": set(), "custom_problem": "", "custom_reason": "", "fix_method": "", "start_at": "", "end_at": "", "line_options": [], "zone_options": [], "problem_options": [], "reason_options": [], }) operators_name_field.value = "" factory_dropdown.value = None line_dropdown.value = None zone_dropdown.value = None fix_method_field.value = "" start_at_field.value = "" end_at_field.value = "" custom_problem_field.value = "" custom_reason_field.value = "" problems_checkboxes.controls.clear() reasons_checkboxes.controls.clear() update_ui() next_button.on_click = on_next back_button.on_click = on_back submit_button.on_click = on_submit def on_factory_change(e): state["factory"] = factory_dropdown.value update_factory_dependent() factory_dropdown.on_change = on_factory_change def on_line_change(e): state["line"] = line_dropdown.value update_line_dependent() line_dropdown.on_change = on_line_change def on_zone_change(e): state["error_zone"] = zone_dropdown.value zone_dropdown.on_change = on_zone_change page.title = "Журнал простоев (мобильная версия) - Flet" page.add(progress_bar, content_column) update_ui() ft.app(target=main)