nordabiz/templates/classifieds/view.html
Maciej Pienczyn cebe52f303 refactor: Rebranding i aktualizacja modelu AI
- Zmiana nazwy: "Norda Biznes Hub" → "Norda Biznes Partner"
- Aktualizacja modelu AI: Gemini 2.0 Flash → Gemini 3 Flash
- Zachowano historyczne odniesienia w timeline i dokumentacji

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:08:39 +01:00

379 lines
13 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}{{ classified.title }} - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.classified-container {
max-width: 800px;
margin: 0 auto;
}
.classified-card {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
}
.classified-card.szukam {
border-top: 4px solid var(--warning);
}
.classified-card.oferuje {
border-top: 4px solid var(--success);
}
.classified-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-lg);
}
.classified-type {
display: inline-block;
padding: 4px 12px;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 600;
text-transform: uppercase;
}
.classified-type.szukam {
background: #fef3c7;
color: #92400e;
}
.classified-type.oferuje {
background: #dcfce7;
color: #166534;
}
.classified-category {
display: inline-block;
padding: 4px 12px;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 500;
text-transform: uppercase;
margin-left: var(--spacing-sm);
}
.category-uslugi {
background: #dbeafe;
color: #1e40af;
}
.category-produkty {
background: #fef3c7;
color: #92400e;
}
.category-wspolpraca {
background: #dcfce7;
color: #166534;
}
.category-praca {
background: #fce7f3;
color: #9d174d;
}
.category-inne {
background: #f3f4f6;
color: #374151;
}
.category-nieruchomosci {
background: #e0e7ff;
color: #3730a3;
}
.classified-title {
font-size: var(--font-size-2xl);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.classified-description {
line-height: 1.8;
color: var(--text-primary);
margin-bottom: var(--spacing-xl);
white-space: pre-wrap;
}
.classified-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg);
padding: var(--spacing-lg);
background: var(--background);
border-radius: var(--radius);
margin-bottom: var(--spacing-xl);
}
.detail-item {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
}
.detail-item svg {
width: 20px;
height: 20px;
color: var(--primary);
flex-shrink: 0;
margin-top: 2px;
}
.detail-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.detail-value {
font-weight: 500;
color: var(--text-primary);
}
.author-card {
display: flex;
align-items: center;
gap: var(--spacing-lg);
padding: var(--spacing-lg);
background: var(--background);
border-radius: var(--radius);
}
.author-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: var(--font-size-lg);
}
.author-info {
flex: 1;
}
.author-name {
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.author-company {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
color: var(--text-secondary);
text-decoration: none;
margin-bottom: var(--spacing-lg);
}
.back-link:hover {
color: var(--primary);
}
.stats-bar {
display: flex;
justify-content: space-between;
font-size: var(--font-size-sm);
color: var(--text-secondary);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border);
margin-top: var(--spacing-lg);
}
.close-btn {
margin-left: auto;
}
</style>
{% endblock %}
{% block content %}
<div class="classified-container">
<a href="{{ url_for('classifieds.classifieds_index') }}" class="back-link">
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
Powrot do tablicy
</a>
<div class="classified-card {{ classified.listing_type }}">
<div class="classified-header">
<div>
<span class="classified-type {{ classified.listing_type }}">{{ 'Szukam' if classified.listing_type == 'szukam' else 'Oferuje' }}</span>
<span class="classified-category category-{{ classified.category }}">{{ classified.category|replace('uslugi', 'Usługi')|replace('produkty', 'Produkty')|replace('wspolpraca', 'Współpraca')|replace('praca', 'Praca')|replace('inne', 'Inne')|replace('nieruchomosci', 'Nieruchomości') }}</span>
</div>
{% if classified.author_id == current_user.id %}
<button class="btn btn-secondary btn-sm close-btn" onclick="closeClassified()">Zamknij ogloszenie</button>
{% endif %}
</div>
<h1 class="classified-title">{{ classified.title }}</h1>
<div class="classified-description">{{ classified.description }}</div>
{% if classified.budget_info or classified.location_info %}
<div class="classified-details">
{% if classified.budget_info %}
<div class="detail-item">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div>
<div class="detail-label">Budzet / Cena</div>
<div class="detail-value">{{ classified.budget_info }}</div>
</div>
</div>
{% endif %}
{% if classified.location_info %}
<div class="detail-item">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<div>
<div class="detail-label">Lokalizacja</div>
<div class="detail-value">{{ classified.location_info }}</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
<div class="author-card">
<div class="author-avatar">
{{ (classified.author.name or classified.author.email)[0].upper() }}
</div>
<div class="author-info">
<div class="author-name">{{ classified.author.name or classified.author.email.split('@')[0] }}</div>
{% if classified.company %}
<div class="author-company">{{ classified.company.name }}</div>
{% endif %}
</div>
{% if classified.author_id != current_user.id %}
<a href="{{ url_for('messages_new', to=classified.author_id) }}" class="btn btn-primary">Skontaktuj sie</a>
{% endif %}
</div>
<div class="stats-bar">
<span>{{ classified.views_count }} wyswietlen</span>
<span>Dodano: {{ classified.created_at.strftime('%d.%m.%Y %H:%M') }}</span>
{% if classified.expires_at %}
<span>Wygasa: {{ classified.expires_at.strftime('%d.%m.%Y') }}</span>
{% endif %}
</div>
</div>
</div>
<!-- Universal Confirm Modal -->
<div class="modal-overlay" id="confirmModal">
<div class="modal" style="max-width: 420px; background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-xl);">
<div style="text-align: center; margin-bottom: var(--spacing-lg);">
<div class="modal-icon" id="confirmModalIcon" style="font-size: 3em; margin-bottom: var(--spacing-md);"></div>
<h3 id="confirmModalTitle" style="margin-bottom: var(--spacing-sm);">Potwierdzenie</h3>
<p class="modal-description" id="confirmModalMessage" style="color: var(--text-secondary);"></p>
</div>
<div class="modal-actions" style="display: flex; gap: var(--spacing-sm); justify-content: center;">
<button type="button" class="btn btn-secondary" id="confirmModalCancel">Anuluj</button>
<button type="button" class="btn btn-primary" id="confirmModalOk">OK</button>
</div>
</div>
</div>
<div id="toastContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1100; display: flex; flex-direction: column; gap: 10px;"></div>
<style>
.modal-overlay#confirmModal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1050; align-items: center; justify-content: center; }
.modal-overlay#confirmModal.active { display: flex; }
.toast { padding: 12px 20px; border-radius: var(--radius); background: var(--surface); border-left: 4px solid var(--primary); box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px; animation: toastIn 0.3s ease; }
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--error); }
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
</style>
{% endblock %}
{% block extra_js %}
const csrfToken = '{{ csrf_token() }}';
let confirmResolve = null;
function showConfirm(message, options = {}) {
return new Promise(resolve => {
confirmResolve = resolve;
document.getElementById('confirmModalIcon').textContent = options.icon || '❓';
document.getElementById('confirmModalTitle').textContent = options.title || 'Potwierdzenie';
document.getElementById('confirmModalMessage').innerHTML = message;
document.getElementById('confirmModalOk').textContent = options.okText || 'OK';
document.getElementById('confirmModalOk').className = 'btn ' + (options.okClass || 'btn-primary');
document.getElementById('confirmModal').classList.add('active');
});
}
function closeConfirm(result) {
document.getElementById('confirmModal').classList.remove('active');
if (confirmResolve) { confirmResolve(result); confirmResolve = null; }
}
document.getElementById('confirmModalOk').addEventListener('click', () => closeConfirm(true));
document.getElementById('confirmModalCancel').addEventListener('click', () => closeConfirm(false));
document.getElementById('confirmModal').addEventListener('click', e => { if (e.target.id === 'confirmModal') closeConfirm(false); });
function showToast(message, type = 'info', duration = 4000) {
const container = document.getElementById('toastContainer');
const icons = { success: '✓', error: '✕', warning: '⚠', info: '' };
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `<span style="font-size:1.2em">${icons[type]||''}</span><span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration);
}
async function closeClassified() {
const confirmed = await showConfirm('Czy na pewno chcesz zamknąć to ogłoszenie?', {
icon: '🔒',
title: 'Zamykanie ogłoszenia',
okText: 'Zamknij',
okClass: 'btn-warning'
});
if (!confirmed) return;
try {
const response = await fetch('{{ url_for("classifieds.classifieds_close", classified_id=classified.id) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
if (data.success) {
showToast('Ogłoszenie zostało zamknięte', 'success');
setTimeout(() => window.location.href = '{{ url_for("classifieds.classifieds_index") }}', 1500);
} else {
showToast(data.error || 'Wystąpił błąd', 'error');
}
} catch (error) {
showToast('Błąd połączenia', 'error');
}
}
{% endblock %}