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
Batch-query confirmed attendee counts per event and display as subtle cyan number with people icon in event meta line. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1344 lines
58 KiB
HTML
Executable File
1344 lines
58 KiB
HTML
Executable File
{% extends "base.html" %}
|
|
|
|
{% block title %}Panel użytkownika - Norda Biznes Partner{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* MTBTRACKER-style dashboard cards */
|
|
.dashboard-card {
|
|
transition: all 0.3s ease;
|
|
border: none;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
background: white;
|
|
border-radius: var(--radius-lg, 12px);
|
|
overflow: hidden;
|
|
}
|
|
.dashboard-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
/* Stat icons - colorful circles */
|
|
.stat-icon {
|
|
width: 60px;
|
|
height: 60px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
color: white;
|
|
}
|
|
.stat-icon.bg-primary { background: var(--primary, #0066cc); }
|
|
.stat-icon.bg-success { background: #10b981; }
|
|
.stat-icon.bg-warning { background: #f59e0b; }
|
|
.stat-icon.bg-info { background: #06b6d4; }
|
|
.stat-icon.bg-danger { background: #ef4444; }
|
|
|
|
/* Admin section */
|
|
.admin-section-highlight {
|
|
border: 2px solid var(--primary, #0066cc);
|
|
border-radius: 12px;
|
|
background: rgba(0, 102, 204, 0.05);
|
|
padding: var(--spacing-xl, 24px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
}
|
|
|
|
/* Card header colors */
|
|
.card-header-primary {
|
|
background: var(--primary, #0066cc);
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-success {
|
|
background: #10b981;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-warning {
|
|
background: #f59e0b;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-info {
|
|
background: #06b6d4;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
|
|
.card-body {
|
|
padding: var(--spacing-lg, 20px);
|
|
}
|
|
|
|
/* Dashboard layout */
|
|
.dashboard-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: var(--spacing-lg, 20px);
|
|
}
|
|
|
|
.dashboard-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-md, 16px);
|
|
}
|
|
|
|
.dashboard-header h2 {
|
|
font-size: var(--font-size-2xl, 1.75rem);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
margin: 0;
|
|
}
|
|
|
|
.dashboard-header h2 svg {
|
|
color: var(--primary, #0066cc);
|
|
}
|
|
|
|
.badge-admin {
|
|
background: #f59e0b;
|
|
color: white;
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Stats grid - 4 columns */
|
|
.stats-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: var(--spacing-md, 16px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
}
|
|
|
|
.stat-card-body {
|
|
padding: var(--spacing-lg, 20px);
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-card-body h5 {
|
|
margin: var(--spacing-sm, 8px) 0;
|
|
font-size: 1rem;
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
.stat-card-body h3 {
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
}
|
|
|
|
.stat-card-body small {
|
|
color: var(--text-secondary, #666);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* Admin function cards grid */
|
|
.admin-functions-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: var(--spacing-md, 16px);
|
|
}
|
|
|
|
.admin-function-card {
|
|
background: white;
|
|
border-radius: var(--radius-lg, 12px);
|
|
padding: var(--spacing-lg, 20px);
|
|
text-align: center;
|
|
text-decoration: none;
|
|
color: var(--text-primary, #333);
|
|
transition: all 0.3s ease;
|
|
border: 1px solid var(--border, #e5e7eb);
|
|
}
|
|
|
|
.admin-function-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
border-color: var(--primary, #0066cc);
|
|
}
|
|
|
|
.admin-function-card svg {
|
|
width: 48px;
|
|
height: 48px;
|
|
margin-bottom: var(--spacing-sm, 8px);
|
|
}
|
|
|
|
.admin-function-card h6 {
|
|
margin: var(--spacing-sm, 8px) 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.admin-function-card .btn-sm {
|
|
font-size: 0.8rem;
|
|
padding: 6px 12px;
|
|
}
|
|
|
|
/* Quick actions row */
|
|
.quick-actions-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: var(--spacing-md, 16px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
}
|
|
|
|
/* Profile card */
|
|
.profile-card-content {
|
|
text-align: center;
|
|
}
|
|
|
|
.profile-avatar {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
background: var(--bg-secondary, #f3f4f6);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto var(--spacing-md, 16px);
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
/* Conversation list */
|
|
.conversation-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--spacing-md, 16px);
|
|
border-bottom: 1px solid var(--border, #e5e7eb);
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.conversation-item:hover {
|
|
background: var(--bg-secondary, #f9fafb);
|
|
}
|
|
|
|
.conversation-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.conversation-title {
|
|
font-weight: 600;
|
|
color: var(--text-primary, #333);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.conversation-meta {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
.badge-messages {
|
|
background: var(--primary, #0066cc);
|
|
color: white;
|
|
padding: 4px 10px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* Empty state */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--spacing-xl, 24px);
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
.empty-state svg {
|
|
opacity: 0.3;
|
|
margin-bottom: var(--spacing-md, 16px);
|
|
}
|
|
|
|
/* Membership CTA */
|
|
.membership-cta {
|
|
background: linear-gradient(135deg, var(--primary, #0066cc) 0%, #0052a3 100%);
|
|
color: white;
|
|
padding: var(--spacing-xl, 24px);
|
|
border-radius: var(--radius-lg, 12px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: var(--spacing-lg, 20px);
|
|
box-shadow: 0 4px 16px rgba(0, 102, 204, 0.3);
|
|
}
|
|
|
|
.membership-cta-content h3 {
|
|
margin: 0 0 var(--spacing-xs, 4px) 0;
|
|
font-size: var(--font-size-xl, 1.25rem);
|
|
}
|
|
|
|
.membership-cta-content p {
|
|
margin: 0;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.membership-cta .btn-cta {
|
|
background: white;
|
|
color: var(--primary, #0066cc);
|
|
padding: var(--spacing-sm, 8px) var(--spacing-xl, 24px);
|
|
border-radius: var(--radius, 6px);
|
|
text-decoration: none;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.membership-cta .btn-cta:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
/* Pending application status */
|
|
.membership-status {
|
|
background: var(--warning-light, #fef3c7);
|
|
border: 1px solid var(--warning, #f59e0b);
|
|
padding: var(--spacing-lg, 20px);
|
|
border-radius: var(--radius-lg, 12px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: var(--spacing-lg, 20px);
|
|
}
|
|
|
|
.membership-status-content h4 {
|
|
margin: 0 0 var(--spacing-xs, 4px) 0;
|
|
color: var(--warning, #f59e0b);
|
|
}
|
|
|
|
.membership-status-content p {
|
|
margin: 0;
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
|
|
.membership-status .btn-outline {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Company data update CTA */
|
|
.data-update-cta {
|
|
background: var(--surface, #f9fafb);
|
|
border: 2px dashed var(--warning, #f59e0b);
|
|
padding: var(--spacing-lg, 20px);
|
|
border-radius: var(--radius-lg, 12px);
|
|
margin-bottom: var(--spacing-xl, 24px);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: var(--spacing-lg, 20px);
|
|
}
|
|
|
|
.data-update-cta-content h4 {
|
|
margin: 0 0 var(--spacing-xs, 4px) 0;
|
|
color: var(--warning, #f59e0b);
|
|
}
|
|
|
|
.data-update-cta-content p {
|
|
margin: 0;
|
|
color: var(--text-secondary, #666);
|
|
font-size: var(--font-size-sm, 0.875rem);
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.stats-row {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
.admin-functions-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
.quick-actions-row {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* New widget card headers */
|
|
.card-header-cyan {
|
|
background: #06b6d4;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-amber {
|
|
background: #f59e0b;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-blue {
|
|
background: #3b82f6;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-purple {
|
|
background: #8b5cf6;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
.card-header-green {
|
|
background: #10b981;
|
|
color: white;
|
|
padding: var(--spacing-md, 16px);
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm, 8px);
|
|
}
|
|
|
|
/* Card header with "see all" link */
|
|
.card-header-link {
|
|
margin-left: auto;
|
|
color: rgba(255,255,255,0.85);
|
|
font-size: 0.8rem;
|
|
font-weight: 400;
|
|
text-decoration: none;
|
|
}
|
|
.card-header-link:hover {
|
|
color: white;
|
|
}
|
|
|
|
/* RSVP badge */
|
|
.badge-rsvp {
|
|
background: #10b981;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* B2B listing type badges */
|
|
.badge-szukam {
|
|
background: #ef4444;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
.badge-oferuje {
|
|
background: #10b981;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Event meta info */
|
|
.event-date-badge {
|
|
background: var(--bg-secondary, #f3f4f6);
|
|
padding: 4px 8px;
|
|
border-radius: 6px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary, #333);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Onboarding widget */
|
|
.onboarding-widget {
|
|
background: var(--surface, #fff);
|
|
border-radius: 12px;
|
|
padding: 20px 24px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
margin-bottom: 24px;
|
|
}
|
|
.onboarding-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
.onboarding-header h3 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.onboarding-pct {
|
|
font-size: 0.85rem;
|
|
color: var(--text-secondary, #666);
|
|
font-weight: 400;
|
|
}
|
|
.onboarding-chevron {
|
|
transition: transform 0.2s;
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
.onboarding-chevron.collapsed {
|
|
transform: rotate(-90deg);
|
|
}
|
|
.onboarding-progress {
|
|
height: 6px;
|
|
background: var(--border, #e5e7eb);
|
|
border-radius: 3px;
|
|
margin: 12px 0 0;
|
|
overflow: hidden;
|
|
}
|
|
.onboarding-progress-fill {
|
|
height: 100%;
|
|
border-radius: 3px;
|
|
transition: width 0.4s ease;
|
|
}
|
|
.onboarding-steps {
|
|
margin-top: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0;
|
|
}
|
|
.onboarding-step {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
padding: 10px 0;
|
|
position: relative;
|
|
}
|
|
.onboarding-step:not(:last-child)::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 11px;
|
|
top: 34px;
|
|
bottom: -2px;
|
|
width: 2px;
|
|
background: var(--border, #e5e7eb);
|
|
}
|
|
.onboarding-step.done:not(:last-child)::after {
|
|
background: #22c55e;
|
|
}
|
|
.step-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
font-size: 12px;
|
|
z-index: 1;
|
|
}
|
|
.step-icon.done {
|
|
background: #22c55e;
|
|
color: #fff;
|
|
}
|
|
.step-icon.pending {
|
|
background: var(--border, #e5e7eb);
|
|
color: var(--text-secondary, #999);
|
|
}
|
|
.step-content {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.step-label {
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
color: var(--text-primary, #333);
|
|
}
|
|
.step-label.done {
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
.step-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 2px;
|
|
}
|
|
.step-badge {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
padding: 1px 8px;
|
|
border-radius: 10px;
|
|
}
|
|
.step-badge.auto { background: #f3f4f6; color: #6b7280; }
|
|
.step-badge.member { background: #dbeafe; color: #1e40af; }
|
|
.step-badge.office { background: #fef3c7; color: #92400e; }
|
|
.step-badge.both { background: #ede9fe; color: #6d28d9; }
|
|
.step-action {
|
|
font-size: 0.8rem;
|
|
color: var(--primary, #0066cc);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
.step-action:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* Forum topic item */
|
|
.topic-category-badge {
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
background: var(--bg-secondary, #f3f4f6);
|
|
color: var(--text-secondary, #666);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="dashboard-container">
|
|
<!-- Header -->
|
|
<div class="dashboard-header">
|
|
<div>
|
|
<h2>
|
|
<svg width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
|
|
</svg>
|
|
Panel użytkownika
|
|
</h2>
|
|
<p style="color: var(--text-secondary); margin: 8px 0 0 0;">
|
|
Witaj, <strong>{{ current_user.name }}</strong>!
|
|
{% if current_user.can_access_admin_panel() %}
|
|
<span class="badge-admin">Administrator</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<a href="{{ url_for('logout') }}" class="btn btn-outline">
|
|
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="margin-right: 4px;">
|
|
<path d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
|
</svg>
|
|
Wyloguj
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Row -->
|
|
<div class="stats-row">
|
|
{% if user_companies|length == 1 and user_companies[0].company %}
|
|
<a href="{{ url_for('company_detail', company_id=user_companies[0].company.id) }}" class="dashboard-card" style="text-decoration: none; color: inherit; cursor: pointer; transition: transform 0.15s, box-shadow 0.15s;" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='var(--shadow-lg, 0 4px 12px rgba(0,0,0,0.1))'" onmouseout="this.style.transform='';this.style.boxShadow=''">
|
|
{% elif user_companies|length > 1 %}
|
|
<div class="dashboard-card" style="cursor: pointer; transition: transform 0.15s, box-shadow 0.15s;" onclick="document.getElementById('companyPickerModal').classList.add('active')" onmouseover="this.style.transform='translateY(-2px)';this.style.boxShadow='var(--shadow-lg, 0 4px 12px rgba(0,0,0,0.1))'" onmouseout="this.style.transform='';this.style.boxShadow=''">
|
|
{% else %}
|
|
<div class="dashboard-card">
|
|
{% endif %}
|
|
<div class="stat-card-body">
|
|
<div class="stat-icon bg-primary" style="margin: 0 auto var(--spacing-sm, 8px);">
|
|
<svg width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
</div>
|
|
<h5>{{ 'Twoje firmy' if user_companies|length > 1 else 'Twoja firma' }}</h5>
|
|
{% if user_companies %}
|
|
<h3 style="color: var(--primary); font-size: 1rem;">{{ user_companies|length }}</h3>
|
|
{% else %}
|
|
<h3 style="color: var(--text-secondary); font-size: 1rem;">Brak</h3>
|
|
{% endif %}
|
|
<small>{{ 'Profile firmowe' if user_companies|length > 1 else 'Profil firmowy' }}</small>
|
|
</div>
|
|
{% if user_companies|length == 1 and user_companies[0].company %}
|
|
</a>
|
|
{% else %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="dashboard-card">
|
|
<div class="stat-card-body">
|
|
<div class="stat-icon bg-success" style="margin: 0 auto var(--spacing-sm, 8px);">
|
|
<svg width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
|
</svg>
|
|
</div>
|
|
<h5>Wiadomości</h5>
|
|
<h3 style="color: #10b981;">{{ total_messages }}</h3>
|
|
<small>Rozmowy z AI</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-card">
|
|
<div class="stat-card-body">
|
|
<div class="stat-icon bg-warning" style="margin: 0 auto var(--spacing-sm, 8px);">
|
|
<svg width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"/>
|
|
</svg>
|
|
</div>
|
|
<h5>Powiadomienia</h5>
|
|
<h3 style="color: #f59e0b;">{{ unread_notifications }}</h3>
|
|
<small>Nieprzeczytane</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-card">
|
|
<div class="stat-card-body">
|
|
<div class="stat-icon bg-info" style="margin: 0 auto var(--spacing-sm, 8px);">
|
|
<svg width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path 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>
|
|
</div>
|
|
<h5>Wydarzenia</h5>
|
|
<h3 style="color: #06b6d4;">{{ upcoming_events_count }}</h3>
|
|
<small>Nadchodzące</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# User's companies section — prominent cards #}
|
|
{% if user_companies %}
|
|
<div style="margin-bottom: var(--spacing-xl, 24px);">
|
|
<h3 style="margin: 0 0 var(--spacing-md, 16px) 0; font-size: 1.25rem; display: flex; align-items: center; gap: 8px;">
|
|
<svg width="22" height="22" fill="none" stroke="var(--primary, #0066cc)" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
{{ 'Twoje firmy' if user_companies|length > 1 else 'Twoja firma' }}
|
|
</h3>
|
|
{% for uc in user_companies %}
|
|
{% if uc.company %}
|
|
<div class="dashboard-card" style="margin-bottom: var(--spacing-md, 16px); border-left: 4px solid var(--primary, #0066cc);">
|
|
<div style="padding: var(--spacing-lg, 20px);">
|
|
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--spacing-sm, 8px);">
|
|
<div>
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-sm, 8px); margin-bottom: 4px;">
|
|
<span style="font-size: 1.1rem; font-weight: 600;">{{ uc.company.name }}</span>
|
|
{% if uc.is_primary and user_companies|length > 1 %}
|
|
<span style="background: var(--primary); color: white; font-size: 0.7rem; padding: 2px 8px; border-radius: 4px;">Główna</span>
|
|
{% endif %}
|
|
</div>
|
|
<span style="color: var(--text-secondary); font-size: 0.85rem;">
|
|
{% if uc.role == 'MANAGER' %}Zarządzający{% elif uc.role == 'EMPLOYEE' %}Pracownik{% elif uc.role == 'VIEWER' %}Podgląd{% endif %}
|
|
{% if uc.company.category %} · {{ uc.company.category.name }}{% endif %}
|
|
</span>
|
|
</div>
|
|
<div style="display: flex; gap: var(--spacing-sm, 8px);">
|
|
<a href="{{ url_for('public.company_detail', company_id=uc.company_id) }}" class="btn btn-sm btn-outline" style="padding: 6px 14px;">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -2px; margin-right: 4px;"><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
|
Zobacz profil
|
|
</a>
|
|
{% if uc.role == 'MANAGER' or current_user.is_admin %}
|
|
<a href="{{ url_for('public.company_edit', company_id=uc.company_id) }}" class="btn btn-sm btn-primary" style="padding: 6px 14px;">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -2px; margin-right: 4px;"><path 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>
|
|
Edytuj profil
|
|
</a>
|
|
{% elif uc.role == 'EMPLOYEE' %}
|
|
<button type="button" class="btn btn-sm btn-primary" style="padding: 6px 14px;"
|
|
onclick="showEditPermissionModal({{ uc.company_id }})">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -2px; margin-right: 4px;"><path 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>
|
|
Edytuj profil
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Onboarding progress widget #}
|
|
{% if onboarding and not onboarding.all_complete %}
|
|
<div class="onboarding-widget">
|
|
<div class="onboarding-header" onclick="toggleOnboarding()">
|
|
<h3>
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
|
Postęp profilu firmy
|
|
<span class="onboarding-pct">{{ onboarding.completed }}/{{ onboarding.total }}</span>
|
|
</h3>
|
|
<svg class="onboarding-chevron" id="onboardingChevron" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>
|
|
</div>
|
|
<div class="onboarding-progress">
|
|
<div class="onboarding-progress-fill" style="width: {{ onboarding.percentage }}%; background: {% if onboarding.percentage == 100 %}#22c55e{% elif onboarding.percentage >= 50 %}#3b82f6{% else %}#f59e0b{% endif %};"></div>
|
|
</div>
|
|
<div class="onboarding-steps" id="onboardingSteps">
|
|
{% for step in onboarding.steps %}
|
|
<div class="onboarding-step {{ 'done' if step.completed else '' }}">
|
|
<div class="step-icon {{ 'done' if step.completed else 'pending' }}">
|
|
{% if step.completed %}
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
|
|
{% else %}
|
|
{{ loop.index }}
|
|
{% endif %}
|
|
</div>
|
|
<div class="step-content">
|
|
<div class="step-label {{ 'done' if step.completed else '' }}">{{ step.label }}</div>
|
|
<div class="step-meta">
|
|
<span class="step-badge {{ step.responsible }}">{{ step.responsible_label }}</span>
|
|
{% if not step.completed and step.action_url %}
|
|
<a href="{{ step.action_url }}" class="step-action">Uzupełnij →</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% elif onboarding and onboarding.all_complete %}
|
|
<div class="onboarding-widget" style="border-left: 4px solid #22c55e;">
|
|
<div class="onboarding-header" onclick="toggleOnboarding()">
|
|
<h3 style="color: #15803d;">
|
|
<svg width="18" height="18" fill="none" stroke="#22c55e" stroke-width="2" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
Profil firmy kompletny!
|
|
</h3>
|
|
<svg class="onboarding-chevron collapsed" id="onboardingChevron" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 9l6 6 6-6"/></svg>
|
|
</div>
|
|
<div class="onboarding-progress">
|
|
<div class="onboarding-progress-fill" style="width: 100%; background: #22c55e;"></div>
|
|
</div>
|
|
<div class="onboarding-steps" id="onboardingSteps" style="display: none;">
|
|
{% for step in onboarding.steps %}
|
|
<div class="onboarding-step done">
|
|
<div class="step-icon done">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7"/></svg>
|
|
</div>
|
|
<div class="step-content">
|
|
<div class="step-label done">{{ step.label }}</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# Membership CTA for users without company #}
|
|
{% if not user_companies %}
|
|
{% if has_pending_application %}
|
|
<div class="membership-status">
|
|
<div class="membership-status-content">
|
|
<h4>Twoja deklaracja członkowska jest w trakcie rozpatrywania</h4>
|
|
<p>Status: {{ pending_application.status_label if pending_application else 'Wysłane' }}</p>
|
|
</div>
|
|
<a href="{{ url_for('membership.status') }}" class="btn btn-outline">
|
|
Zobacz szczegóły →
|
|
</a>
|
|
</div>
|
|
{% elif has_draft_application %}
|
|
<div class="membership-status">
|
|
<div class="membership-status-content">
|
|
<h4>Masz niedokończoną deklarację członkowską</h4>
|
|
<p>Kontynuuj wypełnianie formularza</p>
|
|
</div>
|
|
<a href="{{ url_for('membership.apply') }}" class="btn btn-primary">
|
|
Kontynuuj →
|
|
</a>
|
|
</div>
|
|
{% else %}
|
|
<div class="membership-cta">
|
|
<div class="membership-cta-content">
|
|
<h3>Dołącz do Izby Przedsiębiorców NORDA</h3>
|
|
<p>Wypełnij deklarację członkowską online i zyskaj dostęp do pełnych funkcji portalu</p>
|
|
</div>
|
|
<a href="{{ url_for('membership.apply') }}" class="btn-cta">
|
|
Złóż deklarację →
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
{% elif current_user.company and not current_user.company.nip %}
|
|
{# User has company but missing NIP - suggest data update #}
|
|
<div class="data-update-cta">
|
|
<div class="data-update-cta-content">
|
|
<h4>Uzupełnij dane firmy</h4>
|
|
<p>Twoja firma nie ma jeszcze NIP w systemie. Uzupełnij dane, aby pobrać informacje z rejestru.</p>
|
|
</div>
|
|
<a href="{{ url_for('membership.data_request') }}" class="btn btn-warning">
|
|
Uzupełnij NIP →
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if current_user.can_access_admin_panel() %}
|
|
<!-- Admin Section -->
|
|
<div class="admin-section-highlight">
|
|
<h4 style="margin: 0 0 var(--spacing-lg, 20px) 0; display: flex; align-items: center; gap: 8px;">
|
|
<svg width="24" height="24" fill="none" stroke="#f59e0b" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
|
</svg>
|
|
Funkcje administratora
|
|
</h4>
|
|
|
|
<div class="admin-functions-grid">
|
|
<a href="{{ url_for('admin.admin_recommendations') }}" class="admin-function-card">
|
|
<svg fill="none" stroke="#f59e0b" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/>
|
|
</svg>
|
|
<h6>Rekomendacje</h6>
|
|
<span class="btn btn-sm btn-warning">Moderuj</span>
|
|
</a>
|
|
|
|
<a href="{{ url_for('admin.admin_calendar') }}" class="admin-function-card">
|
|
<svg fill="none" stroke="#06b6d4" stroke-width="2" viewBox="0 0 24 24">
|
|
<path 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>
|
|
<h6>Kalendarz</h6>
|
|
<span class="btn btn-sm btn-info">Zarządzaj</span>
|
|
</a>
|
|
|
|
<a href="{{ url_for('admin.admin_social_media') }}" class="admin-function-card">
|
|
<svg fill="none" stroke="#8b5cf6" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
|
|
</svg>
|
|
<h6>Social Media</h6>
|
|
<span class="btn btn-sm" style="background: #8b5cf6; color: white;">Edytuj</span>
|
|
</a>
|
|
|
|
<a href="{{ url_for('chat_analytics') }}" class="admin-function-card">
|
|
<svg fill="none" stroke="#10b981" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
<h6>Analityka</h6>
|
|
<span class="btn btn-sm btn-success">Statystyki</span>
|
|
</a>
|
|
|
|
<a href="{{ url_for('debug_panel') }}" class="admin-function-card">
|
|
<svg fill="none" stroke="#ef4444" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
|
</svg>
|
|
<h6>Debug Panel</h6>
|
|
<span class="btn btn-sm btn-danger">Logi</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Row 1: Events + Announcements -->
|
|
<div class="quick-actions-row">
|
|
<!-- Upcoming Events -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-cyan">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path 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>
|
|
Nadchodzące wydarzenia
|
|
<a href="{{ url_for('calendar.calendar_index') }}" class="card-header-link">Wszystkie →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if upcoming_events %}
|
|
{% for event in upcoming_events %}
|
|
<a href="{{ url_for('calendar.calendar_event', event_id=event.id) }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ event.title }}</div>
|
|
<div class="conversation-meta">
|
|
{{ event.event_date.strftime('%d.%m.%Y') }}
|
|
{% if event.time_start %} o {{ event.time_start.strftime('%H:%M') }}{% endif %}
|
|
{% if event.location %} · {{ event.location[:30] }}{% endif %}
|
|
{% if event_attendee_counts.get(event.id, 0) > 0 %}
|
|
· <span style="color: #0891b2;">{{ event_attendee_counts[event.id] }}
|
|
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -1px;"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87m-4-12a4 4 0 0 1 0 7.75"/></svg></span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
{% if event.id in user_event_ids %}
|
|
<span class="badge-rsvp">Zapisano</span>
|
|
{% else %}
|
|
<span class="event-date-badge">{{ event.event_date.strftime('%d.%m') }}</span>
|
|
{% endif %}
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path 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>
|
|
<p>Brak nadchodzących wydarzeń</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Announcements -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-amber">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/>
|
|
</svg>
|
|
Ogłoszenia
|
|
<a href="{{ url_for('announcements_list') }}" class="card-header-link">Wszystkie →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if recent_announcements %}
|
|
{% for ann in recent_announcements %}
|
|
<a href="{{ url_for('announcement_detail', slug=ann.slug) }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ ann.title[:50] }}{% if ann.title|length > 50 %}...{% endif %}</div>
|
|
<div class="conversation-meta">
|
|
{{ ann.category_label }}
|
|
{% if ann.published_at %} · {{ ann.published_at.strftime('%d.%m.%Y') }}{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if ann.is_pinned %}
|
|
<span class="event-date-badge" style="color: #f59e0b;">Przypięte</span>
|
|
{% endif %}
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6"/>
|
|
</svg>
|
|
<p>Brak ogłoszeń</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 2: Forum + Classifieds -->
|
|
<div class="quick-actions-row">
|
|
<!-- Forum Topics -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-blue">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
|
|
</svg>
|
|
Forum
|
|
<a href="{{ url_for('forum_index') }}" class="card-header-link">Wszystkie →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if recent_forum_topics %}
|
|
{% for topic in recent_forum_topics %}
|
|
<a href="{{ url_for('forum_topic', topic_id=topic.id) }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ topic.title[:45] }}{% if topic.title|length > 45 %}...{% endif %}</div>
|
|
<div class="conversation-meta">
|
|
<span class="topic-category-badge">{{ topic.CATEGORY_LABELS.get(topic.category, topic.category) }}</span>
|
|
· {{ topic.updated_at.strftime('%d.%m.%Y') }}
|
|
</div>
|
|
</div>
|
|
<span class="badge-messages">{{ topic.views_count }} views</span>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a2 2 0 01-2-2V6a2 2 0 012-2h6a2 2 0 012 2v2"/>
|
|
</svg>
|
|
<p>Brak tematów na forum</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Classifieds B2B -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-green">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
|
|
</svg>
|
|
Tablica B2B
|
|
<a href="{{ url_for('classifieds.classifieds_index') }}" class="card-header-link">Wszystkie →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if recent_classifieds %}
|
|
{% for cl in recent_classifieds %}
|
|
<a href="{{ url_for('classifieds.classifieds_view', classified_id=cl.id) }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ cl.title[:45] }}{% if cl.title|length > 45 %}...{% endif %}</div>
|
|
<div class="conversation-meta">
|
|
{% if cl.company %}{{ cl.company.name[:25] }}{% endif %}
|
|
· {{ cl.created_at.strftime('%d.%m.%Y') }}
|
|
</div>
|
|
</div>
|
|
<span class="badge-{{ cl.listing_type }}">{{ 'Szukam' if cl.listing_type == 'szukam' else 'Oferuję' }}</span>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/>
|
|
</svg>
|
|
<p>Brak ogłoszeń B2B</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 3: New Companies + AI Conversations -->
|
|
<div class="quick-actions-row">
|
|
<!-- New Companies -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-purple">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
Nowi członkowie
|
|
<a href="{{ url_for('index') }}" class="card-header-link">Katalog →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if new_companies %}
|
|
{% for company in new_companies %}
|
|
<a href="{{ url_for('company_detail_by_slug', slug=company.slug) }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ company.name[:40] }}{% if company.name|length > 40 %}...{% endif %}</div>
|
|
<div class="conversation-meta">
|
|
{% if company.address_city %}{{ company.address_city }}{% endif %}
|
|
{% if company.category %} · {{ company.category.name }}{% endif %}
|
|
</div>
|
|
</div>
|
|
<span class="event-date-badge">{{ company.created_at.strftime('%d.%m') if company.created_at else '' }}</span>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16"/>
|
|
</svg>
|
|
<p>Brak nowych firm</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AI Conversations (moved from existing) -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-primary">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
|
</svg>
|
|
Rozmowy z NordaGPT
|
|
<a href="{{ url_for('chat') }}" class="card-header-link">Chat →</a>
|
|
</div>
|
|
<div class="card-body" style="padding: 0;">
|
|
{% if conversations %}
|
|
{% for conv in conversations[:5] %}
|
|
<a href="{{ url_for('chat') }}?conversation_id={{ conv.id }}" class="conversation-item">
|
|
<div>
|
|
<div class="conversation-title">{{ conv.title }}</div>
|
|
<div class="conversation-meta">{{ conv.updated_at.strftime('%d.%m.%Y %H:%M') }}</div>
|
|
</div>
|
|
<span class="badge-messages">{{ conv.message_count }} msg</span>
|
|
</a>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="36" height="36" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
|
</svg>
|
|
<p>Brak rozmów</p>
|
|
<a href="{{ url_for('chat') }}" class="btn btn-primary btn-sm">Rozpocznij chat</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 4: Quick Actions + Profile -->
|
|
<div class="quick-actions-row">
|
|
<!-- Quick Actions (existing) -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-success">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
|
</svg>
|
|
Szybkie akcje
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm, 8px);">
|
|
<a href="{{ url_for('index') }}" class="btn btn-outline" style="justify-content: flex-start; gap: 8px;">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
|
</svg>
|
|
Katalog firm
|
|
</a>
|
|
<a href="{{ url_for('chat') }}" class="btn btn-outline" style="justify-content: flex-start; gap: 8px;">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
|
</svg>
|
|
NordaGPT
|
|
</a>
|
|
<a href="{{ url_for('calendar.calendar_index') }}" class="btn btn-outline" style="justify-content: flex-start; gap: 8px;">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path 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>
|
|
Kalendarz
|
|
</a>
|
|
<a href="{{ url_for('forum_index') }}" class="btn btn-outline" style="justify-content: flex-start; gap: 8px;">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
|
|
</svg>
|
|
Forum
|
|
</a>
|
|
<a href="{{ url_for('messages_inbox') }}" class="btn btn-outline" style="justify-content: flex-start; gap: 8px;">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
|
</svg>
|
|
Wiadomości
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profile -->
|
|
<div class="dashboard-card">
|
|
<div class="card-header-info">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
|
</svg>
|
|
Twój profil
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="display: flex; align-items: center; gap: var(--spacing-lg, 20px); flex-wrap: wrap;">
|
|
<div class="profile-avatar">
|
|
<svg width="40" height="40" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
|
</svg>
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<p style="margin: 0 0 4px 0;"><strong>Nazwa:</strong> {{ current_user.name }}</p>
|
|
<p style="margin: 0 0 4px 0;"><strong>Email:</strong> {{ current_user.email }}</p>
|
|
<p style="margin: 0;">
|
|
<strong>Rola:</strong>
|
|
{% if current_user.can_access_admin_panel() %}
|
|
<span class="badge-admin">Administrator</span>
|
|
{% else %}
|
|
<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem;">Użytkownik</span>
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Company Picker Modal (for users with multiple companies) -->
|
|
{% if user_companies|length > 1 %}
|
|
<div class="modal-overlay" id="companyPickerModal" onclick="if(event.target===this)this.classList.remove('active')">
|
|
<div class="modal" style="max-width: 480px; background: var(--surface, white); border-radius: var(--radius-lg, 12px); padding: var(--spacing-xl, 24px);">
|
|
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--spacing-lg, 20px);">
|
|
<h3 style="margin: 0; font-size: 1.15rem;">Wybierz firmę</h3>
|
|
<button type="button" onclick="document.getElementById('companyPickerModal').classList.remove('active')" style="background: none; border: none; cursor: pointer; padding: 4px; color: var(--text-secondary);">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm, 8px);">
|
|
{% for uc in user_companies %}
|
|
{% if uc.company %}
|
|
<a href="{{ url_for('company_detail', company_id=uc.company.id) }}" style="display: flex; align-items: center; gap: var(--spacing-md, 12px); padding: var(--spacing-md, 12px); background: var(--background, #f9fafb); border-radius: var(--radius, 8px); text-decoration: none; color: inherit; border: 2px solid transparent; transition: border-color 0.15s, background 0.15s;" onmouseover="this.style.borderColor='var(--primary, #0066cc)';this.style.background='white'" onmouseout="this.style.borderColor='transparent';this.style.background=''">
|
|
<div style="width: 44px; height: 44px; border-radius: 10px; background: var(--primary); color: white; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
|
|
<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
|
|
</div>
|
|
<div style="flex: 1; min-width: 0;">
|
|
<div style="font-weight: 600; margin-bottom: 2px;">{{ uc.company.name }}</div>
|
|
<div style="font-size: 0.8rem; color: var(--text-secondary);">
|
|
{% if uc.role == 'MANAGER' %}Zarządzający{% elif uc.role == 'EMPLOYEE' %}Pracownik{% elif uc.role == 'VIEWER' %}Podgląd{% endif %}
|
|
{% if uc.is_primary %} · Główna{% endif %}
|
|
</div>
|
|
</div>
|
|
<svg width="18" height="18" fill="none" stroke="var(--text-secondary)" stroke-width="2" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Edit Permission Modal (for employees) -->
|
|
<div class="modal-overlay" id="editPermissionModal">
|
|
<div class="modal" style="max-width: 480px; background: var(--surface, white); border-radius: var(--radius-lg, 12px); padding: var(--spacing-xl, 24px);">
|
|
<div style="text-align: center; margin-bottom: var(--spacing-lg, 20px);">
|
|
<div style="font-size: 3em; margin-bottom: var(--spacing-md, 16px);">🔒</div>
|
|
<h3 style="margin-bottom: var(--spacing-sm, 8px);">Edycja wymaga uprawnień</h3>
|
|
<p style="color: var(--text-secondary, #666);">
|
|
Edycja profilu firmy jest dostępna dla kadry zarządzającej.
|
|
Skontaktuj się z odpowiednią osobą:
|
|
</p>
|
|
</div>
|
|
<div id="editPermissionManagersList" style="margin-bottom: var(--spacing-lg, 20px);"></div>
|
|
<div style="text-align: center;">
|
|
<button type="button" class="btn btn-primary"
|
|
onclick="document.getElementById('editPermissionModal').classList.remove('active')">
|
|
Rozumiem
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.modal-overlay#editPermissionModal, .modal-overlay#companyPickerModal { 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#editPermissionModal.active, .modal-overlay#companyPickerModal.active { display: flex; }
|
|
</style>
|
|
|
|
<script>
|
|
const companyManagersMap = {{ company_managers_map | tojson }};
|
|
|
|
function showEditPermissionModal(companyId) {
|
|
const managers = companyManagersMap[companyId] || [];
|
|
const list = document.getElementById('editPermissionManagersList');
|
|
|
|
if (managers.length === 0) {
|
|
list.innerHTML = '<div style="text-align: center; padding: 12px; color: var(--text-secondary, #666);">' +
|
|
'Twoja firma nie ma przypisanej kadry zarządzającej.<br>' +
|
|
'Skontaktuj się z biurem izby: <a href="mailto:biuro@nordabiznes.pl" style="color: var(--primary, #0066cc);">biuro@nordabiznes.pl</a>' +
|
|
'</div>';
|
|
} else {
|
|
list.innerHTML = managers.map(function(m) {
|
|
return '<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--background, #f9fafb); border-radius: 8px; margin-bottom: 4px;">' +
|
|
'<svg width="20" height="20" fill="none" stroke="var(--primary, #0066cc)" stroke-width="2" viewBox="0 0 24 24">' +
|
|
'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>' +
|
|
'</svg>' +
|
|
'<div>' +
|
|
'<div style="font-weight: 600;">' + (m.name || 'Brak imienia') + '</div>' +
|
|
(m.email ? '<a href="mailto:' + m.email + '" style="font-size: 0.85rem; color: var(--primary, #0066cc);">' + m.email + '</a>' : '') +
|
|
'</div></div>';
|
|
}).join('');
|
|
}
|
|
document.getElementById('editPermissionModal').classList.add('active');
|
|
}
|
|
|
|
// Close on backdrop click
|
|
document.getElementById('editPermissionModal').addEventListener('click', function(e) {
|
|
if (e.target === this) this.classList.remove('active');
|
|
});
|
|
|
|
// Onboarding widget toggle
|
|
function toggleOnboarding() {
|
|
const steps = document.getElementById('onboardingSteps');
|
|
const chevron = document.getElementById('onboardingChevron');
|
|
if (!steps) return;
|
|
const hidden = steps.style.display === 'none';
|
|
steps.style.display = hidden ? '' : 'none';
|
|
chevron.classList.toggle('collapsed', !hidden);
|
|
try { localStorage.setItem('onboarding_collapsed', hidden ? '0' : '1'); } catch(e) {}
|
|
}
|
|
// Restore collapsed state
|
|
(function() {
|
|
try {
|
|
if (localStorage.getItem('onboarding_collapsed') === '1') {
|
|
const s = document.getElementById('onboardingSteps');
|
|
const c = document.getElementById('onboardingChevron');
|
|
if (s) { s.style.display = 'none'; }
|
|
if (c) { c.classList.add('collapsed'); }
|
|
}
|
|
} catch(e) {}
|
|
})();
|
|
</script>
|
|
{% endblock %}
|