mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 15:44:47 +00:00
fix(note): view/edit toggle — textarea closes after save
Note block now has display mode (read-only text) and edit mode (textarea). Default is display. "Изменить" opens editor, "Сохранить" saves and returns to display, "Отмена" discards. No more always-open textarea confusion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd8691671a
commit
cbea202de5
@ -1205,58 +1205,98 @@ const Clients = (function () {
|
|||||||
<section class="block client-note-block">
|
<section class="block client-note-block">
|
||||||
<div class="block-head">
|
<div class="block-head">
|
||||||
<span>📝 Примечание</span>
|
<span>📝 Примечание</span>
|
||||||
|
<button class="note-edit-toggle" id="noteEditBtn" type="button" title="Редактировать">Изменить</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Режим просмотра -->
|
||||||
|
<div class="note-view" id="noteView">
|
||||||
|
<p class="note-text" id="noteDisplayText" style="color:var(--muted,#998877);font-style:italic;">Загружаем...</p>
|
||||||
<span class="note-meta" id="noteMeta"></span>
|
<span class="note-meta" id="noteMeta"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-editor">
|
|
||||||
<textarea id="noteText" rows="3" placeholder="Заметки по клиенту — характер, предпочтения, договорённости, статус..."></textarea>
|
<!-- Режим редактирования (скрыт по умолчанию) -->
|
||||||
|
<div class="note-editor" id="noteEditor" style="display:none;">
|
||||||
|
<textarea id="noteText" rows="4" placeholder="Заметки по клиенту — характер, предпочтения, договорённости, статус..."></textarea>
|
||||||
<div class="note-actions">
|
<div class="note-actions">
|
||||||
<button class="btn-mic" id="noteMic" type="button" title="Голосовой ввод">🎤 Диктовать</button>
|
<button class="btn-mic" id="noteMic" type="button" title="Голосовой ввод">🎤 Диктовать</button>
|
||||||
<button class="btn-secondary" id="noteSave" type="button">Сохранить</button>
|
<button class="btn-secondary" id="noteCancel" type="button">Отмена</button>
|
||||||
|
<button class="btn-primary" id="noteSave" type="button">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="note-status" id="noteStatus"></div>
|
<div class="note-status" id="noteStatus"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const textarea = section.querySelector("#noteText");
|
const view = section.querySelector("#noteView");
|
||||||
const meta = section.querySelector("#noteMeta");
|
const editor = section.querySelector("#noteEditor");
|
||||||
const status = section.querySelector("#noteStatus");
|
const displayTx = section.querySelector("#noteDisplayText");
|
||||||
|
const textarea = section.querySelector("#noteText");
|
||||||
|
const meta = section.querySelector("#noteMeta");
|
||||||
|
const status = section.querySelector("#noteStatus");
|
||||||
|
const editBtn = section.querySelector("#noteEditBtn");
|
||||||
|
let savedText = "";
|
||||||
|
|
||||||
|
function showView(text, updatedAt) {
|
||||||
|
savedText = text || "";
|
||||||
|
displayTx.style.fontStyle = text ? "normal" : "italic";
|
||||||
|
displayTx.style.color = text ? "var(--ink,#1F1A14)" : "var(--muted,#998877)";
|
||||||
|
displayTx.textContent = text || "Нет примечания";
|
||||||
|
if (updatedAt) meta.textContent = "обновлено " + formatDate(updatedAt);
|
||||||
|
editor.style.display = "none";
|
||||||
|
view.style.display = "";
|
||||||
|
editBtn.textContent = "Изменить";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditor() {
|
||||||
|
textarea.value = savedText;
|
||||||
|
status.textContent = "";
|
||||||
|
status.className = "note-status";
|
||||||
|
editor.style.display = "";
|
||||||
|
view.style.display = "none";
|
||||||
|
editBtn.textContent = "Свернуть";
|
||||||
|
textarea.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем сохранённую заметку
|
// Загружаем сохранённую заметку
|
||||||
fetchClientNote(client).then(data => {
|
fetchClientNote(client)
|
||||||
if (data?.note) textarea.value = data.note;
|
.then(data => showView(data?.note || "", data?.updated_at || ""))
|
||||||
if (data?.updated_at) {
|
.catch(() => showView("", ""));
|
||||||
meta.textContent = "обновлено " + formatDate(data.updated_at);
|
|
||||||
}
|
// Переключатель просмотр ↔ редактирование
|
||||||
}).catch(() => {});
|
editBtn.addEventListener("click", () => {
|
||||||
|
if (editor.style.display === "none") showEditor();
|
||||||
|
else showView(savedText, meta.textContent.replace("обновлено ", ""));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отмена — вернуть исходный текст
|
||||||
|
section.querySelector("#noteCancel").addEventListener("click", () => {
|
||||||
|
showView(savedText, meta.textContent.replace("обновлено ", ""));
|
||||||
|
});
|
||||||
|
|
||||||
// Сохранение
|
// Сохранение
|
||||||
section.querySelector("#noteSave").addEventListener("click", async () => {
|
section.querySelector("#noteSave").addEventListener("click", async () => {
|
||||||
const btn = section.querySelector("#noteSave");
|
const btn = section.querySelector("#noteSave");
|
||||||
btn.disabled = true;
|
btn.disabled = true; btn.textContent = "Сохраняем...";
|
||||||
btn.textContent = "Сохраняем...";
|
status.textContent = ""; status.className = "note-status";
|
||||||
try {
|
try {
|
||||||
const data = await saveClientNote(client, textarea.value);
|
const data = await saveClientNote(client, textarea.value);
|
||||||
if (data?.ok) {
|
if (data?.ok) {
|
||||||
status.textContent = "✓ сохранено";
|
haptic && haptic("success");
|
||||||
status.className = "note-status ok";
|
showView(textarea.value, data.updated_at || "");
|
||||||
if (data.updated_at) meta.textContent = "обновлено " + formatDate(data.updated_at);
|
|
||||||
setTimeout(() => { status.textContent = ""; }, 2500);
|
|
||||||
} else {
|
} else {
|
||||||
status.textContent = "Ошибка: " + (data?.error || "не сохранилось");
|
status.textContent = "Ошибка: " + (data?.error || "не сохранилось");
|
||||||
status.className = "note-status err";
|
status.className = "note-status err";
|
||||||
|
btn.disabled = false; btn.textContent = "Сохранить";
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
status.textContent = "Сеть: " + e.message;
|
status.textContent = "Сеть: " + e.message;
|
||||||
status.className = "note-status err";
|
status.className = "note-status err";
|
||||||
|
btn.disabled = false; btn.textContent = "Сохранить";
|
||||||
}
|
}
|
||||||
btn.disabled = false;
|
|
||||||
btn.textContent = "Сохранить";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Голосовой ввод через Web Speech API
|
// Голосовой ввод
|
||||||
const micBtn = section.querySelector("#noteMic");
|
setupVoiceInput(section.querySelector("#noteMic"), textarea, status);
|
||||||
setupVoiceInput(micBtn, textarea, status);
|
|
||||||
|
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2309,7 +2309,41 @@
|
|||||||
.client-note-block .block-head {
|
.client-note-block .block-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.client-note-block .block-head > span:first-child {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопка-переключатель Изменить / Свернуть */
|
||||||
|
.note-edit-toggle {
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(107, 74, 43, 0.30);
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--walnut, #6B4A2B);
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: background 0.12s, color 0.12s;
|
||||||
|
}
|
||||||
|
.note-edit-toggle:active {
|
||||||
|
background: rgba(107, 74, 43, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Просмотр-режим */
|
||||||
|
.note-view {
|
||||||
|
padding: 6px 2px 4px;
|
||||||
|
}
|
||||||
|
.note-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--ink, #1F1A14);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
margin: 0 0 4px;
|
||||||
}
|
}
|
||||||
.client-note-block .note-meta {
|
.client-note-block .note-meta {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|||||||
@ -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=20260514g">
|
<link rel="stylesheet" href="assets/styles.css?v=20260514h">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260514g">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260514h">
|
||||||
</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=20260514g" alt="@wasrusgen1">
|
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514h" 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=20260514g"></script>
|
<script src="assets/icons.js?v=20260514h"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260514g"></script>
|
<script src="assets/podbor.config.js?v=20260514h"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260514g"></script>
|
<script src="assets/podbor.picts.js?v=20260514h"></script>
|
||||||
<script src="assets/podbor.js?v=20260514g"></script>
|
<script src="assets/podbor.js?v=20260514h"></script>
|
||||||
<script src="assets/clients.js?v=20260514g"></script>
|
<script src="assets/clients.js?v=20260514h"></script>
|
||||||
<script src="assets/zamer-picts.js?v=20260514g"></script>
|
<script src="assets/zamer-picts.js?v=20260514h"></script>
|
||||||
<script src="assets/measurements.js?v=20260514g"></script>
|
<script src="assets/measurements.js?v=20260514h"></script>
|
||||||
<script src="assets/request.js?v=20260514g"></script>
|
<script src="assets/request.js?v=20260514h"></script>
|
||||||
<script src="assets/assembly.js?v=20260514g"></script>
|
<script src="assets/assembly.js?v=20260514h"></script>
|
||||||
<script src="assets/app.js?v=20260514g"></script>
|
<script src="assets/app.js?v=20260514h"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user