nordabiz/templates/announcements/detail.html
Maciej Pienczyn 110d971dca
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: migrate prod docs to OVH VPS + UTC→Warsaw timezone in all templates
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS
(57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash
commands, memory files, architecture docs, and deploy procedures.

Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted
155 .strftime() calls across 71 templates so timestamps display
in Polish timezone regardless of server timezone.

Also includes: created_by_id tracking, abort import fix, ICS
calendar fix for missing end times, Pros Poland data cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:41:53 +02:00

528 lines
16 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ announcement.title }} - Aktualności - Norda Biznes Partner{% endblock %}
{% block meta_description %}{{ announcement.excerpt or announcement.content|striptags|truncate(160) }}{% endblock %}
{% block extra_css %}
<style>
.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-lg);
}
.back-link:hover {
color: var(--primary);
}
.announcement-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: var(--spacing-2xl);
}
@media (max-width: 992px) {
.announcement-layout {
grid-template-columns: 1fr;
}
}
.announcement-main {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
overflow: hidden;
}
.announcement-header {
padding: var(--spacing-xl);
border-bottom: 1px solid var(--border);
}
.announcement-meta {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
flex-wrap: wrap;
}
.category-badge {
display: inline-block;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 600;
background: var(--primary-bg);
color: var(--primary);
}
.category-internal { background: #dbeafe; color: #1d4ed8; }
.category-external { background: #fef3c7; color: #b45309; }
.category-event { background: #e0f2fe; color: #0369a1; }
.category-opportunity { background: #dcfce7; color: #15803d; }
.category-partnership { background: #f3e8ff; color: #7c3aed; }
.meta-date {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.meta-views {
color: var(--text-muted);
font-size: var(--font-size-sm);
}
.meta-author {
color: var(--text-secondary);
font-size: var(--font-size-sm);
font-weight: 500;
}
.announcement-title {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--text-primary);
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.announcement-excerpt {
font-size: var(--font-size-lg);
color: var(--text-secondary);
line-height: 1.5;
}
.announcement-image {
width: 100%;
max-height: 400px;
object-fit: cover;
}
.announcement-content {
padding: var(--spacing-xl);
}
.announcement-content p {
margin-bottom: var(--spacing-md);
line-height: 1.8;
}
.announcement-content h3 {
margin-top: var(--spacing-xl);
margin-bottom: var(--spacing-md);
font-size: var(--font-size-xl);
color: var(--text-primary);
}
.announcement-content ul, .announcement-content ol {
margin-bottom: var(--spacing-md);
padding-left: var(--spacing-xl);
}
.announcement-content li {
margin-bottom: var(--spacing-sm);
line-height: 1.6;
}
.announcement-content a {
color: var(--primary);
}
.announcement-content a:hover {
text-decoration: underline;
}
.external-link-box {
background: var(--primary-bg);
border: 1px solid var(--primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin: var(--spacing-xl) 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
flex-wrap: wrap;
}
.external-link-box .link-text {
font-weight: 500;
color: var(--text-primary);
}
.external-link-box .btn {
white-space: nowrap;
background: var(--primary);
color: #ffffff;
padding: 10px 24px;
border-radius: var(--radius);
font-weight: 600;
text-decoration: none;
}
.external-link-box .btn:hover {
opacity: 0.9;
}
.sidebar {
position: sticky;
top: var(--spacing-xl);
}
.sidebar-section {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.sidebar-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 2px solid var(--primary);
}
.other-announcement {
padding: var(--spacing-md) 0;
border-bottom: 1px solid var(--border);
}
.other-announcement:last-child {
border-bottom: none;
padding-bottom: 0;
}
.other-announcement:first-child {
padding-top: 0;
}
.other-announcement a {
color: var(--text-primary);
text-decoration: none;
font-weight: 500;
line-height: 1.4;
display: block;
}
.other-announcement a:hover {
color: var(--primary);
}
.other-announcement .date {
font-size: var(--font-size-xs);
color: var(--text-muted);
margin-top: var(--spacing-xs);
}
.share-buttons {
display: flex;
gap: var(--spacing-sm);
}
.share-btn {
width: 40px;
height: 40px;
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
font-size: 1.2em;
transition: transform 0.2s ease;
}
.share-btn:hover {
transform: scale(1.1);
}
.share-btn.facebook { background: #1877f2; color: white; }
.share-btn.linkedin { background: #0a66c2; color: white; }
.share-btn.twitter { background: #1da1f2; color: white; }
.share-btn.copy { background: var(--surface-secondary); color: var(--text-primary); border: 1px solid var(--border); }
.pinned-notice {
background: linear-gradient(135deg, var(--primary-bg) 0%, var(--surface) 100%);
border: 1px solid var(--primary);
border-radius: var(--radius);
padding: var(--spacing-sm) var(--spacing-md);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--primary);
font-weight: 500;
}
/* Seen by section */
.seen-by-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border);
}
.seen-by-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.seen-by-title {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.seen-by-stats {
font-size: var(--font-size-xs);
color: var(--text-muted);
background: var(--background);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-full);
}
.seen-by-avatars {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
}
.reader-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.reader-avatar:hover {
transform: scale(1.1);
box-shadow: var(--shadow-md);
z-index: 10;
}
.reader-avatar[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: var(--text-primary);
color: white;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
white-space: nowrap;
margin-bottom: 4px;
z-index: 100;
}
.reader-avatar.more {
background: var(--surface-secondary);
color: var(--text-secondary);
border: 1px solid var(--border);
}
.progress-bar-container {
width: 100%;
height: 6px;
background: var(--border);
border-radius: 3px;
margin-top: var(--spacing-sm);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary) 0%, var(--success) 100%);
border-radius: 3px;
transition: width 0.5s ease;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<a href="{{ url_for('announcements_list') }}" class="back-link">
&larr; Powrot do listy ogloszen
</a>
{% if announcement.is_pinned %}
<div class="pinned-notice">
&#128204; To ogloszenie jest przypiete i wyswietla sie na gorze listy
</div>
{% endif %}
<div class="announcement-layout">
<!-- Main content -->
<article class="announcement-main">
<div class="announcement-header">
<div class="announcement-meta">
{% for cat in (announcement.categories or [announcement.category]) %}
<span class="category-badge category-{{ cat }}">
{{ category_labels.get(cat, cat) }}
</span>
{% endfor %}
<span class="meta-date">
{{ announcement.published_at|local_time('%d %B %Y') if announcement.published_at else '' }}
</span>
{% if announcement.author %}
<span class="meta-author">
&#128100; {{ announcement.author.name }}
</span>
{% endif %}
<span class="meta-views">
&#128065; {{ announcement.views_count or 0 }} wyswietlen
</span>
</div>
<h1 class="announcement-title">{{ announcement.title }}</h1>
{% if announcement.excerpt %}
<p class="announcement-excerpt">{{ announcement.excerpt }}</p>
{% endif %}
</div>
{% if announcement.image_url %}
<img src="{{ announcement.image_url }}" alt="{{ announcement.title }}" class="announcement-image"
onerror="this.style.display='none'">
{% endif %}
<div class="announcement-content">
{{ announcement.content|safe }}
{% if announcement.external_link %}
<div class="external-link-box">
<div class="link-text">
&#127760; Wiecej informacji znajdziesz na zewnetrznej stronie
</div>
<a href="{{ announcement.external_link }}" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
Przejdz do strony &rarr;
</a>
</div>
{% endif %}
<!-- Seen by section -->
<div class="seen-by-section">
<div class="seen-by-header">
<div class="seen-by-title">
&#128065; Przeczytane przez
</div>
<div class="seen-by-stats">
{{ readers_count }} z {{ total_users }} ({{ read_percentage }}%)
</div>
</div>
<div class="seen-by-avatars">
{% for read in readers[:20] %}
<div class="reader-avatar"
data-tooltip="{{ read.user.name or read.user.email.split('@')[0] }}{% if current_user.is_authenticated and read.user.id == current_user.id %} (Ty){% endif %}"
style="background: hsl({{ (read.user.id * 137) % 360 }}, 65%, 50%);">
{{ (read.user.name or read.user.email)[0]|upper }}
</div>
{% endfor %}
{% if readers_count > 20 %}
<div class="reader-avatar more" data-tooltip="i {{ readers_count - 20 }} innych">
+{{ readers_count - 20 }}
</div>
{% endif %}
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: {{ read_percentage }}%;"></div>
</div>
</div>
</div>
</article>
<!-- Sidebar -->
<aside class="sidebar">
<!-- Share -->
<div class="sidebar-section">
<h3 class="sidebar-title">Udostepnij</h3>
<div class="share-buttons">
<a href="https://www.facebook.com/sharer/sharer.php?u={{ request.url|urlencode }}"
target="_blank" rel="noopener" class="share-btn facebook" title="Udostepnij na Facebooku">
f
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ request.url|urlencode }}"
target="_blank" rel="noopener" class="share-btn linkedin" title="Udostepnij na LinkedIn">
in
</a>
<a href="https://twitter.com/intent/tweet?url={{ request.url|urlencode }}&text={{ announcement.title|urlencode }}"
target="_blank" rel="noopener" class="share-btn twitter" title="Udostepnij na Twitterze">
X
</a>
<button class="share-btn copy" title="Kopiuj link" onclick="copyLink()">
&#128279;
</button>
</div>
</div>
<!-- Other announcements -->
{% if other_announcements %}
<div class="sidebar-section">
<h3 class="sidebar-title">Inne ogloszenia</h3>
{% for other in other_announcements %}
<div class="other-announcement">
<a href="{{ url_for('announcement_detail', slug=other.slug) }}">
{{ other.title }}
</a>
<div class="date">
{{ other.published_at|local_time('%d.%m.%Y') if other.published_at else '' }}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Back to list -->
<div class="sidebar-section" style="text-align: center;">
<a href="{{ url_for('announcements_list') }}" class="btn btn-secondary" style="width: 100%;">
&larr; Wszystkie ogloszenia
</a>
</div>
</aside>
</div>
</div>
{% endblock %}
{% block extra_js %}
function copyLink() {
navigator.clipboard.writeText(window.location.href).then(function() {
const btn = document.querySelector('.share-btn.copy');
const originalText = btn.innerHTML;
btn.innerHTML = '&#10003;';
btn.style.background = 'var(--success-bg)';
btn.style.color = 'var(--success)';
setTimeout(function() {
btn.innerHTML = originalText;
btn.style.background = '';
btn.style.color = '';
}, 2000);
});
}
{% endblock %}