Show name status in operator customer profile
This commit is contained in:
@@ -317,8 +317,11 @@ def test_operator_ui_can_fix_customer_name_from_call_views():
|
||||
assert "function saveLiveCallCustomerName" in app_js
|
||||
assert "function saveCustomerProfileName" in app_js
|
||||
assert "function openCustomerProfileNameEditor" in app_js
|
||||
assert "function customerProfileVoiceNameContext" in app_js
|
||||
assert "data-customer-name-form" in app_js
|
||||
assert 'data-customer-action="edit-name"' in app_js
|
||||
assert "customer-profile-name-meta" in app_js
|
||||
assert "customer-profile-name-flash" in app_js
|
||||
assert "function applyCustomerNamePatchLocally" in app_js
|
||||
assert "function updateLiveCallNameEditorsUi" in app_js
|
||||
assert "function handleLiveCallTableClick" in app_js
|
||||
@@ -329,6 +332,9 @@ def test_operator_ui_can_fix_customer_name_from_call_views():
|
||||
assert ".live-call-card-actions {" in styles_css
|
||||
assert ".customer-profile-name-editor {" in styles_css
|
||||
assert ".customer-profile-name-row {" in styles_css
|
||||
assert ".customer-profile-name-meta {" in styles_css
|
||||
assert ".customer-profile-name-note {" in styles_css
|
||||
assert ".customer-profile-name-flash {" in styles_css
|
||||
|
||||
|
||||
def test_operator_ui_surfaces_voice_name_state_across_call_views():
|
||||
@@ -349,8 +355,8 @@ def test_operator_ui_surfaces_voice_name_state_across_call_views():
|
||||
assert ".micro-badge.name-confirmed {" in styles_css
|
||||
assert ".micro-badge.name-followup {" in styles_css
|
||||
assert ".micro-badge.name-missing {" in styles_css
|
||||
assert "/operator/assets/styles.css?v=track22-customer-profile-name1" in index_html
|
||||
assert "/operator/assets/app.js?v=track41-customer-profile-name1" in index_html
|
||||
assert "/operator/assets/styles.css?v=track23-customer-profile-name-meta1" in index_html
|
||||
assert "/operator/assets/app.js?v=track42-customer-profile-name-meta1" in index_html
|
||||
|
||||
|
||||
def test_operator_ui_hides_whatsapp_behind_feature_flag():
|
||||
|
||||
+33
-2
@@ -47,6 +47,7 @@ const state = {
|
||||
draft: '',
|
||||
saving: false,
|
||||
error: '',
|
||||
flash: '',
|
||||
},
|
||||
},
|
||||
telegram: {
|
||||
@@ -1074,15 +1075,40 @@ function emptyCustomerSpotlightMarkup(title, description) {
|
||||
`;
|
||||
}
|
||||
|
||||
function customerProfileNameEditorMarkup(customer) {
|
||||
function customerProfileVoiceNameContext(liveCalls = []) {
|
||||
const latestCall = latestItemByTime(liveCalls, ['last_transition_at', 'ended_at', 'connected_at', 'started_at', 'updated_at']);
|
||||
if (!latestCall) {
|
||||
return {
|
||||
statusMeta: null,
|
||||
sourceLabel: '',
|
||||
};
|
||||
}
|
||||
const voiceContext = voiceSummaryForItem(latestCall) || latestCall || {};
|
||||
return {
|
||||
statusMeta: voiceCustomerNameStatusMeta(voiceContext?.customer_name_status),
|
||||
sourceLabel: formatVoiceCustomerNameSource(voiceContext?.customer_name_source),
|
||||
};
|
||||
}
|
||||
|
||||
function customerProfileNameEditorMarkup(customer, context = {}) {
|
||||
const editor = state.customers.nameEditor;
|
||||
const isOpen = editor.open && editor.customerId === customer.customer_id;
|
||||
const statusMeta = context.statusMeta || null;
|
||||
const sourceLabel = String(context.sourceLabel || '').trim();
|
||||
const flash = editor.flash && editor.customerId === customer.customer_id ? editor.flash : '';
|
||||
const statusText = editor.error || (editor.saving ? 'Сохраняем имя клиента...' : 'Имя сохранится в профиле клиента и voice-контуре.');
|
||||
return `
|
||||
<div class="customer-profile-name-row">
|
||||
<div class="customer-profile-name-stack">
|
||||
<h3>${escapeHtml(customer.display_name || 'Без имени')}</h3>
|
||||
<p>${escapeHtml(customerLeadCompany(customer))}</p>
|
||||
${(statusMeta || sourceLabel || flash) ? `
|
||||
<div class="customer-profile-name-meta">
|
||||
${statusMeta ? `<span class="micro-badge ${escapeHtml(statusMeta.className)}">${escapeHtml(statusMeta.shortLabel)}</span>` : ''}
|
||||
${sourceLabel ? `<span class="customer-profile-name-note">Источник: ${escapeHtml(sourceLabel)}</span>` : ''}
|
||||
${flash ? `<span class="customer-profile-name-flash">${escapeHtml(flash)}</span>` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<button class="btn ghost customer-profile-name-trigger" type="button" data-customer-action="edit-name">
|
||||
${isOpen ? 'Редактирование имени' : 'Изменить имя'}
|
||||
@@ -1128,6 +1154,7 @@ function customerSpotlightMarkup(customer, options = {}) {
|
||||
const liveCalls = Array.isArray(historyPayload?.live_calls)
|
||||
? historyPayload.live_calls
|
||||
: customerLiveCalls(customer);
|
||||
const profileNameContext = customerProfileVoiceNameContext(liveCalls);
|
||||
const historyEvents = Array.isArray(historyPayload?.history) && historyPayload.history.length
|
||||
? historyPayload.history
|
||||
: buildCustomerHistoryEvents(customer);
|
||||
@@ -1203,7 +1230,7 @@ function customerSpotlightMarkup(customer, options = {}) {
|
||||
<div class="customer-profile-avatar">${escapeHtml(customerInitials(customer.display_name))}</div>
|
||||
<div class="customer-profile-copy">
|
||||
<div class="customer-profile-kicker">Клиент ${escapeHtml(customer.customer_id)}</div>
|
||||
${customerProfileNameEditorMarkup(customer)}
|
||||
${customerProfileNameEditorMarkup(customer, profileNameContext)}
|
||||
</div>
|
||||
<div class="customer-profile-badges">
|
||||
<span class="${source.className}">${escapeHtml(source.label)}</span>
|
||||
@@ -1420,6 +1447,7 @@ function selectCustomer(customerId) {
|
||||
state.customers.nameEditor.draft = '';
|
||||
state.customers.nameEditor.saving = false;
|
||||
state.customers.nameEditor.error = '';
|
||||
state.customers.nameEditor.flash = '';
|
||||
}
|
||||
state.customers.selectedCustomerId = customerId || '';
|
||||
if (customerId) {
|
||||
@@ -4188,6 +4216,7 @@ function openCustomerProfileNameEditor(customerId = state.customers.selectedCust
|
||||
state.customers.nameEditor.draft = customer.display_name || '';
|
||||
state.customers.nameEditor.saving = false;
|
||||
state.customers.nameEditor.error = '';
|
||||
state.customers.nameEditor.flash = '';
|
||||
renderCustomerSpotlight();
|
||||
}
|
||||
|
||||
@@ -4354,6 +4383,7 @@ async function saveCustomerProfileName() {
|
||||
renderCustomerSpotlight();
|
||||
try {
|
||||
const savedName = await persistCustomerDisplayName({ customerId, displayName });
|
||||
state.customers.nameEditor.flash = 'Сохранено';
|
||||
closeCustomerProfileNameEditor();
|
||||
log('Имя клиента обновлено из профиля', { customer_id: customerId, name: savedName });
|
||||
} catch (err) {
|
||||
@@ -6394,6 +6424,7 @@ function handleCustomerProfileNameInput(event) {
|
||||
return;
|
||||
}
|
||||
state.customers.nameEditor.draft = input.value || '';
|
||||
state.customers.nameEditor.flash = '';
|
||||
if (!state.customers.nameEditor.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/operator/assets/styles.css?v=track22-customer-profile-name1" />
|
||||
<link rel="stylesheet" href="/operator/assets/styles.css?v=track23-customer-profile-name-meta1" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
@@ -640,6 +640,6 @@
|
||||
|
||||
<script src="/operator/assets/access-guards.js?v=track16-voice-transcript1"></script>
|
||||
<script src="/operator/assets/sip-0.21.2.min.js?v=track16-voice-transcript1"></script>
|
||||
<script src="/operator/assets/app.js?v=track41-customer-profile-name1"></script>
|
||||
<script src="/operator/assets/app.js?v=track42-customer-profile-name-meta1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1117,6 +1117,36 @@ textarea::placeholder {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.customer-profile-name-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.customer-profile-name-note,
|
||||
.customer-profile-name-flash {
|
||||
min-height: 24px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.customer-profile-name-note {
|
||||
background: #f6f8fc;
|
||||
border: 1px solid #dce4ef;
|
||||
color: #52627d;
|
||||
}
|
||||
|
||||
.customer-profile-name-flash {
|
||||
background: rgba(16, 185, 129, 0.14);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
color: #117c5f;
|
||||
}
|
||||
|
||||
.customer-profile-kicker {
|
||||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
|
||||
Reference in New Issue
Block a user