Files

404 lines
45 KiB
Python
Raw Permalink 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.
"""Сценарии скриншотов руководства: что открыть, под кем, какие элементы отметить (метки попадают и в легенду PDF)."""
from dataclasses import dataclass, field
@dataclass
class M:
"""Метка на скриншоте. sel/text — как найти элемент; label — подпись в легенде под картинкой."""
label: str
sel: str = ""
text: str = ""
nth: int = 0
dir: str = "auto"
pad: int = 0 # доп. отступ рамки, px
grow: tuple = (0, 0) # расширить рамку вправо/вниз (для групп элементов)
@dataclass
class Shot:
name: str
device: str # desktop | mobile | android
user: str | None = None # логин (пароль: admin → admin, остальные demo1234)
steps: list = field(default_factory=list)
marks: list = field(default_factory=list)
height: int = 0 # высота окна (0 — по умолчанию устройства)
crop: tuple = () # (x, y, w, h) — обрезать итоговую картинку
PASS = {"admin": "admin"}
def order_js(title, status="", sel="location.hash='#/order/'+o.id"):
q = f"&status={status}" if status else ""
return (f"(async()=>{{const l=await api('GET','/orders?limit=500{q}'); const o=l.find(x=>x.title.includes('{title}')); "
f"if(!o) throw new Error('нет наряда {title}'); {sel};}})()")
def m_order_js(title, status=""):
q = f"&status={status}" if status else ""
return (f"(async()=>{{const l=await api('GET','/orders?limit=500{q}'); const o=l.find(x=>x.title.includes('{title}')); "
f"if(!o) throw new Error('нет наряда {title}'); location.hash='#/o/'+o.id;}})()")
# ответы для заполнения наряда «ТО компрессора» через API (давление, масло, фильтр, слив)
FILL_COMP = ("(async()=>{const l=await api('GET','/orders?limit=500&status=new'); const o=l.find(x=>x.title.includes('ТО компрессора')); "
"const d=await api('GET','/orders/'+o.id); const v=['7.2','в норме','done','done']; "
"for(let i=0;i<4;i++) await api('PUT','/orders/'+o.id+'/items/'+d.items[i].id,{value:v[i]});})()")
# фото-заглушка к пункту «Воздушный фильтр»
PHOTO_JS = ("(async()=>{const l=await api('GET','/orders?limit=500&status=in_progress'); const o=l.find(x=>x.title.includes('ТО компрессора')); "
"const d=await api('GET','/orders/'+o.id); const it=d.items.find(i=>i.photo==='required'); "
"const cv=document.createElement('canvas'); cv.width=640; cv.height=480; const g=cv.getContext('2d'); "
"const gr=g.createLinearGradient(0,0,640,480); gr.addColorStop(0,'#5b8def'); gr.addColorStop(1,'#223a66'); g.fillStyle=gr; g.fillRect(0,0,640,480); "
"g.fillStyle='#e9eef9'; g.fillRect(140,120,360,240); g.fillStyle='#223a66'; for(let i=0;i<8;i++) g.fillRect(160+i*42,140,22,200); "
"const b=await new Promise(r=>cv.toBlob(r,'image/jpeg',.85)); "
"await authFetch(API+'/orders/'+o.id+'/items/'+it.id+'/photos',{method:'POST',headers:{'Content-Type':'image/jpeg'},body:b});})()")
DESKTOP = [
Shot("d_login", "desktop", None, [("reset",), ("hash", "")], [
M("Логин и пароль, которые выдал администратор", "input", nth=0, pad=2, grow=(0, 78), dir="left"),
M("Кнопка «Войти»", "button", "Войти"),
M("Ссылка на скачивание приложения для Android (если администратор загрузил файл)", ".btn", "Скачать приложение", dir="right"),
M("Инструкция и презентация системы", "p", "Презентация", dir="right"),
], crop=(250, 40, 780, 600)),
Shot("d_orders", "desktop", "sidorov", [("hash", "#/"), ("wait", 1.5), ("js", "const s=document.querySelector('#app select'); s.value='new'; s.dispatchEvent(new Event('change'))"), ("wait", 1.2)], [
M("Боковое меню: раздел «Наряды» — список всех ваших нарядов", "#side nav a", "Наряды", dir="right"),
M("Счётчики по статусам: нажмите на плитку, чтобы отфильтровать", ".stat", nth=0, grow=(560, 0), dir="up"),
M("Строка поиска: название, оборудование или номер наряда", "input[type=search]", dir="down"),
M("Фильтр по статусу", "#app select", nth=0, dir="down"),
M("Быстрые фильтры «Мои» и «Просроченные»", "label", "Мои", grow=(120, 0), dir="left"),
M("Строка наряда — нажмите, чтобы открыть. Подсказка «можно выполнять» значит, что наряд ваш", "tbody tr", nth=0, dir="right"),
]),
Shot("d_order_top", "desktop", "sidorov", [("js", order_js("ТО компрессора", "new")), ("wait", 1.5)], [
M("Статус наряда", ".badge", nth=0, dir="right"),
M("Оборудование, место, кто назначен и проверяющий, срок выполнения", ".grid", nth=0, dir="right"),
M("Прогресс чек-листа: сколько пунктов заполнено", "h2", "Чек-лист", dir="right"),
]),
Shot("d_order_items", "desktop", "sidorov", [("js", order_js("ТО компрессора", "new")), ("wait", 1.2), ("scroll", "h2", "Чек-лист", 0)], [
M("Число: введите значение, под полем — допустимая норма. Вне нормы поле подсветится", "input[type=text]", nth=0, grow=(0, 24), dir="left"),
M("Выбор из списка: просто выберите вариант", ".item select", nth=0, dir="left"),
M("Статус пункта: «Не начато», «В работе», «Выполнено»", ".item select", nth=1, dir="left"),
M("Кнопка добавления фото (на телефоне откроет камеру)", ".photos button, .item button", "Добавить фото", dir="right"),
]),
Shot("d_order_filled", "desktop", "sidorov", [("js", FILL_COMP), ("js", PHOTO_JS), ("js", order_js("ТО компрессора", "in_progress")), ("wait", 1.6),
("scroll", "h2", "Чек-лист", 0)], [
M("Заполненные пункты", ".item select", nth=0, dir="left"),
M("Приложенное фото (нажмите, чтобы увеличить; ✕ — удалить)", ".photos .ph", nth=0, dir="right"),
M("Когда всё заполнено — «Сдать наряд»", "button", "Сдать наряд", dir="up"),
], height=900),
Shot("d_order_review_wait", "desktop", "sidorov", [("js", order_js("ТО компрессора", "in_progress", "const x=await api('POST','/orders/'+o.id+'/submit'); location.hash='#/order/'+o.id")), ("wait", 1.5)], [
M("После сдачи наряд получает статус «На проверке» и уходит проверяющему", ".badge", nth=0, dir="right"),
M("Время сдачи фиксируется автоматически", ".kv, .grid", nth=0, dir="right"),
]),
Shot("d_review", "desktop", "morozova", [("js", order_js("ТО компрессора", "review")), ("wait", 1.5), ("scroll", "h2", "Проверка", 0)], [
M("Комментарий проверяющего: обязателен, если возвращаете на доработку", "textarea", nth=0, dir="left"),
M("«Принять» — наряд завершён", "button", "Принять", dir="up"),
M("«Вернуть на доработку» — исполнитель увидит ваш комментарий", "button", "Вернуть", dir="up"),
]),
Shot("d_neworder", "desktop", "volkov", [("hash", "#/new-order"), ("wait", 1.2)], [
M("Название наряда", "input", nth=0, dir="left"),
M("Чек-лист — шаблон работ, который нужно выполнить", ".grid select", nth=0, dir="up"),
M("Оборудование и место", ".grid select", nth=1, grow=(250, 0), dir="up"),
M("Исполнитель: должность (любой, кто на смене) или конкретный человек", ".grid select", nth=3, grow=(250, 0), dir="left"),
M("Проверяющий: должность или конкретный человек", ".grid select", nth=5, grow=(250, 0), dir="right"),
M("«Создать наряд»", "button", "Создать наряд", dir="right"),
]),
Shot("d_checklist", "desktop", "volkov", [("js", "(async()=>{const l=await api('GET','/checklists'); location.hash='#/checklists/'+l.find(x=>x.name.includes('насосной')).id})()"), ("wait", 1.5)], [
M("Название чек-листа", "input", nth=0, dir="left"),
M("Тип ответа: статус, список, число, текст", ".card select", nth=0, dir="left"),
M("Режим фото: без фото, по желанию, обязательно", ".card select", nth=1, dir="left"),
M("Порядок и удаление задач", ".card button", "↑", dir="left"),
], height=900),
Shot("d_templates", "desktop", "volkov", [("hash", "#/templates"), ("wait", 1.5)], [
M("Расписание автозапуска: как часто создаётся наряд", "tbody tr", nth=0, dir="up"),
M("Время следующего запуска", "tbody tr td", nth=2, dir="down"),
M("«Создать сейчас» — разовый запуск без ожидания расписания", "button", "Создать сейчас", nth=0, dir="up"),
M("«Изменить» — настройки наряда и периодичности", "button", "Изменить", nth=0, dir="up"),
]),
Shot("d_reports", "desktop", "volkov", [("hash", "#/reports/completed-orders"), ("wait", 1.0), ("click", "button", "7 дней", 0), ("click", "button", "Показать", 0), ("wait", 1.5)], [
M("Период (дата и время) обязателен", "input[type=datetime-local]", nth=0, grow=(300, 0), dir="up"),
M("Фильтры: машина, должность исполнителя, исполнитель", ".grid select", nth=0, grow=(250, 0), dir="up"),
M("Быстрый выбор периода", "button", "Сегодня", grow=(380, 0), dir="left"),
M("«Показать» — на экран, «PDF» и «CSV» — файл", "button", "Показать", grow=(210, 0), dir="up"),
], height=900),
Shot("d_admin", "desktop", "admin", [("hash", "#/admin"), ("wait", 1.5)], [
M("Пользователи, должности и графики работы", ".tiles", nth=0, dir="up"),
M("Рабочие данные: чек-листы, оборудование, места, расписание нарядов", ".tiles", nth=1, dir="up"),
M("Сервер: мастер настройки, настройки сервера и обратная связь (администратор портала), копии, демо-режим", ".tiles", nth=2, dir="up"),
]),
Shot("d_feedback", "desktop", "admin", [("js", "(async()=>{await api('POST','/feedback',{kind:'idea',text:'Добавить в карточку машины фото паспортной таблички',page:'#/equipment/3'}); await api('POST','/feedback',{kind:'bug',text:'На телефоне карта кранов открывается не сразу',page:'#/map/cranes',client:'m'}); location.hash='#/feedback'})()"), ("wait", 2.0)], [
M("Открытые, сделанные и все сообщения", ".tabs", dir="down"),
M("Сообщение: что предложили, кто, из какого раздела", ".fbcard", nth=0, dir="right"),
M("Статус и заметка о решении", ".fbcard select", nth=0, dir="left"),
], height=900),
Shot("d_user_form", "desktop", "admin", [("hash", "#/users"), ("wait", 1.0), ("click", "button", "+ Добавить", 0), ("wait", 0.8)], [
M("Логин для входа (ФИО и пароль — рядом)", ".card .grid input", nth=0, dir="left"),
M("Должность — определяет график работы и права (техник или инженер)", ".card .grid select", nth=0, dir="right"),
M("Роли «Модератор» и «Администратор» назначает только администратор", ".card .grid input[type=checkbox]", nth=0, grow=(300, 0), dir="down"),
], height=1000),
Shot("d_schedule", "desktop", "admin", [("hash", "#/schedules"), ("wait", 1.0), ("click", "button", "+ Добавить", 0), ("wait", 1.5), ("scroll", "h2", "Новый график", 0)], [
M("Тип графика: свои шаги, N через M, по дням недели", ".card select", nth=0, dir="left"),
M("Готовые шаблоны графиков", "button", "Сутки через трое", grow=(260, 0), dir="up"),
M("Шаг цикла: смена или выходной, сколько дней подряд, время и метка", ".step", nth=0, dir="right"),
M("Первый день цикла — с какой даты начинается шаг №1", "input[type=date]", nth=0, dir="right"),
], height=1100),
Shot("d_schedule_cal", "desktop", "admin", [("hash", "#/schedules"), ("wait", 1.0), ("click", "button", "+ Добавить", 0), ("wait", 1.5), ("scroll", ".cal", "", 0)], [
M("Календарь: синие — дневные смены, фиолетовые — ночные, серые — выходные", ".cal", nth=0, dir="right"),
], height=900),
Shot("d_menu", "desktop", "admin", [("hash", "#/tasks"), ("wait", 1.2)], [
M("Ваша карточка: ФИО, должность, роль. Нажмите, чтобы открыть", ".meblock", dir="right"),
M("Работа: вкладка «Задачи», наряды, задачи и поручения, расписание, чек-листы", ".navh", "РАБОТА", grow=(0, 200), dir="right"),
M("Оборудование, карта завода, отказы, места", ".navh", "ОБОРУДОВАНИЕ", grow=(0, 140), dir="right"),
M("Инструмент: мой инструмент, заявки, склады и учёт, карта складов", ".navh", "ИНСТРУМЕНТ", grow=(0, 140), dir="right"),
M("Сотрудники, отчёты и администрирование", ".navh", "ЛЮДИ", grow=(0, 120), dir="right"),
M("Краны — отдельная служба (если включено в настройках)", ".navh", "КРАНЫ", grow=(0, 110), dir="right"),
M("Инструкция, приложение, презентация и форма для заявки", ".sidelinks", dir="right"),
M("Уведомления и выход", ".sidefoot", dir="right"),
], height=1450, crop=(0, 0, 620, 1450)),
Shot("d_tasks", "desktop", "volkov", [("hash", "#/tasks"), ("wait", 1.5)], [
M("День: по умолчанию сегодня; «Все дни» — без фильтра", "#app input[type=date]", dir="down"),
M("Фильтры по статусу и сотруднику, «Только мои»", "#app select", nth=0, grow=(330, 0), dir="down"),
M("Задание: статус, исполнитель, оборудование; последний комментарий", ".taskc", nth=0, dir="up"),
M("«+ Задание» — поставить новое", "a.btn", "+ Задание", dir="left"),
]),
Shot("d_task", "desktop", "kozlov", [("js", "(async()=>{const l=await api('GET','/tasks?limit=500'); const t=l.find(x=>x.title.includes('ремней')); if(!t) throw new Error('нет задания'); location.hash='#/task/'+t.id})()"), ("wait", 1.5)], [
M("Статус задания", ".badge", nth=0, dir="left"),
M("Оборудование или узел, к которому привязано задание", "a", "Вентилятор", dir="down"),
M("Комментарий к смене статуса", "textarea", nth=0, dir="left"),
M("Кнопки статусов: приостановить, выполнить, отменить — только с комментарием", "button", "Приостановить", grow=(300, 0), dir="up"),
M("Фото к заданию", "button", "Добавить фото", dir="right"),
], height=1000),
Shot("d_equipment", "desktop", "sidorov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Н-2')).id})()"), ("wait", 2.0)], [
M("Иконка машины; «!» — открыт отказ", ".eqbig", dir="right"),
M("Состояние: «Авария», «Требуется ТО», «Скоро ТО» или «В норме»", ".badge.es-alarm", dir="right"),
M("Зафиксировать отказ (авария, машина стоит)", "button", "Зафиксировать отказ", dir="down"),
M("Сообщить о неисправности (машина работает, но есть проблема)", "button", "Сообщить о неисправности", dir="down"),
M("Задание по этой машине", "a.btn", "Задание по машине", dir="down"),
], height=760),
Shot("d_incident", "desktop", "sidorov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Н-2')).id})()"), ("wait", 2.0), ("click", "button", "Зафиксировать отказ", 0), ("wait", 1.0)], [
M("Что произошло и когда", ".card .grid input", nth=0, grow=(560, 0), dir="up"),
M("Подробности", ".card textarea", nth=0, dir="left"),
M("Затронутые узлы (из заведённых в карточке). «Заменён» — ресурс узла начнётся заново", ".unitpick", nth=0, dir="left"),
M("Была ли замена и что заменено", ".chk", "Была замена", grow=(500, 0), dir="up"),
M("«Зафиксировать» — машина сразу получит состояние «Авария»", "button.bad", "Зафиксировать", nth=1, dir="right"),
], height=1150),
Shot("d_icons", "desktop", "volkov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Компрессор')).id})()"), ("wait", 2.0), ("click", "button", "Иконка", 0), ("wait", 1.0)], [
M("Встроенный набор иконок — нажмите, чтобы выбрать", ".iconpick", dir="right"),
M("Или загрузите своё фото/картинку", "button", "Загрузить своё фото", dir="right"),
], height=900),
Shot("d_map", "desktop", "sidorov", [("hash", "#/map"), ("wait", 2.5)], [
M("Слои: оборудование и склады", ".tabs a", "Склады", dir="right"),
M("Легенда: «!» — авария, «⚠» — неисправность, жёлтое свечение — приближается лимит ТО", ".legend", dir="left"),
M("Машина с открытой неисправностью: оранжевый значок", ".mk.st-fault", nth=0, dir="down"),
M("Машина с открытым отказом: восклицательный знак", ".mk.st-alarm", nth=0, dir="down"),
M("Подсветка жёлтым: по узлам машины скоро или уже нужно ТО", ".mk.st-due, .mk.st-soon", nth=0, dir="up"),
], height=900),
Shot("d_map_edit", "desktop", "admin", [("hash", "#/map"), ("wait", 2.0), ("click", "button", "Редактировать карту", 0), ("wait", 1.0)], [
M("Загрузить или заменить картинку плана завода", "button", "Заменить картинку", dir="right"),
M("Список машин: «Поставить» и клик по карте — разместить; ✕ — убрать с карты", ".eqpick", dir="right"),
M("Иконки на карте перетаскиваются мышью или пальцем", ".mk.edit", nth=0, dir="down"),
], height=1150),
Shot("d_map_wh", "desktop", "lebedev", [("hash", "#/map/warehouses"), ("wait", 2.0), ("js", "document.querySelector('.zone').click()"), ("wait", 1.5)], [
M("Зоны складов на карте завода", ".zone", nth=0, grow=(0, 0), dir="left"),
M("Нажмите на зону — появится список того, что лежит на складе", ".card", "Открыть склад", dir="up"),
M("«Редактировать карту»: протяните прямоугольник зоны для склада", "button", "Редактировать карту", dir="right"),
], height=1150),
Shot("d_inventory", "desktop", "lebedev", [("hash", "#/inventory/stock"), ("wait", 1.8)], [
M("Вкладки: склады, каталог, выдано сотрудникам, журнал движения", ".tabs", dir="down"),
M("Операции: выдача, возврат, поступление, перемещение, списание", "button", "Выдать", grow=(560, 0), dir="down"),
M("Склад и его содержимое", "#app .card h2", nth=0, dir="right"),
M("Выдать позицию прямо из строки склада", "td button", "Выдать", nth=0, dir="left"),
], height=900),
Shot("d_issue", "desktop", "lebedev", [("hash", "#/inventory/stock"), ("wait", 1.5), ("click", "button", "→ Выдать", 0), ("wait", 0.8)], [
M("Позиция каталога и количество", ".card .grid select", nth=0, grow=(300, 0), dir="up"),
M("Со склада — конкретному сотруднику (лично, не должности)", ".card .grid select", nth=1, grow=(300, 0), dir="up"),
M("«Провести» — остатки и журнал обновятся сразу", "button", "Провести", dir="right"),
], height=900),
Shot("d_requests", "desktop", "lebedev", [("hash", "#/requests"), ("wait", 1.5), ("scroll", "h2", "Все заявки", 0)], [
M("Заявка: что купить, зачем, ссылка на товар", ".card", "Съёмник", dir="right"),
M("Статус заявки и ответ сотруднику", ".card select", nth=0, grow=(0, 0), dir="up"),
M("«Сохранить» — автор получит уведомление", "button", "Сохранить", nth=0, dir="left"),
], height=900),
Shot("d_request_form", "desktop", "ivanov", [("hash", "#/requests"), ("wait", 1.5)], [
M("Что купить (обязательно)", ".card input", nth=0, dir="up"),
M("Ссылка на конкретный товар", ".card input[type=url]", dir="up"),
M("Зачем нужно (обязательно)", ".card textarea", nth=0, dir="right"),
M("Ответ ответственного появится в карточке заявки", ".sheet", nth=0, dir="right"),
], height=1000),
Shot("d_people", "desktop", "sidorov", [("hash", "#/people"), ("wait", 1.5)], [
M("Поиск по ФИО, должности и навыкам", "input[type=search]", dir="down"),
M("Карточка сотрудника: фото, должность, на смене ли, навыки", ".person", nth=1, dir="down"),
], height=700),
Shot("d_user_card", "desktop", "volkov", [("js", "(async()=>{const l=await api('GET','/users'); location.hash='#/user/'+l.find(x=>x.username==='ivanov').id})()"), ("wait", 1.8)], [
M("Фото, ФИО, должность, на смене ли сейчас", ".ucard", dir="down"),
M("График: ближайшие смены", ".card", "График", dir="right"),
M("«Что умею» и телефон — заполняет сам сотрудник", ".card", "Что умею", dir="left"),
M("Текущие задания и наряды", ".card", "Текущие задания", dir="right"),
M("Выданный инструмент (видят сам сотрудник, склад и инженеры)", ".card", "Выданный инструмент", dir="left"),
], height=1000),
Shot("d_exec_report", "desktop", "volkov", [("hash", "#/reports/execution"), ("wait", 2.5), ("click", "tbody tr", "", 0), ("wait", 0.6)], [
M("Итог за период по видам работ", ".stats", dir="left"),
M("По машинам: % выполнения суточных осмотров, чек-листов, ТО, задач и поручений", "tbody tr", nth=0, dir="left"),
M("Отклонения и комментарии: значения вне нормы, возвраты, приостановки", "tbody tr", nth=1, dir="left"),
M("PDF и CSV для печати и Excel", "button", "PDF", grow=(130, 0), dir="right"),
], height=1000),
Shot("d_transfer", "desktop", "kozlov", [("js", "(async()=>{const l=await api('GET','/tasks?limit=500'); const t=l.find(x=>x.title.includes('ремней')); if(!t) throw new Error('нет задания'); location.hash='#/task/'+t.id})()"), ("wait", 1.5), ("scroll", "h2", "Передать задание", 0)], [
M("Сотрудник: начните вводить имя или фамилию — список сузится", ".pick input", dir="up"),
M("Причина передачи обязательна", ".card input", "", nth=1, dir="up"),
M("«Передать» — задание уйдёт другому, в истории появится запись", "button", "Передать", dir="right"),
], height=900),
Shot("d_history", "desktop", "sidorov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Н-1')).id})()"), ("wait", 2.5), ("scroll", "h2", "История машины", 0)], [
M("Типы событий: отказы, обслуживание узлов, наработка, наряды, задания", ".hchip", nth=0, grow=(640, 0), dir="up"),
M("Период и поиск по тексту", ".card input[type=date]", nth=0, grow=(560, 0), dir="up"),
M("События по дням: что произошло, кто, ссылка на наряд или задание", ".hrow", nth=0, dir="left"),
], height=1000),
Shot("d_report_form", "desktop", None, [("reset",), ("hash", "report"), ("wait", 1.5),
("js", "(()=>{const [l,e]=document.querySelectorAll('select'); l.value=l.options[1].value; l.dispatchEvent(new Event('change'));})()")], [
M("Место: в списке машин останется только то, что стоит в этом месте", "select", nth=0, dir="left"),
M("Машина", "select", nth=1, dir="left"),
M("Описание и контакты обязательны", "textarea", dir="left"),
M("«Отправить» — техслужба сразу получит уведомление", "button", "Отправить", dir="left"),
], height=900, crop=(260, 0, 760, 900)),
Shot("d_departments", "desktop", "admin", [("hash", "#/departments"), ("wait", 1.5)], [
M("Отдел, вышестоящий отдел, руководитель, число сотрудников", "tbody tr", nth=0, dir="up"),
M("«Сотрудники» — состав отдела", "a.btn", "Сотрудники", dir="left"),
M("«+ Добавить» — новый отдел", "button", "+ Добавить", dir="left"),
], height=600),
Shot("d_setup", "desktop", "admin", [("hash", "#/setup/equipment"), ("wait", 2.5)], [
M("Шаги мастера: ✓ — выполнено, рядом — сколько записей уже есть", ".wsteps", dir="right"),
M("Пакетный ввод: одна строка — одна запись, поля через «;» (можно вставить столбцы из Excel)", ".wbody textarea", dir="up"),
M("«Вставить пример» — образец формата", "button", "Вставить пример", dir="right"),
M("Переход между шагами и отметка «шаг выполнен»", ".wbody .row", "Далее", nth=0, dir="up"),
], height=1000),
Shot("d_backups", "desktop", "admin", [("hash", "#/backups"), ("wait", 1.0), ("click", "button", "Создать копию", 0), ("wait", 2.5)], [
M("Создать копию вручную (с фото — по желанию)", "button", "Создать копию", dir="left"),
M("Автокопии: как часто и сколько хранить", ".card", "Автоматические копии", dir="left"),
M("Скачать, восстановить или удалить копию", "td.row", nth=0, dir="up"),
M("Загрузить файл копии, например с другого сервера", "button", "Загрузить файл копии", dir="left"),
], height=1000),
Shot("d_assistant", "desktop", "ivanov", [("hash", "#/tasks"), ("wait", 1.5), ("js", "(async()=>{const b=document.getElementById('ai-btn'); for(let i=0;i<20&&!b.classList.contains('on');i++){window.assistantRefresh&&window.assistantRefresh(); await new Promise(r=>setTimeout(r,300));} b.click(); const ta=document.querySelector('#ai-form textarea'); for (const q of ['Какие у меня задания?','Как зафиксировать аварию?']) {ta.value=q; document.querySelector('#ai-form button').click(); await new Promise(r=>setTimeout(r,1800));}})()"), ("wait", 4.5)], [
M("Кнопка помощника — в правом нижнем углу на всех экранах", "#ai-btn", dir="left"),
M("Ответ по данным пользователя: его задания (с его правами)", ".ai-m.a", nth=0, dir="left"),
M("Ответ «как сделать» со ссылками на разделы портала", ".ai-m.a", nth=1, dir="left"),
M("Поле вопроса; помощник отвечает только о работе в портале", "#ai-form", dir="left"),
], height=1000),
Shot("d_assistant_settings", "desktop", "admin", [("hash", "#/server"), ("wait", 1.5), ("scroll", "h2", "AI-помощник", 0)], [
M("Провайдер, адрес API, модель и ключ", ".card .grid", "Провайдер", dir="right"),
M("Указания помощнику от администратора (необязательно)", "textarea", nth=0, dir="right"),
M("«Проверить» — тестовый вопрос и проверка фильтра; затем включить и сохранить", "button", "Проверить", grow=(110, 0), dir="up"),
], height=900),
Shot("d_work", "desktop", "volkov", [("hash", "#/work"), ("wait", 3.0)], [
M("Шапка: быстрые действия (задача, наряд, авария, неисправность, заявка) и сводка", ".qtop", dir="right"),
M("Наряды: мои, на проверку, все доступные; просроченные, статус и поиск", ".quad.q-orders .qfilters", dir="left"),
M("Задачи и поручения: мои, поставленные мной, все; день и статус", ".quad.q-tasks .qfilters", dir="right"),
M("Отказы: открытые или все, мои, источник, поиск; список или карта", ".quad.q-inc .qfilters", dir="left"),
M("Сообщения о неисправности: те же фильтры; оборудование работает, но есть проблема", ".quad.q-fault .qfilters", dir="right"),
], height=1250),
Shot("d_fault", "desktop", "kozlov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Токарный')).id+'?fault=1'})()"), ("wait", 2.5),
("scroll", "h2", "Сообщить о неисправности", 0)], [
M("Что замечено: оборудование работает, но есть проблема", ".card input", nth=0, dir="right"),
M("Какой узел (если известно); замены здесь нет", "h2", "Какой узел", dir="down"),
M("«Сообщить» — машина получит отметку «Неисправность», инженеры — уведомление", "button.fault", "Сообщить", nth=1, dir="up"),
], height=1000),
Shot("d_work_map", "desktop", "morozova", [("hash", "#/work"), ("wait", 2.5), ("click", "button", "На карте", 0), ("wait", 3.0),
("scroll", ".quad.q-inc", "", 0)], [
M("Переключатель «Список / На карте»", "button", "На карте", dir="up"),
M("Карты: завод и краны (если краны — отдельная служба); в скобках — сколько машин с отказами", ".q-inc .qlist .row", nth=0, dir="right"),
M("Машины с отказами по фильтру; нажатие открывает карточку", ".q-inc .mk", nth=0, dir="right"),
], height=1000),
Shot("d_people_report", "desktop", "volkov", [("hash", "#/reports/people"), ("wait", 2.5)], [
M("Период (обязательно), отдел, должность, сотрудник; быстрый выбор периода", ".card .grid", nth=0, dir="left"),
M("Итоги за период", ".stats", nth=0, dir="right"),
M("Список или календарь", ".tabs", nth=0, dir="right"),
M("По каждому сотруднику: выполнено, в срок, просрочки, возвраты, отклонения, трудоёмкость", "tbody tr", nth=0, dir="left"),
], height=1000),
Shot("d_people_cal", "desktop", "volkov", [("hash", "#/reports/people"), ("wait", 2.5), ("click", ".tabs a", "Календарь", 0), ("wait", 1.5),
("scroll", ".tabs", "", 0)], [
M("Календарь: в клетке — сколько выполнено за день и трудоёмкость, ч; чем темнее, тем больше часов", ".pcal tbody tr", nth=0, dir="down"),
M("Итого за период по сотруднику", ".pcal tbody tr td:last-child", nth=0, dir="left"),
], height=900),
Shot("d_cranes", "desktop", "morozova", [("hash", "#/cranes"), ("wait", 2.0)], [
M("Раздел «Краны» появляется, когда включена отдельная служба кранов", ".navh", "КРАНЫ", grow=(0, 110), dir="right"),
M("Список кранов: отдел, наработка; «Карточка» — состояние, узлы, отказы", "tbody tr", nth=0, dir="down"),
], height=800),
Shot("d_crane_map", "desktop", "orlov", [("hash", "#/map/cranes"), ("wait", 3.0)], [
M("Отдельная карта кранов: своя картинка или план завода", ".tabs a", "Краны", dir="down"),
M("Кран с открытым отказом", ".mk.st-alarm", nth=0, dir="right"),
]),
Shot("d_eq_form", "desktop", "admin", [("hash", "#/equipment"), ("wait", 1.5), ("click", "button", "+ Добавить", 0), ("wait", 1.0)], [
M("Закрепление за отделом: оборудование видят только сотрудники этого отдела и вложенных", "label", "Закреплено за отделом", grow=(300, 40), dir="right"),
M("Отметка «Кран»: машина уходит в раздел «Краны» и на карту кранов", "label", "Кран (грузо", grow=(200, 30), dir="right"),
], height=1000),
Shot("d_demo", "desktop", "admin", [("hash", "#/demo"), ("wait", 2.0)], [
M("Войти в демо самому: остальные продолжают работать с рабочими данными", "button", "Войти в демо", dir="right"),
M("Кто сейчас в демо: администратор может включить или выключить демо любому сотруднику", "table", nth=0, dir="up"),
], height=900),
Shot("d_failure_close", "desktop", "volkov", [("js", "(async()=>{const l=await api('GET','/equipment'); location.hash='#/equipment/'+l.find(x=>x.name.includes('Н-2')).id})()"), ("wait", 2.5),
("click", "button", "Закрыть отказ", 0), ("wait", 2.0), ("click", ".failform .chipf", "Замена узла", 0),
("click", ".failform .chipf", "Постепенный", 0), ("scroll", ".failform", "", 0)], [
M("Что сделано и характер отказа — два нажатия", ".failform .chipsel", nth=0, grow=(0, 70), dir="left"),
M("Время, наработка и причина уже подставлены — поправьте при необходимости", ".faildet summary", dir="left"),
M("Узел-причина и причина отказа", ".faildet select", nth=0, dir="left"),
M("Заменённые при ремонте узлы — ресурс сбросится", ".faildet label.chk", nth=0, dir="left"),
], height=1300),
Shot("d_reliability", "desktop", "morozova", [("hash", "#/reliability"), ("wait", 3.0), ("scroll", ".stats", "", 0)], [
M("Показатели по парку: отказы, наработка на отказ, её граница при доверии 90 %, восстановление, простой", ".stats", dir="down"),
M("По машинам: θ, λ, восстановление, готовность, прогноз; нажатие открывает карточку машины", ".reltable thead", dir="up"),
], height=1100),
Shot("d_reliability_bars", "desktop", "morozova", [("hash", "#/reliability"), ("wait", 3.0), ("scroll", ".relgrid", "", 0)], [
M("Причины отказов: где искать слабые места", ".relgrid .card", nth=0, dir="right"),
M("Были ли признаки заранее — отказы, которые можно было предотвратить", ".relgrid .card", nth=3, dir="left"),
], height=1000),
Shot("d_drift", "desktop", "morozova", [("hash", "#/reliability/drift"), ("wait", 3.0)], [
M("Сколько параметров приближаются к границе нормы", "h2", "Параметры под наблюдением", dir="down"),
M("Тренд, направление, дней до границы и выходы за норму", ".reltable thead", dir="up"),
], height=900),
Shot("d_server", "desktop", "admin", [("hash", "#/server"), ("wait", 1.5), ("js", "document.querySelector('.grid.form input').value='/data/photos'")], [
M("Каталог для фото и максимальный размер снимка", ".grid.form", nth=0, dir="right"),
M("Состояние: сколько фото и сколько свободно места", ".card", nth=1, dir="right"),
]),
]
MOBILE = [
Shot("m_login", "mobile", None, [("reset",), ("hash", "")], [
M("Логин и пароль", "input", nth=0, grow=(0, 62), dir="up"),
M("«Войти»", "button", "Войти"),
]),
Shot("m_list", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("wait", 1.5)], [
M("Название экрана и время последней синхронизации", "#bar h1", dir="down"),
M("Кнопка меню: настройки и разделы портала", "#burger", dir="down"),
M("Принудительное обновление", "#bar .ib", nth=0, dir="down"),
M("Поиск по названию, оборудованию или номеру", "input[type=search]", dir="down"),
M("Фильтры: активные, мои, завершённые", ".filters", dir="down"),
M("Карточка наряда: статус, срок, прогресс. «▶ можно выполнять» — наряд ваш", ".card.tap", nth=0, dir="up"),
]),
Shot("m_menu", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("js", "document.getElementById('burger').click()"), ("wait", 0.8)], [
M("Кто вошёл и состояние синхронизации", "#drawer .dhead", dir="down"),
M("Без связи работают наряды и настройки", "#drawer .navh", "БЕЗ СВЯЗИ", grow=(0, 100), dir="down"),
M("Разделы портала открываются в полной версии сайта (нужна связь)", "#drawer .navh", "ПОРТАЛ", grow=(0, 380), dir="up"),
], height=844),
Shot("m_order", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("js", m_order_js("Проверка насосной", "in_progress")), ("wait", 2.0), ("scroll", "h2", "Чек-лист", 0)], [
M("Число: введите значение; норма указана под полем", "input[inputmode=decimal]", nth=0, grow=(0, 28), dir="up"),
M("Выбор варианта — одним нажатием", ".item .chips", nth=0, dir="up"),
M("Фото: «Снять» откроет камеру, значок справа — выбор из галереи", ".photos", nth=0, dir="down"),
], height=844),
Shot("m_submit", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("js", m_order_js("ТО компрессора", "new")), ("wait", 2.0), ("scroll", "h2", "Чек-лист", 0)], [
M("Статус пункта: «Не начато», «В работе», «Выполнено»", ".item .chips", nth=1, dir="up"),
M("«Взять в работу» закрепляет наряд за вами. «Сдать наряд» отправляет его на проверку", ".actions", nth=0, dir="up"),
], height=844),
Shot("m_offline", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("js", m_order_js("ТО компрессора", "new")), ("wait", 2.0), ("offline", True),
("js", "document.querySelectorAll('.item')[3].querySelectorAll('.chip')[2].click()"), ("wait", 1.5),
("scroll", ".item", "Слив конденсата", 0)], [
M("Ответ записан на телефоне. Он уйдёт на сервер сам, как только появится сеть", ".item .chip.on", "Выполнено", dir="right"),
M("Кнопка «Сдать наряд» тоже работает без сети: наряд будет сдан позже", ".actions", dir="up"),
], height=844),
Shot("m_review", "mobile", "morozova", [("m_login", "morozova", "demo1234"), ("js", m_order_js("ТО компрессора", "review")), ("wait", 2.0), ("scroll", "h2", "Проверка", 0)], [
M("Комментарий проверяющего (обязателен при возврате)", "textarea[placeholder*='обязателен']", dir="up"),
M("«Принять» или «Вернуть» на доработку", ".card .row button", "Принять", grow=(190, 0), dir="up"),
], height=844),
Shot("m_settings", "mobile", "sidorov", [("m_login", "sidorov", "demo1234"), ("hash", "#/s"), ("wait", 1.5)], [
M("Синхронизировать сейчас", "button", "Синхронизировать", dir="up"),
M("Уведомления о новых нарядах", "button", "Включить", dir="up"),
M("Выход из аккаунта", "button", "Выйти", dir="up"),
], height=844),
]