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
Adds bidirectional visibility control: published posts can be switched between public (live) and draft (debug/admin-only) mode via Facebook Graph API. Includes is_live column, status indicator, and toggle buttons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
636 lines
26 KiB
HTML
636 lines
26 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{% if post %}Edycja Posta #{{ post.id }}{% else %}Nowy Post{% endif %} - Social Publisher{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.admin-header {
|
|
margin-bottom: var(--spacing-xl);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.admin-header h1 {
|
|
font-size: var(--font-size-3xl);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.form-section {
|
|
background: var(--surface);
|
|
padding: var(--spacing-xl);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
max-width: 900px;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: var(--spacing-lg);
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: var(--spacing-xs);
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.form-group input[type="text"],
|
|
.form-group input[type="datetime-local"],
|
|
.form-group select,
|
|
.form-group textarea {
|
|
width: 100%;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--font-size-base);
|
|
font-family: inherit;
|
|
}
|
|
|
|
.form-group textarea {
|
|
min-height: 120px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-group textarea.content-editor {
|
|
min-height: 250px;
|
|
}
|
|
|
|
.form-hint {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: var(--spacing-lg);
|
|
}
|
|
|
|
.section-title {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-top: var(--spacing-xl);
|
|
margin-bottom: var(--spacing-md);
|
|
padding-bottom: var(--spacing-xs);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
margin-top: var(--spacing-xl);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-generate {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.btn-generate:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.btn-generate:disabled {
|
|
opacity: 0.6;
|
|
cursor: wait;
|
|
}
|
|
|
|
.btn-generate-sm {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
font-size: var(--font-size-xs);
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.btn-generate-sm:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.btn-generate-sm:disabled {
|
|
opacity: 0.6;
|
|
cursor: wait;
|
|
}
|
|
|
|
.ai-engine-info {
|
|
font-size: 11px;
|
|
color: var(--text-tertiary, #9ca3af);
|
|
margin-top: var(--spacing-xs);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.hashtag-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.status-info {
|
|
background: var(--background);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
margin-bottom: var(--spacing-lg);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.status-info strong {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.engagement-box {
|
|
background: var(--background);
|
|
padding: var(--spacing-lg);
|
|
border-radius: var(--radius);
|
|
margin-top: var(--spacing-lg);
|
|
}
|
|
|
|
.engagement-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
gap: var(--spacing-md);
|
|
margin-bottom: var(--spacing-md);
|
|
}
|
|
|
|
.engagement-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.engagement-item .value {
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
}
|
|
|
|
.engagement-item .label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.d-none {
|
|
display: none !important;
|
|
}
|
|
|
|
.char-counter {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--text-secondary);
|
|
text-align: right;
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
.char-counter.warning { color: var(--warning); }
|
|
.char-counter.over { color: var(--error); }
|
|
|
|
.fb-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
color: #1877f2;
|
|
font-weight: 500;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.fb-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.visibility-status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.visibility-status.live {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.visibility-status.debug {
|
|
background: #fef9c3;
|
|
color: #854d0e;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="admin-header">
|
|
<h1>{% if post %}Edycja Posta #{{ post.id }}{% else %}Nowy Post{% endif %}</h1>
|
|
<a href="{{ url_for('admin.social_publisher_list') }}" class="btn btn-secondary">Powrot do listy</a>
|
|
</div>
|
|
|
|
{% if post %}
|
|
<div class="status-info">
|
|
<strong>Status:</strong>
|
|
{% if post.status == 'draft' %}Szkic
|
|
{% elif post.status == 'approved' %}Zatwierdzony
|
|
{% elif post.status == 'scheduled' %}Zaplanowany
|
|
{% elif post.status == 'published' %}Opublikowany
|
|
{% elif post.status == 'failed' %}Błąd
|
|
{% else %}{{ post.status }}{% endif %}
|
|
{% if post.creator %}
|
|
| <strong>Autor:</strong> {{ post.creator.name }}
|
|
{% endif %}
|
|
{% if post.ai_model %}
|
|
| <strong>Model AI:</strong> {{ post.ai_model }}
|
|
{% endif %}
|
|
{% if post.published_at %}
|
|
| <strong>Opublikowano:</strong> {{ post.published_at.strftime('%Y-%m-%d %H:%M') }}
|
|
{% endif %}
|
|
{% if post.status == 'published' and post.meta_post_id %}
|
|
|
|
|
{% if post.is_live %}
|
|
<span class="visibility-status live">Publiczny</span>
|
|
{% else %}
|
|
<span class="visibility-status debug">Debug (tylko admin)</span>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="form-section">
|
|
<form method="POST" id="postForm">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
|
|
<!-- Publikuj jako (firma z konfiguracją FB) -->
|
|
{% if configured_companies %}
|
|
<div class="form-group">
|
|
<label for="publishing_company_id">Publikuj jako (strona FB)</label>
|
|
<select id="publishing_company_id" name="publishing_company_id">
|
|
<option value="">-- Bez publikacji na FB --</option>
|
|
{% for cc in configured_companies %}
|
|
<option value="{{ cc.company_id }}" {% if post and post.publishing_company_id == cc.company_id %}selected{% endif %}>
|
|
{{ cc.company_name }} ({{ cc.page_name }}){% if cc.debug_mode %} [DEBUG]{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="form-hint">Strona Facebook, na ktorej zostanie opublikowany post</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Typ posta -->
|
|
<div class="form-group">
|
|
<label for="post_type">Typ posta</label>
|
|
<select id="post_type" name="post_type" required>
|
|
<option value="">-- Wybierz typ --</option>
|
|
{% for key, label in post_types.items() %}
|
|
<option value="{{ key }}" {% if post and post.post_type == key %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Firma (widoczna dla member_spotlight) -->
|
|
<div class="form-group" id="company-field">
|
|
<label for="company_id">Firma</label>
|
|
<select id="company_id" name="company_id">
|
|
<option value="">-- Wybierz firmę --</option>
|
|
{% for company in companies %}
|
|
<option value="{{ company.id }}" {% if post and post.company_id == company.id %}selected{% endif %}>{{ company.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="form-hint">Firma, którą chcesz zaprezentować w poście</p>
|
|
</div>
|
|
|
|
<!-- Wydarzenie (widoczne dla event_*) -->
|
|
<div class="form-group" id="event-field">
|
|
<label for="event_id">Wydarzenie</label>
|
|
<select id="event_id" name="event_id">
|
|
<option value="">-- Wybierz wydarzenie --</option>
|
|
{% for event in events %}
|
|
<option value="{{ event.id }}" {% if post and post.event_id == event.id %}selected{% endif %}>{{ event.title }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="form-hint">Wydarzenie powiązane z postem</p>
|
|
</div>
|
|
|
|
<!-- Kontekst dodatkowy (dla regional_news/chamber_news) -->
|
|
<div class="form-group" id="custom-context-field">
|
|
<h3 class="section-title">Kontekst dla AI</h3>
|
|
<div class="form-group">
|
|
<label for="custom_topic">Temat</label>
|
|
<input type="text" id="custom_topic" name="custom_topic" placeholder="np. Nowa inwestycja w porcie Gdynia">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="custom_details">Szczegoly</label>
|
|
<textarea id="custom_details" name="custom_details" rows="3" placeholder="Dodatkowe informacje, które AI powinno uwzględnić..."></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="custom_facts">Fakty/dane</label>
|
|
<input type="text" id="custom_facts" name="custom_facts" placeholder="np. Wartosc: 50 mln PLN, termin: Q3 2026">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="custom_source">Zrodlo</label>
|
|
<input type="text" id="custom_source" name="custom_source" placeholder="np. Portal Morski, komunikat prasowy">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tonacja + Model + Przycisk generowania AI -->
|
|
<div class="form-group" style="display: flex; align-items: center; justify-content: flex-end; gap: var(--spacing-sm); flex-wrap: wrap;">
|
|
<label for="tone" style="margin: 0; white-space: nowrap;">Tonacja:</label>
|
|
<select id="tone" name="tone" style="width: auto; min-width: 160px; padding: var(--spacing-xs) var(--spacing-sm); border: 1px solid var(--border); border-radius: var(--radius-md); font-size: var(--font-size-sm);">
|
|
{% for key, tone in post_tones.items() %}
|
|
<option value="{{ key }}" {% if key == default_tone %}selected{% endif %}>{{ tone.label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<label for="ai_model" style="margin: 0; white-space: nowrap;">Model:</label>
|
|
<select id="ai_model" name="ai_model" style="width: auto; min-width: 170px; padding: var(--spacing-xs) var(--spacing-sm); border: 1px solid var(--border); border-radius: var(--radius-md); font-size: var(--font-size-sm);">
|
|
{% for key, model in ai_models.items() %}
|
|
<option value="{{ key }}" {% if key == default_ai_model %}selected{% endif %} title="{{ model.description }}">{{ model.label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<button type="button" id="btn-generate-ai" class="btn btn-generate">
|
|
Generuj AI
|
|
</button>
|
|
</div>
|
|
<div id="ai-error-msg" style="display:none; padding: var(--spacing-sm) var(--spacing-md); margin-bottom: var(--spacing-md); border-radius: var(--radius); background: var(--error-bg, #fef2f2); color: var(--error, #dc2626); border: 1px solid var(--error, #dc2626); font-size: var(--font-size-sm);"></div>
|
|
|
|
<!-- Tresc -->
|
|
<div class="form-group">
|
|
<label for="content">Tresc posta</label>
|
|
<textarea id="content" name="content" class="content-editor" required
|
|
placeholder="Wpisz tresc posta lub wygeneruj za pomoca AI...">{{ post.content if post else '' }}</textarea>
|
|
<div class="char-counter" id="content-counter">0 znaków</div>
|
|
</div>
|
|
|
|
<!-- Hashtagi -->
|
|
<div class="form-group">
|
|
<div class="hashtag-header">
|
|
<label for="hashtags">Hashtagi</label>
|
|
<button type="button" id="btn-generate-hashtags" class="btn-generate-sm">
|
|
Generuj hashtagi AI
|
|
</button>
|
|
</div>
|
|
<input type="text" id="hashtags" name="hashtags"
|
|
value="{{ post.hashtags if post else '' }}"
|
|
placeholder="#NordaBiznes #Wejherowo #Pomorze">
|
|
<p class="form-hint">Hashtagi oddzielone spacjami</p>
|
|
<div id="hashtag-error-msg" style="display:none; padding: var(--spacing-xs) var(--spacing-sm); margin-top: var(--spacing-xs); border-radius: var(--radius); background: var(--error-bg, #fef2f2); color: var(--error, #dc2626); border: 1px solid var(--error, #dc2626); font-size: var(--font-size-xs);"></div>
|
|
</div>
|
|
|
|
<!-- Info o silniku AI -->
|
|
<div class="ai-engine-info">
|
|
Silnik AI: <strong>Google Gemini</strong>. Domyslny model (Gemini 3 Flash) jest szybki i tani.
|
|
Dla lepszej jakosci wybierz Gemini 3 Pro — lepsza kreatywnosc i styl, ale ~4x wyzszy koszt.
|
|
<span id="ai-model-used" style="display:none;"> Ostatnio uzyty: <strong id="ai-model-used-name"></strong></span>
|
|
</div>
|
|
|
|
<!-- Akcje -->
|
|
<div class="btn-group">
|
|
<a href="{{ url_for('admin.social_publisher_list') }}" class="btn btn-secondary">Anuluj</a>
|
|
|
|
{% set is_debug = configured_companies and configured_companies|selectattr('debug_mode')|list|length > 0 %}
|
|
{% if post %}
|
|
<button type="submit" name="action" value="save" class="btn btn-primary">Zapisz zmiany</button>
|
|
{% if post.status == 'draft' %}
|
|
<button type="submit" name="action" value="approve" class="btn btn-info" style="background: #0ea5e9; color: white; border: none;">Zatwierdz</button>
|
|
{% endif %}
|
|
{% if post.status in ['draft', 'approved'] %}
|
|
<button type="submit" name="action" value="publish" class="btn btn-success">Publikuj teraz{% if is_debug %} (debug){% endif %}</button>
|
|
{% if is_debug %}
|
|
<button type="submit" name="action" value="publish_live" class="btn btn-success"
|
|
style="background: #dc2626; border: none;"
|
|
onclick="return confirm('Post zostanie opublikowany PUBLICZNIE i bedzie widoczny dla wszystkich. Kontynuowac?');">
|
|
Publikuj na zywo
|
|
</button>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if post.status == 'published' and post.meta_post_id %}
|
|
</div>
|
|
<div class="btn-group" style="margin-top: var(--spacing-md);">
|
|
<strong style="align-self: center;">Widoczność na FB:</strong>
|
|
{% if post.is_live %}
|
|
<form method="POST" action="{{ url_for('admin.social_publisher_toggle_visibility', post_id=post.id) }}" style="display:inline;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-secondary"
|
|
onclick="return confirm('Post zostanie UKRYTY — widoczny tylko dla adminów strony FB. Kontynuować?');">
|
|
Zmień na debug
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<form method="POST" action="{{ url_for('admin.social_publisher_toggle_visibility', post_id=post.id) }}" style="display:inline;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-success"
|
|
style="background: #dc2626; border: none;"
|
|
onclick="return confirm('Post zostanie UPUBLICZNIONY — widoczny dla wszystkich. Kontynuować?');">
|
|
Opublikuj publicznie
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if post.status != 'published' %}
|
|
<button type="submit" name="action" value="delete" class="btn btn-error"
|
|
onclick="return confirm('Czy na pewno chcesz usunac ten post?');">Usun</button>
|
|
{% endif %}
|
|
{% else %}
|
|
<button type="submit" name="action" value="draft" class="btn btn-secondary">Zapisz szkic</button>
|
|
<button type="submit" name="action" value="publish" class="btn btn-success">Publikuj teraz{% if is_debug %} (debug){% endif %}</button>
|
|
{% if is_debug %}
|
|
<button type="submit" name="action" value="publish_live" class="btn btn-success"
|
|
style="background: #dc2626; border: none;"
|
|
onclick="return confirm('Post zostanie opublikowany PUBLICZNIE i bedzie widoczny dla wszystkich. Kontynuowac?');">
|
|
Publikuj na zywo
|
|
</button>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</form>
|
|
|
|
{% if post and post.status == 'published' %}
|
|
<!-- Engagement metrics -->
|
|
<div class="engagement-box">
|
|
<h3 class="section-title" style="margin-top: 0;">Engagement</h3>
|
|
<div class="engagement-grid">
|
|
<div class="engagement-item">
|
|
<div class="value">{{ post.engagement_likes or 0 }}</div>
|
|
<div class="label">Polubienia</div>
|
|
</div>
|
|
<div class="engagement-item">
|
|
<div class="value">{{ post.engagement_comments or 0 }}</div>
|
|
<div class="label">Komentarze</div>
|
|
</div>
|
|
<div class="engagement-item">
|
|
<div class="value">{{ post.engagement_shares or 0 }}</div>
|
|
<div class="label">Udostepnienia</div>
|
|
</div>
|
|
<div class="engagement-item">
|
|
<div class="value">{{ post.engagement_reach or 0 }}</div>
|
|
<div class="label">Zasieg</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: var(--spacing-md); align-items: center; flex-wrap: wrap;">
|
|
<form method="POST" action="{{ url_for('admin.social_publisher_refresh_engagement', post_id=post.id) }}" style="display:inline;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-secondary btn-small">Odswiez engagement</button>
|
|
</form>
|
|
{% if post.meta_post_id %}
|
|
<a href="https://www.facebook.com/{{ post.meta_post_id }}" target="_blank" class="fb-link">
|
|
Zobacz post na Facebook ↗
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
function showAiError(msg) {
|
|
const el = document.getElementById('ai-error-msg');
|
|
el.textContent = msg;
|
|
el.style.display = 'block';
|
|
}
|
|
function hideAiError() {
|
|
document.getElementById('ai-error-msg').style.display = 'none';
|
|
}
|
|
|
|
// AI generation
|
|
document.getElementById('btn-generate-ai')?.addEventListener('click', async function() {
|
|
const postType = document.getElementById('post_type').value;
|
|
const companyId = document.getElementById('company_id')?.value;
|
|
const eventId = document.getElementById('event_id')?.value;
|
|
const tone = document.getElementById('tone')?.value || '';
|
|
const aiModel = document.getElementById('ai_model')?.value || '';
|
|
|
|
if (!postType) {
|
|
showAiError('Wybierz typ posta przed generowaniem.');
|
|
return;
|
|
}
|
|
|
|
const customContext = {};
|
|
const topicEl = document.getElementById('custom_topic');
|
|
if (topicEl) customContext.topic = topicEl.value;
|
|
const detailsEl = document.getElementById('custom_details');
|
|
if (detailsEl) customContext.details = detailsEl.value;
|
|
const factsEl = document.getElementById('custom_facts');
|
|
if (factsEl) customContext.facts = factsEl.value;
|
|
const sourceEl = document.getElementById('custom_source');
|
|
if (sourceEl) customContext.source = sourceEl.value;
|
|
|
|
this.disabled = true;
|
|
this.textContent = 'Generowanie...';
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('admin.social_publisher_generate') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrf_token]')?.value || ''
|
|
},
|
|
body: JSON.stringify({
|
|
post_type: postType,
|
|
company_id: companyId || null,
|
|
event_id: eventId || null,
|
|
publishing_company_id: document.getElementById('publishing_company_id')?.value || null,
|
|
tone: tone,
|
|
ai_model: aiModel,
|
|
custom_context: customContext
|
|
})
|
|
});
|
|
const data = await resp.json();
|
|
if (data.success) {
|
|
document.getElementById('content').value = data.content;
|
|
if (data.hashtags) {
|
|
document.getElementById('hashtags').value = data.hashtags;
|
|
}
|
|
updateContentCounter();
|
|
hideAiError();
|
|
if (data.model) {
|
|
document.getElementById('ai-model-used-name').textContent = data.model;
|
|
document.getElementById('ai-model-used').style.display = 'inline';
|
|
}
|
|
} else {
|
|
showAiError(data.error || 'Nie udalo sie wygenerowac tresci. Sprobuj ponownie.');
|
|
}
|
|
} catch (err) {
|
|
showAiError('Blad polaczenia z serwerem. Sprawdz polaczenie internetowe i sprobuj ponownie.');
|
|
} finally {
|
|
this.disabled = false;
|
|
this.textContent = 'Generuj AI';
|
|
}
|
|
});
|
|
|
|
// Hashtag generation
|
|
document.getElementById('btn-generate-hashtags')?.addEventListener('click', async function() {
|
|
const content = document.getElementById('content').value.trim();
|
|
const errEl = document.getElementById('hashtag-error-msg');
|
|
|
|
if (!content) {
|
|
errEl.textContent = 'Wpisz najpierw tresc posta, aby wygenerowac hashtagi.';
|
|
errEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
errEl.style.display = 'none';
|
|
this.disabled = true;
|
|
this.textContent = 'Generowanie...';
|
|
|
|
try {
|
|
const resp = await fetch("{{ url_for('admin.social_publisher_generate_hashtags') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('[name=csrf_token]')?.value || ''
|
|
},
|
|
body: JSON.stringify({
|
|
content: content,
|
|
post_type: document.getElementById('post_type')?.value || '',
|
|
ai_model: document.getElementById('ai_model')?.value || ''
|
|
})
|
|
});
|
|
const data = await resp.json();
|
|
if (data.success) {
|
|
document.getElementById('hashtags').value = data.hashtags;
|
|
errEl.style.display = 'none';
|
|
} else {
|
|
errEl.textContent = data.error || 'Nie udalo sie wygenerowac hashtagow.';
|
|
errEl.style.display = 'block';
|
|
}
|
|
} catch (err) {
|
|
errEl.textContent = 'Blad polaczenia z serwerem.';
|
|
errEl.style.display = 'block';
|
|
} finally {
|
|
this.disabled = false;
|
|
this.textContent = 'Generuj hashtagi AI';
|
|
}
|
|
});
|
|
|
|
// Show/hide context fields based on post type
|
|
document.getElementById('post_type')?.addEventListener('change', function() {
|
|
const type = this.value;
|
|
document.getElementById('company-field')?.classList.toggle('d-none', type !== 'member_spotlight');
|
|
document.getElementById('event-field')?.classList.toggle('d-none', !type.startsWith('event_'));
|
|
document.getElementById('custom-context-field')?.classList.toggle('d-none',
|
|
!['regional_news', 'chamber_news'].includes(type));
|
|
});
|
|
// Trigger on load
|
|
document.getElementById('post_type')?.dispatchEvent(new Event('change'));
|
|
|
|
// Character counter
|
|
function updateContentCounter() {
|
|
const content = document.getElementById('content');
|
|
const counter = document.getElementById('content-counter');
|
|
if (content && counter) {
|
|
const len = content.value.length;
|
|
counter.textContent = len + ' znaków';
|
|
counter.classList.remove('warning', 'over');
|
|
if (len > 2000) counter.classList.add('over');
|
|
else if (len > 1500) counter.classList.add('warning');
|
|
}
|
|
}
|
|
document.getElementById('content')?.addEventListener('input', updateContentCounter);
|
|
updateContentCounter();
|
|
{% endblock %}
|