mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 19:44:48 +00:00
feat(miniapp): redesign quick-action buttons + fix voice duplication
- Quick-actions: 2×2 grid, walnut circle icon chip, 3D card press effect - Voice-to-text: recompute-from-scratch in setupVoiceInput (client note block) - Cache bump v=20260514f Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f280dea9ea
commit
61f23c9bca
@ -419,13 +419,52 @@ const Clients = (function () {
|
|||||||
// Управление карточкой — кнопки прямо под шапкой
|
// Управление карточкой — кнопки прямо под шапкой
|
||||||
root.appendChild(renderClientManagement(client));
|
root.appendChild(renderClientManagement(client));
|
||||||
|
|
||||||
// Быстрые действия для менеджера
|
// Быстрые действия для менеджера — кастомные SVG-иконки в орехе
|
||||||
|
const QA_ICON_PODBOR = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M12 3l1.8 4.6L18 9l-4.2 1.4L12 15l-1.8-4.6L6 9l4.2-1.4L12 3z"/>
|
||||||
|
<path d="M19 14.5l.7 1.8 1.8.7-1.8.7-.7 1.8-.7-1.8-1.8-.7 1.8-.7.7-1.8z"/>
|
||||||
|
<path d="M5 16.5l.5 1.3 1.3.5-1.3.5-.5 1.3-.5-1.3-1.3-.5 1.3-.5.5-1.3z"/>
|
||||||
|
</svg>`;
|
||||||
|
const QA_ICON_RULER = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.4 2.4 0 0 1 0-3.4l2.6-2.6a2.4 2.4 0 0 1 3.4 0z"/>
|
||||||
|
<path d="M14.5 12.5 12 15"/>
|
||||||
|
<path d="M11.5 9.5 9 12"/>
|
||||||
|
<path d="M8.5 6.5 6 9"/>
|
||||||
|
<path d="M17.5 15.5 15 18"/>
|
||||||
|
</svg>`;
|
||||||
|
const QA_ICON_WRENCH = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
|
||||||
|
</svg>`;
|
||||||
|
const QA_ICON_COPY = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<rect x="8" y="8" width="13" height="13" rx="2"/>
|
||||||
|
<path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3"/>
|
||||||
|
</svg>`;
|
||||||
const actionsRow = el(`
|
const actionsRow = el(`
|
||||||
<div class="client-quick-actions">
|
<div class="client-quick-actions">
|
||||||
<button class="qa-btn" data-act="podbor">🤖<span>Подбор техники</span></button>
|
<button class="qa-btn" data-act="podbor" type="button">
|
||||||
<button class="qa-btn" data-act="measure">📐<span>Заказать замер</span></button>
|
<span class="qa-icon">${QA_ICON_PODBOR}</span>
|
||||||
<button class="qa-btn" data-act="assembly">🔨<span>Заказать сборку</span></button>
|
<span class="qa-label">Подбор техники</span>
|
||||||
<button class="qa-btn" data-act="copy">📋<span>Копировать ФИО+тел</span></button>
|
</button>
|
||||||
|
<button class="qa-btn" data-act="measure" type="button">
|
||||||
|
<span class="qa-icon">${QA_ICON_RULER}</span>
|
||||||
|
<span class="qa-label">Заказать замер</span>
|
||||||
|
</button>
|
||||||
|
<button class="qa-btn" data-act="assembly" type="button">
|
||||||
|
<span class="qa-icon">${QA_ICON_WRENCH}</span>
|
||||||
|
<span class="qa-label">Заказать сборку</span>
|
||||||
|
</button>
|
||||||
|
<button class="qa-btn" data-act="copy" type="button">
|
||||||
|
<span class="qa-icon">${QA_ICON_COPY}</span>
|
||||||
|
<span class="qa-label">Копировать ФИО+тел</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
actionsRow.querySelectorAll(".qa-btn").forEach(btn => {
|
actionsRow.querySelectorAll(".qa-btn").forEach(btn => {
|
||||||
@ -1233,13 +1272,11 @@ const Clients = (function () {
|
|||||||
}
|
}
|
||||||
let rec = null;
|
let rec = null;
|
||||||
let recording = false;
|
let recording = false;
|
||||||
let baseText = ""; // текст до начала записи — чтобы не перетирать
|
let baseText = ""; // что было в textarea ДО старта записи
|
||||||
|
let confirmedFinal = ""; // финальная фраза текущей сессии записи
|
||||||
|
|
||||||
micBtn.addEventListener("click", () => {
|
micBtn.addEventListener("click", () => {
|
||||||
if (recording) {
|
if (recording) { rec?.stop(); return; }
|
||||||
rec?.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
rec = new SR();
|
rec = new SR();
|
||||||
rec.lang = "ru-RU";
|
rec.lang = "ru-RU";
|
||||||
@ -1251,7 +1288,7 @@ const Clients = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
baseText = (textarea.value || "").trim();
|
baseText = (textarea.value || "").trim();
|
||||||
const sep = baseText ? "\n" : "";
|
confirmedFinal = "";
|
||||||
|
|
||||||
rec.onstart = () => {
|
rec.onstart = () => {
|
||||||
recording = true;
|
recording = true;
|
||||||
@ -1261,20 +1298,21 @@ const Clients = (function () {
|
|||||||
status.className = "note-status";
|
status.className = "note-status";
|
||||||
haptic && haptic("impact");
|
haptic && haptic("impact");
|
||||||
};
|
};
|
||||||
|
// Защита от дублей: пересчитываем ВСЕ финальные и interim с нуля
|
||||||
|
// на каждом событии. Не полагаемся на ev.resultIndex (в Chrome он
|
||||||
|
// ведёт себя нестабильно при паузах — отсюда дублирование слов).
|
||||||
rec.onresult = (ev) => {
|
rec.onresult = (ev) => {
|
||||||
let interim = "";
|
let finalAll = "", interim = "";
|
||||||
let final = "";
|
for (let i = 0; i < ev.results.length; i++) {
|
||||||
for (let i = ev.resultIndex; i < ev.results.length; i++) {
|
|
||||||
const t = ev.results[i][0].transcript;
|
const t = ev.results[i][0].transcript;
|
||||||
if (ev.results[i].isFinal) final += t;
|
if (ev.results[i].isFinal) finalAll += t;
|
||||||
else interim += t;
|
else interim += t;
|
||||||
}
|
}
|
||||||
if (final) {
|
confirmedFinal = finalAll.trim();
|
||||||
baseText = (baseText + sep + final).trim();
|
const sep = baseText ? " " : "";
|
||||||
textarea.value = baseText;
|
const fp = confirmedFinal ? sep + confirmedFinal : "";
|
||||||
} else if (interim) {
|
const ip = interim.trim() ? ((baseText || confirmedFinal) ? " " : "") + interim.trim() : "";
|
||||||
textarea.value = baseText + sep + interim;
|
textarea.value = baseText + fp + ip;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
rec.onerror = (ev) => {
|
rec.onerror = (ev) => {
|
||||||
status.textContent = "Ошибка распознавания: " + (ev.error || "неизвестно");
|
status.textContent = "Ошибка распознавания: " + (ev.error || "неизвестно");
|
||||||
@ -1288,6 +1326,11 @@ const Clients = (function () {
|
|||||||
micBtn.classList.remove("rec");
|
micBtn.classList.remove("rec");
|
||||||
micBtn.textContent = "🎤 Диктовать";
|
micBtn.textContent = "🎤 Диктовать";
|
||||||
if (status.textContent === "Слушаю...") status.textContent = "";
|
if (status.textContent === "Слушаю...") status.textContent = "";
|
||||||
|
// Фиксируем подтверждённый текст в baseText на случай повторного запуска
|
||||||
|
if (confirmedFinal) {
|
||||||
|
baseText = (baseText + (baseText ? " " : "") + confirmedFinal).trim();
|
||||||
|
textarea.value = baseText;
|
||||||
|
}
|
||||||
haptic && haptic("impact");
|
haptic && haptic("impact");
|
||||||
};
|
};
|
||||||
try { rec.start(); }
|
try { rec.start(); }
|
||||||
|
|||||||
@ -2134,34 +2134,74 @@
|
|||||||
}
|
}
|
||||||
.client-call-btn:active { transform: scale(0.95); }
|
.client-call-btn:active { transform: scale(0.95); }
|
||||||
|
|
||||||
|
/* ===== Быстрые действия — 2×2 объёмные кнопки-карточки ===== */
|
||||||
.client-quick-actions {
|
.client-quick-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
margin: 14px 0 18px;
|
margin: 14px 0 18px;
|
||||||
}
|
}
|
||||||
.client-quick-actions .qa-btn {
|
|
||||||
|
.qa-btn {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 9px;
|
||||||
padding: 12px 6px;
|
padding: 15px 10px 13px;
|
||||||
background: var(--card, #fff);
|
background:
|
||||||
border: 1px solid rgba(107, 74, 43, 0.18);
|
linear-gradient(160deg,
|
||||||
border-radius: 12px;
|
rgba(255, 255, 255, 0.80) 0%,
|
||||||
|
rgba(245, 237, 220, 0.62) 100%);
|
||||||
|
border: 1px solid rgba(107, 74, 43, 0.16);
|
||||||
|
border-radius: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 19px;
|
box-shadow:
|
||||||
line-height: 1;
|
0 2px 0 rgba(107, 74, 43, 0.20),
|
||||||
|
0 1px 5px rgba(0, 0, 0, 0.07),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.90);
|
||||||
|
transition: transform 0.10s ease, box-shadow 0.10s ease;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
.client-quick-actions .qa-btn:active { background: var(--paper-2, #F5EDDC); }
|
|
||||||
.client-quick-actions .qa-btn span {
|
.qa-btn:active {
|
||||||
font-size: 11px;
|
transform: translateY(2px);
|
||||||
font-weight: 500;
|
box-shadow:
|
||||||
|
0 0px 0 rgba(107, 74, 43, 0.20),
|
||||||
|
inset 0 1px 4px rgba(0, 0, 0, 0.14),
|
||||||
|
inset 0 0 0 1px rgba(107, 74, 43, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Круглый орехово-коричневый чип с иконкой */
|
||||||
|
.qa-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(145deg, #845A2E 0%, #5C3A1C 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 2px 0 rgba(0, 0, 0, 0.24),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.18);
|
||||||
|
color: #F5DAAA;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qa-icon svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Подпись под иконкой */
|
||||||
|
.qa-label {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 600;
|
||||||
color: var(--ink, #1F1A14);
|
color: var(--ink, #1F1A14);
|
||||||
margin-top: 4px;
|
line-height: 1.25;
|
||||||
line-height: 1.2;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== Хронология клиента ===== */
|
/* ===== Хронология клиента ===== */
|
||||||
|
|||||||
@ -12,14 +12,14 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Cormorant+Garamond:ital,wght@1,400;1,500;1,600&family=Caveat:wght@500;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&family=Cormorant+Garamond:ital,wght@1,400;1,500;1,600&family=Caveat:wght@500;700&display=swap">
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||||
<link rel="stylesheet" href="assets/styles.css?v=20260514e">
|
<link rel="stylesheet" href="assets/styles.css?v=20260514f">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260514e">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260514f">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
|
<!-- Splash — лого @wasrusgen1 + опилки (16) + вращающийся диск -->
|
||||||
<div class="loader splash" id="splash">
|
<div class="loader splash" id="splash">
|
||||||
<div class="brand-logo-wrap">
|
<div class="brand-logo-wrap">
|
||||||
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514e" alt="@wasrusgen1">
|
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514f" alt="@wasrusgen1">
|
||||||
<div class="splash-dust" aria-hidden="true">
|
<div class="splash-dust" aria-hidden="true">
|
||||||
<span class="dust d1"></span> <span class="dust d2"></span>
|
<span class="dust d1"></span> <span class="dust d2"></span>
|
||||||
<span class="dust d3"></span> <span class="dust d4"></span>
|
<span class="dust d3"></span> <span class="dust d4"></span>
|
||||||
@ -35,15 +35,15 @@
|
|||||||
<div class="brand-tagline-gold">CRM</div>
|
<div class="brand-tagline-gold">CRM</div>
|
||||||
</div>
|
</div>
|
||||||
<main id="app"></main>
|
<main id="app"></main>
|
||||||
<script src="assets/icons.js?v=20260514e"></script>
|
<script src="assets/icons.js?v=20260514f"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260514e"></script>
|
<script src="assets/podbor.config.js?v=20260514f"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260514e"></script>
|
<script src="assets/podbor.picts.js?v=20260514f"></script>
|
||||||
<script src="assets/podbor.js?v=20260514e"></script>
|
<script src="assets/podbor.js?v=20260514f"></script>
|
||||||
<script src="assets/clients.js?v=20260514e"></script>
|
<script src="assets/clients.js?v=20260514f"></script>
|
||||||
<script src="assets/zamer-picts.js?v=20260514e"></script>
|
<script src="assets/zamer-picts.js?v=20260514f"></script>
|
||||||
<script src="assets/measurements.js?v=20260514e"></script>
|
<script src="assets/measurements.js?v=20260514f"></script>
|
||||||
<script src="assets/request.js?v=20260514e"></script>
|
<script src="assets/request.js?v=20260514f"></script>
|
||||||
<script src="assets/assembly.js?v=20260514e"></script>
|
<script src="assets/assembly.js?v=20260514f"></script>
|
||||||
<script src="assets/app.js?v=20260514e"></script>
|
<script src="assets/app.js?v=20260514f"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user