Files
Onyva-Postling/src/web/templates/user/company_manage_posts.html
2026-02-15 17:24:48 +01:00

287 lines
15 KiB
HTML

{% extends "company_base.html" %}
{% block title %}Posts - {{ employee_name }} - {{ session.company_name }}{% endblock %}
{% macro render_post_card(post) %}
<div class="post-card"
draggable="true"
data-post-id="{{ post.id }}"
ondragstart="handleDragStart(event)"
ondragend="handleDragEnd(event)"
onclick="window.location.href='/company/manage/post/{{ post.id }}?employee_id={{ employee_id }}'">
<div class="flex items-start justify-between gap-2 mb-2">
<h4 class="post-card-title">{{ post.topic_title or 'Untitled' }}</h4>
{% if post.critic_feedback and post.critic_feedback | length > 0 and post.critic_feedback[-1].get('overall_score') is not none %}
{% set score = post.critic_feedback[-1].get('overall_score', 0) %}
<span class="score-badge flex-shrink-0 {{ 'score-high' if score >= 85 else 'score-medium' if score >= 70 else 'score-low' }}">
{{ score }}
</span>
{% endif %}
</div>
<div class="post-card-meta">
<span class="flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
{{ post.created_at.strftime('%d.%m.%Y') if post.created_at else 'N/A' }}
</span>
<span class="flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
{{ post.iterations }}x
</span>
</div>
{% if post.post_content %}
<p class="post-card-preview">{{ post.post_content[:150] }}{% if post.post_content | length > 150 %}...{% endif %}</p>
{% endif %}
</div>
{% endmacro %}
{% block head %}
<style>
.kanban-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
min-height: calc(100vh - 300px);
}
@media (max-width: 1024px) {
.kanban-board { grid-template-columns: 1fr; }
}
.kanban-column {
background: rgba(45, 56, 56, 0.3);
border: 1px solid rgba(61, 72, 72, 0.6);
border-radius: 1rem;
display: flex;
flex-direction: column;
min-height: 400px;
}
.kanban-header {
padding: 1rem 1.25rem;
border-bottom: 1px solid rgba(61, 72, 72, 0.6);
display: flex;
align-items: center;
justify-content: space-between;
}
.kanban-header h3 { font-weight: 600; display: flex; align-items: center; gap: 0.5rem; }
.kanban-count { background: rgba(61, 72, 72, 0.8); padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; }
.kanban-body { flex: 1; padding: 1rem; overflow-y: auto; min-height: 100px; }
.kanban-body.drag-over { background: rgba(255, 199, 0, 0.05); border: 2px dashed rgba(255, 199, 0, 0.3); border-radius: 0.5rem; margin: 0.5rem; }
.post-card {
background: linear-gradient(135deg, rgba(61, 72, 72, 0.5) 0%, rgba(45, 56, 56, 0.6) 100%);
border: 1px solid rgba(61, 72, 72, 0.8);
border-radius: 0.75rem;
padding: 1rem;
margin-bottom: 0.75rem;
cursor: grab;
transition: all 0.2s ease;
}
.post-card:hover { border-color: rgba(255, 199, 0, 0.4); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); }
.post-card.dragging { opacity: 0.5; cursor: grabbing; }
.post-card-title { font-weight: 500; color: white; margin-bottom: 0.5rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.post-card-meta { display: flex; align-items: center; gap: 0.75rem; font-size: 0.75rem; color: #9ca3af; }
.post-card-preview { font-size: 0.8rem; color: #9ca3af; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid rgba(61, 72, 72, 0.6); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; }
.score-badge { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.7rem; font-weight: 600; }
.score-high { background: rgba(34, 197, 94, 0.2); color: #86efac; }
.score-medium { background: rgba(234, 179, 8, 0.2); color: #fde047; }
.score-low { background: rgba(239, 68, 68, 0.2); color: #fca5a5; }
.column-draft .kanban-header { border-left: 3px solid #f59e0b; }
.column-approved .kanban-header { border-left: 3px solid #3b82f6; }
.column-ready .kanban-header { border-left: 3px solid #22c55e; }
.empty-column { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem; color: #6b7280; text-align: center; }
.empty-column svg { width: 3rem; height: 3rem; margin-bottom: 0.75rem; opacity: 0.5; }
</style>
{% endblock %}
{% block content %}
<!-- Breadcrumb -->
<div class="mb-6">
<nav class="flex items-center gap-2 text-sm">
<a href="/company/manage" class="text-gray-400 hover:text-white transition-colors">Inhalte verwalten</a>
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
<a href="/company/manage?employee_id={{ employee_id }}" class="text-gray-400 hover:text-white transition-colors">{{ employee_name }}</a>
<svg class="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
<span class="text-white">Posts</span>
</nav>
</div>
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-white mb-1">Posts von {{ employee_name }}</h1>
<p class="text-gray-400 text-sm">Ziehe Posts zwischen den Spalten um den Status zu ändern</p>
</div>
<a href="/company/manage/create?employee_id={{ employee_id }}" class="px-4 py-2.5 btn-primary rounded-lg font-medium flex items-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
Neuer Post
</a>
</div>
<div class="kanban-board">
<!-- Column: Vorschlag (draft) -->
<div class="kanban-column column-draft">
<div class="kanban-header">
<h3 class="text-yellow-400">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
Vorschlag
</h3>
<span class="kanban-count" id="count-draft">{{ posts | selectattr('status', 'equalto', 'draft') | list | length }}</span>
</div>
<div class="kanban-body" data-status="draft" ondragover="handleDragOver(event)" ondrop="handleDrop(event)" ondragleave="handleDragLeave(event)">
{% for post in posts if post.status == 'draft' %}
{{ render_post_card(post) }}
{% else %}
<div class="empty-column" id="empty-draft">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
<p>Keine Vorschläge</p>
</div>
{% endfor %}
</div>
</div>
<!-- Column: Bearbeitet (approved) - waiting for customer approval -->
<div class="kanban-column column-approved">
<div class="kanban-header">
<h3 class="text-blue-400">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
Bearbeitet
</h3>
<span class="kanban-count" id="count-approved">{{ posts | selectattr('status', 'equalto', 'approved') | list | length }}</span>
</div>
<div class="kanban-body" data-status="approved" ondragover="handleDragOver(event)" ondrop="handleDrop(event)" ondragleave="handleDragLeave(event)">
{% for post in posts if post.status == 'approved' %}
{{ render_post_card(post) }}
{% else %}
<div class="empty-column" id="empty-approved">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
<p>Keine bearbeiteten Posts</p>
</div>
{% endfor %}
</div>
</div>
<!-- Column: Freigegeben (ready) - approved by customer, ready for calendar scheduling -->
<div class="kanban-column column-ready">
<div class="kanban-header">
<h3 class="text-green-400">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Freigegeben
</h3>
<span class="kanban-count" id="count-ready">{{ posts | selectattr('status', 'equalto', 'ready') | list | length }}</span>
</div>
<div class="kanban-body" data-status="ready" ondragover="handleDragOver(event)" ondrop="handleDrop(event)" ondragleave="handleDragLeave(event)">
{% for post in posts if post.status == 'ready' %}
{{ render_post_card(post) }}
{% else %}
<div class="empty-column" id="empty-ready">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<p>Keine freigegebenen Posts</p>
</div>
{% endfor %}
</div>
</div>
</div>
{% if not posts %}
<div class="card-bg rounded-xl border p-12 text-center mt-6">
<div class="w-20 h-20 bg-brand-bg rounded-2xl flex items-center justify-center mx-auto mb-6">
<svg class="w-10 h-10 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">Noch keine Posts</h3>
<p class="text-gray-400 mb-6 max-w-md mx-auto">Erstelle den ersten LinkedIn Post für {{ employee_name }}.</p>
<a href="/company/manage/create?employee_id={{ employee_id }}" class="inline-flex items-center gap-2 px-6 py-3 btn-primary font-medium rounded-lg transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/></svg>
Post erstellen
</a>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
let draggedElement = null;
let sourceStatus = null;
function handleDragStart(e) {
draggedElement = e.target;
sourceStatus = e.target.closest('.kanban-body').dataset.status;
e.target.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', e.target.dataset.postId);
}
function handleDragEnd(e) {
e.target.classList.remove('dragging');
document.querySelectorAll('.kanban-body').forEach(body => body.classList.remove('drag-over'));
}
function handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const kanbanBody = e.target.closest('.kanban-body');
if (kanbanBody) kanbanBody.classList.add('drag-over');
}
function handleDragLeave(e) {
const kanbanBody = e.target.closest('.kanban-body');
if (kanbanBody && !kanbanBody.contains(e.relatedTarget)) kanbanBody.classList.remove('drag-over');
}
async function handleDrop(e) {
e.preventDefault();
const kanbanBody = e.target.closest('.kanban-body');
if (!kanbanBody || !draggedElement) return;
kanbanBody.classList.remove('drag-over');
const newStatus = kanbanBody.dataset.status;
const postId = draggedElement.dataset.postId;
if (sourceStatus === newStatus) return;
const emptyPlaceholder = kanbanBody.querySelector('.empty-column');
if (emptyPlaceholder) emptyPlaceholder.remove();
kanbanBody.appendChild(draggedElement);
const sourceBody = document.querySelector(`.kanban-body[data-status="${sourceStatus}"]`);
if (sourceBody && sourceBody.querySelectorAll('.post-card').length === 0) {
addEmptyPlaceholder(sourceBody, sourceStatus);
}
updateCounts();
try {
const formData = new FormData();
formData.append('status', newStatus);
const response = await fetch(`/api/posts/${postId}/status`, {
method: 'PATCH',
body: formData
});
if (!response.ok) throw new Error('Failed to update status');
} catch (error) {
console.error('Error updating status:', error);
location.reload();
}
}
function addEmptyPlaceholder(container, status) {
const icons = {
'draft': '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>',
'approved': '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>',
'ready': '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>'
};
const labels = { 'draft': 'Keine Vorschläge', 'approved': 'Keine bearbeiteten Posts', 'ready': 'Keine freigegebenen Posts' };
const placeholder = document.createElement('div');
placeholder.className = 'empty-column';
placeholder.id = `empty-${status}`;
placeholder.innerHTML = `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">${icons[status]}</svg><p>${labels[status]}</p>`;
container.appendChild(placeholder);
}
function updateCounts() {
['draft', 'approved', 'ready'].forEach(status => {
const count = document.querySelectorAll(`.kanban-body[data-status="${status}"] .post-card`).length;
document.getElementById(`count-${status}`).textContent = count;
});
}
</script>
{% endblock %}