mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 17:44:48 +00:00
UX: карточка клиента — кнопки наверх, объёмные, хронология свёрнута
1. Кнопки управления (Редактировать/Удалить) теперь сразу под шапкой карточки — не нужно скроллить через таймлайн/файлы. 2. Брендовые объёмные кнопки .ct-btn вместо текста с эмодзи: - inline SVG (карандаш + корзина), монолиния stroke-width 1.7 - градиент 3-стопа (highlight → base → shadow) - inset highlights + drop-shadow для лёгкого 3D - Edit: палитра ореха #8A6541 → #6B4A2B → #523620 - Delete: благородный кирпич #C95A4A → #A6382A → #832418 - press-state: invert insets + translateY(1px) - filter:brightness на hover 3. Хронология свёрнута по умолчанию (<details> без open). Шеврон поворачивается при раскрытии. summary без default-маркера. index.html: cache bump v=20260514e
This commit is contained in:
parent
4612c3a4e4
commit
f280dea9ea
@ -416,6 +416,9 @@ const Clients = (function () {
|
|||||||
</div>
|
</div>
|
||||||
`));
|
`));
|
||||||
|
|
||||||
|
// Управление карточкой — кнопки прямо под шапкой
|
||||||
|
root.appendChild(renderClientManagement(client));
|
||||||
|
|
||||||
// Быстрые действия для менеджера
|
// Быстрые действия для менеджера
|
||||||
const actionsRow = el(`
|
const actionsRow = el(`
|
||||||
<div class="client-quick-actions">
|
<div class="client-quick-actions">
|
||||||
@ -482,28 +485,48 @@ const Clients = (function () {
|
|||||||
// Детальные списки внизу (свёрнуты)
|
// Детальные списки внизу (свёрнуты)
|
||||||
detailsPlaceholder.replaceWith(renderClientDetails(client, myMeasurements));
|
detailsPlaceholder.replaceWith(renderClientDetails(client, myMeasurements));
|
||||||
|
|
||||||
// Управление карточкой клиента — редактировать + (условно) удалить
|
// (управление перенесено наверх — сразу под шапку)
|
||||||
root.appendChild(renderClientManagement(client));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===================== Управление карточкой (edit / delete) ===================== */
|
/* ===================== Управление карточкой (edit / delete) ===================== */
|
||||||
|
|
||||||
|
// Кастомные SVG-иконки в брендовом монолинейном стиле (stroke-width 1.7)
|
||||||
|
const ICON_EDIT_SVG = `
|
||||||
|
<svg class="ct-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M3.5 20.5h4.5l10-10-4.5-4.5-10 10v4.5z"/>
|
||||||
|
<path d="M14 6l4 4"/>
|
||||||
|
<path d="M14.5 4.5l1.5-1.5a2 2 0 0 1 2.8 0l1.7 1.7a2 2 0 0 1 0 2.8L19 9"/>
|
||||||
|
</svg>`;
|
||||||
|
const ICON_TRASH_SVG = `
|
||||||
|
<svg class="ct-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
|
<path d="M4 7h16"/>
|
||||||
|
<path d="M9.5 7V5.2A1.7 1.7 0 0 1 11.2 3.5h1.6A1.7 1.7 0 0 1 14.5 5.2V7"/>
|
||||||
|
<path d="M6 7l1.1 12.3a1.8 1.8 0 0 0 1.8 1.7h6.2a1.8 1.8 0 0 0 1.8-1.7L18 7"/>
|
||||||
|
<path d="M10 11.5v5.5"/>
|
||||||
|
<path d="M14 11.5v5.5"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
function renderClientManagement(client) {
|
function renderClientManagement(client) {
|
||||||
const inWork = !!client.in_work;
|
const inWork = !!client.in_work;
|
||||||
const wrap = el(`
|
const wrap = el(`
|
||||||
<section class="block client-manage" style="margin-top:18px;">
|
<div class="client-toolbar ${inWork ? "is-locked" : "is-free"}">
|
||||||
<div class="block-head">⚙️ Управление карточкой</div>
|
<button class="ct-btn ct-edit" id="editClient" type="button" aria-label="Редактировать">
|
||||||
<div class="client-manage-info" style="padding:6px 4px 10px;font-size:12.5px;color:var(--muted);line-height:1.4;">
|
${ICON_EDIT_SVG}
|
||||||
${inWork
|
<span class="ct-label">Редактировать</span>
|
||||||
? "Клиент в работе. Удалить нельзя, можно только отредактировать данные."
|
</button>
|
||||||
: "Клиент ещё не передан в работу — можно изменить данные или удалить карточку."}
|
${inWork ? "" : `
|
||||||
|
<button class="ct-btn ct-delete" id="deleteClient" type="button" aria-label="Удалить">
|
||||||
|
${ICON_TRASH_SVG}
|
||||||
|
<span class="ct-label">Удалить</span>
|
||||||
|
</button>`}
|
||||||
|
<div class="ct-hint">${inWork
|
||||||
|
? "В работе — данные можно править"
|
||||||
|
: "Не в работе — можно править или удалить"}
|
||||||
</div>
|
</div>
|
||||||
<div class="podbor-cta-row" style="gap:8px;flex-wrap:wrap;">
|
<div class="ct-result" id="manageResult"></div>
|
||||||
<button class="btn-secondary" id="editClient" type="button">✏️ Редактировать</button>
|
</div>
|
||||||
${inWork ? "" : `<button class="btn-danger" id="deleteClient" type="button">🗑 Удалить клиента</button>`}
|
|
||||||
</div>
|
|
||||||
<div id="manageResult" style="margin-top:10px;font-size:12.5px;"></div>
|
|
||||||
</section>
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
wrap.querySelector("#editClient")?.addEventListener("click", () => {
|
wrap.querySelector("#editClient")?.addEventListener("click", () => {
|
||||||
@ -515,8 +538,10 @@ const Clients = (function () {
|
|||||||
const confirmed = await confirmDialog(`Удалить клиента ${client.client_name}? Это нельзя отменить из бота.`);
|
const confirmed = await confirmDialog(`Удалить клиента ${client.client_name}? Это нельзя отменить из бота.`);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
const btn = wrap.querySelector("#deleteClient");
|
const btn = wrap.querySelector("#deleteClient");
|
||||||
|
const labelEl = btn.querySelector(".ct-label");
|
||||||
const result = wrap.querySelector("#manageResult");
|
const result = wrap.querySelector("#manageResult");
|
||||||
btn.disabled = true; btn.textContent = "Удаляем...";
|
btn.disabled = true;
|
||||||
|
if (labelEl) labelEl.textContent = "Удаляем…";
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${BACKEND_URL}/api/client_delete`, {
|
const res = await fetch(`${BACKEND_URL}/api/client_delete`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -529,17 +554,19 @@ const Clients = (function () {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
const msg = data.msg || data.error;
|
const msg = data.msg || data.error;
|
||||||
result.innerHTML = `<span style="color:#C0392B;">${escHtml(msg)}</span>`;
|
result.innerHTML = `<span class="ct-err">${escHtml(msg)}</span>`;
|
||||||
btn.disabled = false; btn.textContent = "🗑 Удалить клиента";
|
btn.disabled = false;
|
||||||
|
if (labelEl) labelEl.textContent = "Удалить";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
haptic && haptic("success");
|
haptic && haptic("success");
|
||||||
clientsCache = null;
|
clientsCache = null;
|
||||||
result.innerHTML = `<span style="color:#27AE60;">Архивировано ${data.archived} записей. Возвращаемся в список...</span>`;
|
result.innerHTML = `<span class="ct-ok">Архивировано ${data.archived} записей. Возвращаемся в список…</span>`;
|
||||||
setTimeout(() => { location.hash = "#/clients"; window.location.reload(); }, 1200);
|
setTimeout(() => { location.hash = "#/clients"; window.location.reload(); }, 1200);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result.innerHTML = `<span style="color:#C0392B;">Сеть: ${escHtml(e.message)}</span>`;
|
result.innerHTML = `<span class="ct-err">Сеть: ${escHtml(e.message)}</span>`;
|
||||||
btn.disabled = false; btn.textContent = "🗑 Удалить клиента";
|
btn.disabled = false;
|
||||||
|
if (labelEl) labelEl.textContent = "Удалить";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -729,8 +756,11 @@ const Clients = (function () {
|
|||||||
events.sort((a, b) => (b.ts || "").localeCompare(a.ts || ""));
|
events.sort((a, b) => (b.ts || "").localeCompare(a.ts || ""));
|
||||||
|
|
||||||
const section = el(`
|
const section = el(`
|
||||||
<section class="block client-timeline-block">
|
<details class="block client-timeline-block client-collapse">
|
||||||
<div class="block-head">🕒 Хронология · ${events.length}</div>
|
<summary class="block-head collapse-head">
|
||||||
|
<span class="collapse-title">🕒 Хронология · ${events.length}</span>
|
||||||
|
<span class="collapse-chev" aria-hidden="true">›</span>
|
||||||
|
</summary>
|
||||||
${events.length === 0
|
${events.length === 0
|
||||||
? `<div class="empty" style="padding:14px;text-align:center;color:var(--muted);font-size:13px;">Пока нет событий</div>`
|
? `<div class="empty" style="padding:14px;text-align:center;color:var(--muted);font-size:13px;">Пока нет событий</div>`
|
||||||
: `<div class="timeline">${events.map(ev => `
|
: `<div class="timeline">${events.map(ev => `
|
||||||
@ -743,7 +773,7 @@ const Clients = (function () {
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
`).join("")}</div>`}
|
`).join("")}</div>`}
|
||||||
</section>
|
</details>
|
||||||
`);
|
`);
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3052,6 +3052,143 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Тулбар управления карточкой клиента — объёмные кнопки ===== */
|
||||||
|
.client-toolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 12px 0 4px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.client-toolbar.is-locked {
|
||||||
|
grid-template-columns: 1fr; /* только «Редактировать», когда в работе */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-btn {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 9px;
|
||||||
|
min-height: 46px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 13px;
|
||||||
|
font-family: var(--font-ui, "Inter", system-ui, sans-serif);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
color: #FBF7F0;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
|
||||||
|
}
|
||||||
|
.ct-btn .ct-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
stroke-width: 1.7;
|
||||||
|
flex-shrink: 0;
|
||||||
|
filter: drop-shadow(0 1px 0 rgba(0,0,0,0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit — глубокий орех */
|
||||||
|
.ct-edit {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg,
|
||||||
|
rgba(255,255,255,0.10) 0%,
|
||||||
|
rgba(255,255,255,0.00) 36%,
|
||||||
|
rgba(0,0,0,0.00) 64%,
|
||||||
|
rgba(0,0,0,0.16) 100%),
|
||||||
|
linear-gradient(180deg, #8A6541 0%, #6B4A2B 55%, #523620 100%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.22),
|
||||||
|
inset 0 -1px 0 rgba(0,0,0,0.18),
|
||||||
|
0 3px 8px rgba(82, 54, 32, 0.32),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.ct-edit:hover { filter: brightness(1.04); }
|
||||||
|
.ct-edit:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.10),
|
||||||
|
inset 0 2px 4px rgba(0,0,0,0.22),
|
||||||
|
0 1px 2px rgba(0,0,0,0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete — благородный кирпично-красный, не «алярм» */
|
||||||
|
.ct-delete {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg,
|
||||||
|
rgba(255,255,255,0.10) 0%,
|
||||||
|
rgba(255,255,255,0.00) 36%,
|
||||||
|
rgba(0,0,0,0.00) 64%,
|
||||||
|
rgba(0,0,0,0.18) 100%),
|
||||||
|
linear-gradient(180deg, #C95A4A 0%, #A6382A 55%, #832418 100%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.22),
|
||||||
|
inset 0 -1px 0 rgba(0,0,0,0.20),
|
||||||
|
0 3px 8px rgba(131, 36, 24, 0.30),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.ct-delete:hover { filter: brightness(1.05); }
|
||||||
|
.ct-delete:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255,255,255,0.10),
|
||||||
|
inset 0 2px 4px rgba(0,0,0,0.22),
|
||||||
|
0 1px 2px rgba(0,0,0,0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-btn:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: wait;
|
||||||
|
filter: saturate(0.7);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ct-hint {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
font-size: 11.5px;
|
||||||
|
color: var(--muted, #998877);
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
margin-top: 2px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.ct-result { grid-column: 1 / -1; min-height: 0; font-size: 12.5px; }
|
||||||
|
.ct-result:empty { display: none; }
|
||||||
|
.ct-ok { color: #4A8016; font-weight: 500; }
|
||||||
|
.ct-err { color: #A6382A; font-weight: 500; }
|
||||||
|
|
||||||
|
/* ===== Сворачиваемые блоки (хронология и т.п.) ===== */
|
||||||
|
.client-collapse {
|
||||||
|
/* убираем дефолтный маркер */
|
||||||
|
}
|
||||||
|
.client-collapse > summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 12px 6px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.client-collapse > summary::-webkit-details-marker { display: none; }
|
||||||
|
.client-collapse > summary.collapse-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.client-collapse .collapse-chev {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--muted, #998877);
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.client-collapse[open] .collapse-chev { transform: rotate(-90deg); }
|
||||||
|
|
||||||
/* ===== Сборки (Phase 4) ===== */
|
/* ===== Сборки (Phase 4) ===== */
|
||||||
.assembly-list {
|
.assembly-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -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=20260514d">
|
<link rel="stylesheet" href="assets/styles.css?v=20260514e">
|
||||||
<link rel="stylesheet" href="assets/podbor.css?v=20260514d">
|
<link rel="stylesheet" href="assets/podbor.css?v=20260514e">
|
||||||
</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=20260514d" alt="@wasrusgen1">
|
<img class="brand-logo" src="assets/wasrusgen-logo.svg?v=20260514e" 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=20260514d"></script>
|
<script src="assets/icons.js?v=20260514e"></script>
|
||||||
<script src="assets/podbor.config.js?v=20260514d"></script>
|
<script src="assets/podbor.config.js?v=20260514e"></script>
|
||||||
<script src="assets/podbor.picts.js?v=20260514d"></script>
|
<script src="assets/podbor.picts.js?v=20260514e"></script>
|
||||||
<script src="assets/podbor.js?v=20260514d"></script>
|
<script src="assets/podbor.js?v=20260514e"></script>
|
||||||
<script src="assets/clients.js?v=20260514d"></script>
|
<script src="assets/clients.js?v=20260514e"></script>
|
||||||
<script src="assets/zamer-picts.js?v=20260514d"></script>
|
<script src="assets/zamer-picts.js?v=20260514e"></script>
|
||||||
<script src="assets/measurements.js?v=20260514d"></script>
|
<script src="assets/measurements.js?v=20260514e"></script>
|
||||||
<script src="assets/request.js?v=20260514d"></script>
|
<script src="assets/request.js?v=20260514e"></script>
|
||||||
<script src="assets/assembly.js?v=20260514d"></script>
|
<script src="assets/assembly.js?v=20260514e"></script>
|
||||||
<script src="assets/app.js?v=20260514d"></script>
|
<script src="assets/app.js?v=20260514e"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user