nordabiz/templates/admin/gbp_match_places.html
Maciej Pienczyn d3fa2d7516
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
feat: admin tool for manual Google Place ID matching
New page /admin/gbp-audit/match-places shows companies without
google_place_id. Admin searches Google Maps, reviews results, and
confirms the correct match. Adds search_places_raw() to return all
results without name filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:57:54 +01:00

261 lines
9.9 KiB
HTML

{% extends "base.html" %}
{% block title %}Dopasuj firmy do Google Maps - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.match-page { max-width: 1100px; margin: 0 auto; }
.back-link {
display: inline-flex; align-items: center; gap: var(--spacing-xs);
color: var(--text-secondary); text-decoration: none; font-size: var(--font-size-sm);
margin-bottom: var(--spacing-md);
}
.back-link:hover { color: var(--primary); }
.match-header {
margin-bottom: var(--spacing-xl);
}
.match-header h1 { font-size: var(--font-size-2xl); color: var(--text-primary); margin-bottom: var(--spacing-xs); }
.match-header p { color: var(--text-secondary); }
.company-card {
background: white; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
margin-bottom: var(--spacing-md); overflow: hidden;
}
.company-card.matched { opacity: 0.5; }
.company-header {
display: flex; justify-content: space-between; align-items: center;
padding: var(--spacing-md) var(--spacing-lg);
cursor: default;
}
.company-info { flex: 1; }
.company-name { font-weight: 600; font-size: var(--font-size-lg); color: var(--text-primary); }
.company-city { font-size: var(--font-size-sm); color: var(--text-secondary); margin-top: 2px; }
.btn-search {
background: var(--primary); color: white; border: none; padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius-md); cursor: pointer; font-size: var(--font-size-sm); font-weight: 500;
white-space: nowrap;
}
.btn-search:hover { opacity: 0.9; }
.btn-search:disabled { background: var(--secondary); cursor: not-allowed; }
.btn-skip {
background: transparent; color: var(--text-secondary); border: 1px solid var(--border-color);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md); cursor: pointer; font-size: var(--font-size-sm);
margin-left: var(--spacing-sm);
}
.btn-skip:hover { background: var(--bg-secondary); }
.search-results {
padding: 0 var(--spacing-lg) var(--spacing-lg);
display: none;
}
.search-results.visible { display: block; }
.search-loading {
text-align: center; padding: var(--spacing-lg); color: var(--text-secondary);
}
.place-result {
display: flex; justify-content: space-between; align-items: center;
padding: var(--spacing-md); border: 1px solid var(--border-color);
border-radius: var(--radius-md); margin-bottom: var(--spacing-sm);
gap: var(--spacing-md);
}
.place-result:hover { border-color: var(--primary); background: var(--bg-primary); }
.place-info { flex: 1; }
.place-name { font-weight: 600; color: var(--text-primary); }
.place-address { font-size: var(--font-size-sm); color: var(--text-secondary); margin-top: 2px; }
.place-meta { font-size: var(--font-size-xs); color: var(--text-tertiary); margin-top: 4px; }
.place-meta .rating { color: #f59e0b; font-weight: 600; }
.btn-confirm {
background: #16a34a; color: white; border: none; padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius-md); cursor: pointer; font-size: var(--font-size-sm); font-weight: 500;
white-space: nowrap;
}
.btn-confirm:hover { background: #15803d; }
.btn-confirm:disabled { background: var(--secondary); cursor: not-allowed; }
.no-results {
text-align: center; padding: var(--spacing-lg); color: var(--text-secondary);
font-style: italic;
}
.match-badge {
display: inline-block; background: #16a34a; color: white; font-size: var(--font-size-xs);
padding: 2px 8px; border-radius: var(--radius-full); font-weight: 600;
}
.counter {
background: var(--bg-secondary); padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--radius-lg); margin-bottom: var(--spacing-xl);
display: flex; gap: var(--spacing-xl); flex-wrap: wrap;
}
.counter-item { font-size: var(--font-size-sm); }
.counter-value { font-weight: 700; font-size: var(--font-size-lg); }
</style>
{% endblock %}
{% block content %}
<div class="match-page">
<a href="{{ url_for('admin.admin_gbp_audit') }}" class="back-link">&larr; GBP Audit Dashboard</a>
<div class="match-header">
<h1>Dopasuj firmy do Google Maps</h1>
<p>{{ companies|length }} firm bez identyfikatora Google Place ID. Wyszukaj i potwierdz dopasowanie.</p>
</div>
<div class="counter">
<div class="counter-item">Pozostalo: <span class="counter-value" id="remainingCount">{{ companies|length }}</span></div>
<div class="counter-item">Dopasowano: <span class="counter-value" id="matchedCount" style="color: #16a34a;">0</span></div>
</div>
{% for c in companies %}
<div class="company-card" id="card-{{ c.id }}" data-company-id="{{ c.id }}">
<div class="company-header">
<div class="company-info">
<div class="company-name">{{ c.name }}</div>
<div class="company-city">{{ c.address_city or 'brak miasta' }}{% if c.website %} &middot; {{ c.website }}{% endif %}</div>
</div>
<div>
<button class="btn-search" onclick="searchPlace({{ c.id }}, '{{ c.name|e }}')">Szukaj w Google</button>
<button class="btn-skip" onclick="skipCompany({{ c.id }})">Brak w Google</button>
</div>
</div>
<div class="search-results" id="results-{{ c.id }}"></div>
</div>
{% endfor %}
{% if not companies %}
<div style="text-align: center; padding: 60px 20px; color: var(--text-secondary);">
Wszystkie aktywne firmy maja przypisany Google Place ID.
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
var csrfToken = '{{ csrf_token() }}';
var matchedCount = 0;
var totalCompanies = {{ companies|length }};
function searchPlace(companyId, companyName) {
var resultsDiv = document.getElementById('results-' + companyId);
var btn = event.target;
btn.disabled = true;
btn.textContent = 'Szukam...';
resultsDiv.innerHTML = '<div class="search-loading">Szukam "' + companyName + '" w Google Maps...</div>';
resultsDiv.classList.add('visible');
var formData = new FormData();
formData.append('company_id', companyId);
formData.append('csrf_token', csrfToken);
fetch('{{ url_for("admin.admin_gbp_search_place") }}', {
method: 'POST',
body: formData
})
.then(function(r) { return r.json(); })
.then(function(data) {
btn.disabled = false;
btn.textContent = 'Szukaj w Google';
if (data.error) {
resultsDiv.innerHTML = '<div class="no-results">Blad: ' + data.error + '</div>';
return;
}
if (!data.results || data.results.length === 0) {
resultsDiv.innerHTML = '<div class="no-results">Nie znaleziono wynikow dla "' + data.query + '"</div>';
return;
}
var html = '';
data.results.forEach(function(p) {
var meta = [];
if (p.rating) meta.push('<span class="rating">' + p.rating + ' &#9733;</span>');
if (p.reviews_count) meta.push(p.reviews_count + ' opinii');
if (p.types) meta.push(p.types);
html += '<div class="place-result">';
html += ' <div class="place-info">';
html += ' <div class="place-name">' + p.name + '</div>';
html += ' <div class="place-address">' + p.address + '</div>';
if (meta.length) html += ' <div class="place-meta">' + meta.join(' &middot; ') + '</div>';
html += ' </div>';
html += ' <button class="btn-confirm" onclick="confirmPlace(' + companyId + ', \'' + p.place_id + '\', \'' + p.name.replace(/'/g, "\\'") + '\')">Potwierdz</button>';
html += '</div>';
});
resultsDiv.innerHTML = html;
})
.catch(function(err) {
btn.disabled = false;
btn.textContent = 'Szukaj w Google';
resultsDiv.innerHTML = '<div class="no-results">Blad polaczenia</div>';
});
}
function confirmPlace(companyId, placeId, googleName) {
var btn = event.target;
btn.disabled = true;
btn.textContent = 'Zapisuje...';
var formData = new FormData();
formData.append('company_id', companyId);
formData.append('place_id', placeId);
formData.append('google_name', googleName);
formData.append('csrf_token', csrfToken);
fetch('{{ url_for("admin.admin_gbp_confirm_place") }}', {
method: 'POST',
body: formData
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.error) {
btn.disabled = false;
btn.textContent = 'Potwierdz';
alert('Blad: ' + data.error);
return;
}
var card = document.getElementById('card-' + companyId);
card.classList.add('matched');
var header = card.querySelector('.company-header');
var nameDiv = header.querySelector('.company-name');
nameDiv.innerHTML += ' <span class="match-badge">' + googleName + '</span>';
var resultsDiv = document.getElementById('results-' + companyId);
resultsDiv.classList.remove('visible');
var buttons = header.querySelectorAll('button');
buttons.forEach(function(b) { b.style.display = 'none'; });
matchedCount++;
document.getElementById('matchedCount').textContent = matchedCount;
document.getElementById('remainingCount').textContent = totalCompanies - matchedCount;
})
.catch(function(err) {
btn.disabled = false;
btn.textContent = 'Potwierdz';
alert('Blad polaczenia');
});
}
function skipCompany(companyId) {
var card = document.getElementById('card-' + companyId);
card.style.display = 'none';
totalCompanies--;
document.getElementById('remainingCount').textContent = totalCompanies - matchedCount;
}
{% endblock %}