Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android

This commit is contained in:
darkorion
2026-10-13 09:09:53 +03:00
commit c298f5a830
247 changed files with 23200 additions and 0 deletions
+403
View File
@@ -0,0 +1,403 @@
"""Сценарии скриншотов руководства: что открыть, под кем, какие элементы отметить (метки попадают и в легенду 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),
]