Add profile completeness indicator and two enrichment modes
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

1. Completeness bar above buttons: "Profil 65%" with missing field list
2. Two buttons:
   - "Uzupełnij brakujące" (fill mode) — only fills empty fields,
     skips existing data, all results pre-checked
   - "Sprawdź aktualizacje" (full mode) — shows all changes including
     updates to existing fields, changes unchecked by default
3. "Uzupełnij" button hidden when profile is 100% complete
4. Backend provides profile_filled, profile_total, profile_missing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-25 17:22:59 +01:00
parent c2fc5abe6d
commit 5ca0cdd139
2 changed files with 74 additions and 12 deletions

View File

@ -477,6 +477,19 @@ def company_detail(company_id):
except Exception:
pass
# Profile completeness for AI enrichment button
profile_fields = {
'Opis firmy': bool(ai_insights and ai_insights.business_summary) or bool(company.description_full),
'Usługi': bool(ai_insights and ai_insights.services_list) or bool(company.services_offered),
'Grupa docelowa': bool(ai_insights and ai_insights.target_market),
'Wyróżniki firmy': bool(ai_insights and ai_insights.unique_selling_points),
'Wartości firmy': bool(ai_insights and ai_insights.company_values),
'Tagi branżowe': bool(ai_insights and ai_insights.industry_tags),
}
profile_filled = sum(1 for v in profile_fields.values() if v)
profile_total = len(profile_fields)
profile_missing = [k for k, v in profile_fields.items() if not v]
# For child brands — inherit NIP from parent for display/enrichment
effective_nip = company.nip
if not effective_nip and company.parent_company_id:
@ -505,6 +518,9 @@ def company_detail(company_id):
pkd_codes=pkd_codes,
can_enrich=can_enrich,
can_edit_profile=can_edit_profile,
profile_filled=profile_filled,
profile_total=profile_total,
profile_missing=profile_missing,
company_managers=company_managers,
is_admin=current_user.is_authenticated and current_user.is_admin,
zopk_links=zopk_links,

View File

@ -940,7 +940,18 @@
<!-- AI Enrichment Button + Edit Profile (hidden for guests) -->
{% if not is_guest %}
<div style="margin: var(--spacing-md) 0; display: flex; gap: var(--spacing-sm); flex-wrap: wrap; align-items: center;">
{% if can_enrich and profile_missing %}
<div style="margin: var(--spacing-md) 0 var(--spacing-xs) 0; font-size: 13px; color: var(--text-secondary);">
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-weight: 600;">Profil {{ ((profile_filled / profile_total) * 100)|int }}%</span>
<div style="flex: 1; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;">
<div style="width: {{ ((profile_filled / profile_total) * 100)|int }}%; height: 100%; background: {% if profile_filled == profile_total %}#10b981{% elif profile_filled >= profile_total / 2 %}#f59e0b{% else %}#ef4444{% endif %}; border-radius: 3px;"></div>
</div>
</div>
<span style="font-size: 12px;">Brakuje: {{ profile_missing|join(', ') }}</span>
</div>
{% endif %}
<div style="margin: var(--spacing-xs) 0 var(--spacing-md) 0; display: flex; gap: var(--spacing-sm); flex-wrap: wrap; align-items: center;">
{% if can_edit_profile %}
<a href="{{ url_for('company_edit', company_id=company.id) }}" class="ai-enrich-btn" style="text-decoration: none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -958,12 +969,14 @@
Edytuj profil
</button>
{% endif %}
{% if can_enrich and profile_missing %}
<button
id="aiEnrichBtn"
class="ai-enrich-btn"
data-company-id="{{ company.id }}"
title="Przeszukamy internet i stronę WWW firmy, żeby znaleźć brakujące informacje. Wyniki pokażemy do akceptacji — nic nie zostanie zmienione bez Twojej zgody."
{% if not can_enrich %}disabled title="Tylko administrator lub właściciel firmy może wzbogacić dane"{% endif %}
data-mode="fill"
title="Wypełni tylko puste pola — istniejące dane nie zostaną zmienione."
{% if not can_enrich %}disabled{% endif %}
>
<span class="spinner"></span>
<svg class="btn-text" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@ -971,8 +984,28 @@
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
<span class="btn-text">Zbierz informacje o firmie</span>
<span class="btn-text">Uzupełnij brakujące</span>
</button>
{% endif %}
{% if can_enrich %}
<button
id="aiEnrichFullBtn"
class="ai-enrich-btn"
data-company-id="{{ company.id }}"
data-mode="full"
style="background: linear-gradient(135deg, #475569 0%, #334155 100%);"
title="Porówna istniejące dane z nowymi źródłami — pokażemy różnice do akceptacji."
{% if not can_enrich %}disabled{% endif %}
>
<span class="spinner"></span>
<svg class="btn-text" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
<path d="M2 17l10 5 10-5"/>
<path d="M2 12l10 5 10-5"/>
</svg>
<span class="btn-text">Sprawdź aktualizacje</span>
</button>
{% endif %}
{% if is_admin %}
<button
id="registryEnrichBtn"
@ -4736,7 +4769,9 @@ function finishAiEnrichment(success) {
function showProposalApprovalButtons(companyId, proposalId, proposedData, currentData) {
// Update modal title
document.getElementById('aiModalTitle').textContent = 'Znalezione informacje — wybierz co dodać';
document.getElementById('aiModalTitle').textContent = aiEnrichMode === 'fill'
? 'Znalezione brakujące informacje'
: 'Znalezione informacje — wybierz co dodać';
document.getElementById('aiCancelBtn').style.display = 'none';
document.getElementById('aiSpinner').style.display = 'none';
@ -4781,6 +4816,9 @@ function showProposalApprovalButtons(companyId, proposalId, proposedData, curren
if (isSame) continue; // Skip unchanged fields
// In "fill" mode, skip fields that already have data
if (aiEnrichMode === 'fill' && !isNew) continue;
fieldCount++;
var statusBadge = isNew
@ -4917,11 +4955,21 @@ function showProposalApprovalButtons(companyId, proposalId, proposedData, curren
});
}
var aiEnrichMode = 'full'; // 'fill' or 'full'
document.addEventListener('DOMContentLoaded', function() {
const aiEnrichBtn = document.getElementById('aiEnrichBtn');
if (aiEnrichBtn && !aiEnrichBtn.disabled) {
aiEnrichBtn.addEventListener('click', async function() {
const companyId = this.dataset.companyId;
// Bind both buttons to the same flow
['aiEnrichBtn', 'aiEnrichFullBtn'].forEach(function(btnId) {
var btn = document.getElementById(btnId);
if (btn && !btn.disabled) {
btn.addEventListener('click', function() { startAiEnrich(this); });
}
});
});
async function startAiEnrich(triggerBtn) {
const companyId = triggerBtn.dataset.companyId;
aiEnrichMode = triggerBtn.dataset.mode || 'full';
const companyName = '{{ company.name }}';
// Open modal
@ -4995,9 +5043,7 @@ document.addEventListener('DOMContentLoaded', function() {
addAiLogEntry('Blad polaczenia: ' + error.message, 'error');
finishAiEnrichment(false);
}
});
}
});
}
// Registry enrichment button handler
const registryEnrichBtn = document.getElementById('registryEnrichBtn');