nordabiz/templates/company_edit.html
Maciej Pienczyn 10eec63c2f
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
fix(company): fix logo upload saving to boolean column instead of file
Logo upload in company edit was writing the file path to `logo_dark_bg`
(a Boolean column), causing TypeError and rolling back ALL profile changes.
Now saves to static/img/companies/{slug}.{ext} where company_detail expects it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:34:40 +02:00

1907 lines
92 KiB
HTML

{% extends "base.html" %}
{% block title %}Edytuj profil — {{ company.name }}{% endblock %}
{% block head_extra %}
<link href="{{ url_for('static', filename='css/quill.snow.css') }}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
{% endblock %}
{% block extra_css %}
<style>
.ce-container {
max-width: 1400px;
margin: 0 auto;
padding: var(--spacing-md) var(--spacing-lg);
}
/* Split layout: editor + preview */
.ce-layout {
display: grid;
grid-template-columns: 1fr 380px;
gap: var(--spacing-lg);
align-items: start;
}
/* Preview panel */
.ce-preview {
position: sticky;
top: 80px;
max-height: calc(100vh - 100px);
overflow-y: auto;
background: var(--surface, #fff);
border-radius: var(--radius-lg, 0.75rem);
box-shadow: var(--shadow);
padding: var(--spacing-lg);
}
.ce-preview-title {
font-size: var(--font-size-sm);
font-weight: 600;
color: white;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: calc(-1 * var(--spacing-lg)) calc(-1 * var(--spacing-lg)) var(--spacing-md);
padding: var(--spacing-md) var(--spacing-lg);
background: linear-gradient(135deg, var(--primary, #2E4872), #4a6999);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.ce-preview-title svg { width: 16px; height: 16px; }
/* Preview sections */
.preview-company-name {
font-size: var(--font-size-xl, 1.25rem);
font-weight: 700;
color: var(--text-primary, #303030);
margin-bottom: var(--spacing-sm);
}
.preview-short-desc {
font-size: var(--font-size-sm);
color: var(--text-secondary, #464646);
margin-bottom: var(--spacing-lg);
font-style: italic;
}
.preview-section {
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--border, #e0e4eb);
}
.preview-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.preview-section-label {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-primary, #303030);
margin-bottom: var(--spacing-sm);
display: flex;
align-items: center;
gap: 6px;
}
.preview-section-label::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
/* Preview dot colors matching tabs */
#previewDescriptionSection .preview-section-label::before,
#previewHistorySection .preview-section-label::before,
#previewValuesSection .preview-section-label::before { background: #6366f1; }
#previewServicesSection .preview-section-label::before { background: #f59e0b; }
#previewContactSection .preview-section-label::before { background: #10b981; }
#previewSocialSection .preview-section-label::before { background: #ec4899; }
.preview-section-content {
font-size: var(--font-size-sm);
color: var(--text-primary, #303030);
line-height: 1.7;
}
.preview-section-content p { margin-bottom: var(--spacing-sm); }
.preview-section-content ul, .preview-section-content ol {
padding-left: var(--spacing-lg);
margin-bottom: var(--spacing-sm);
}
.preview-section-content li { margin-bottom: 2px; }
.preview-section-content a { color: var(--primary, #2E4872); }
.preview-section-content strong { font-weight: 600; }
.preview-empty {
color: var(--text-secondary, #464646);
font-style: italic;
font-size: var(--font-size-sm);
opacity: 0.6;
}
/* Preview contact and social items */
.preview-contact-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-xs);
}
.preview-contact-item svg { width: 14px; height: 14px; color: var(--primary); flex-shrink: 0; }
.preview-social-item {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: var(--font-size-sm);
color: var(--primary, #2E4872);
margin-right: var(--spacing-md);
margin-bottom: var(--spacing-xs);
}
/* Mobile preview button & bottom sheet */
.ce-preview-mobile-btn {
display: none;
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
z-index: 900;
padding: 12px 20px;
background: var(--primary, #2E4872);
color: white;
border: none;
border-radius: 50px;
font-size: var(--font-size-sm);
font-weight: 600;
font-family: var(--font-family);
cursor: pointer;
box-shadow: 0 4px 12px rgba(46, 72, 114, 0.3);
align-items: center;
gap: var(--spacing-sm);
transition: all 0.2s;
}
.ce-preview-mobile-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(46, 72, 114, 0.4);
}
.ce-preview-mobile-btn svg { width: 18px; height: 18px; }
.ce-preview-sheet-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 1100;
}
.ce-preview-sheet-overlay.active { display: block; }
.ce-preview-sheet {
position: fixed;
bottom: 0; left: 0; right: 0;
max-height: 80vh;
background: var(--surface, #fff);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
z-index: 1200;
overflow-y: auto;
padding: var(--spacing-lg);
transform: translateY(100%);
transition: transform 0.3s ease;
}
.ce-preview-sheet-overlay.active .ce-preview-sheet {
transform: translateY(0);
}
.ce-preview-sheet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border, #e0e4eb);
}
.ce-preview-sheet-close {
background: none;
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius);
padding: 6px 12px;
cursor: pointer;
font-size: var(--font-size-sm);
font-family: var(--font-family);
color: var(--text-secondary);
}
/* Header card */
.ce-header {
background: var(--surface, #fff);
border-radius: var(--radius-lg, 0.75rem);
padding: var(--spacing-xl);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.ce-header-icon {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary, #2E4872), var(--primary-light, #4a6999));
color: white;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.ce-header-icon svg { width: 28px; height: 28px; }
.ce-header-info h1 {
font-size: var(--font-size-2xl, 1.5rem);
color: var(--text-primary, #303030);
margin: 0 0 var(--spacing-xs);
}
.ce-header-info p {
color: var(--text-secondary, #464646);
margin: 0;
font-size: var(--font-size-sm);
}
.ce-header-actions {
margin-left: auto;
}
/* Main form card */
.ce-card {
background: var(--surface, #fff);
border-radius: var(--radius-lg, 0.75rem);
box-shadow: var(--shadow);
overflow: hidden;
}
/* Tabs — colored per section */
.ce-tabs {
display: flex;
background: var(--background, #EDF0F5);
border-bottom: 1px solid var(--border, #e0e4eb);
overflow-x: auto;
}
.ce-tab {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-lg);
border: none;
background: none;
cursor: pointer;
font-size: var(--font-size-sm);
font-family: var(--font-family);
color: var(--text-secondary, #464646);
white-space: nowrap;
transition: all 0.2s;
border-bottom: 3px solid transparent;
margin-bottom: -1px;
font-weight: 500;
}
.ce-tab svg { width: 18px; height: 18px; flex-shrink: 0; opacity: 0.5; }
.ce-tab:hover {
color: var(--text-primary, #303030);
background: rgba(255,255,255,0.5);
}
.ce-tab:hover svg { opacity: 0.8; }
.ce-tab.active { background: var(--surface, #fff); font-weight: 600; }
.ce-tab.active svg { opacity: 1; }
/* Per-tab colors */
.ce-tab[data-tab="description"].active { color: #6366f1; border-bottom-color: #6366f1; }
.ce-tab[data-tab="description"].active svg { color: #6366f1; }
.ce-tab[data-tab="services"].active { color: #f59e0b; border-bottom-color: #f59e0b; }
.ce-tab[data-tab="services"].active svg { color: #f59e0b; }
.ce-tab[data-tab="contacts"].active { color: #10b981; border-bottom-color: #10b981; }
.ce-tab[data-tab="contacts"].active svg { color: #10b981; }
.ce-tab[data-tab="social"].active { color: #ec4899; border-bottom-color: #ec4899; }
.ce-tab[data-tab="social"].active svg { color: #ec4899; }
.ce-tab[data-tab="visibility"].active { color: #8b5cf6; border-bottom-color: #8b5cf6; }
.ce-tab[data-tab="visibility"].active svg { color: #8b5cf6; }
.ce-tab[data-tab="team"].active { color: #0ea5e9; border-bottom-color: #0ea5e9; }
.ce-tab[data-tab="team"].active svg { color: #0ea5e9; }
/* Team management */
.team-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-lg); flex-wrap: wrap; gap: var(--spacing-sm); }
.team-header h3 { margin: 0; font-size: var(--font-size-lg); color: var(--text-primary); }
.team-add-form { background: var(--surface-alt, #f8fafc); border-radius: var(--radius); padding: var(--spacing-lg); margin-bottom: var(--spacing-lg); border: 1px solid var(--border-light, #e2e8f0); }
.team-add-form .form-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: var(--spacing-md); align-items: end; }
.team-add-form .form-row-bottom { display: grid; grid-template-columns: 1fr auto; gap: var(--spacing-md); align-items: end; margin-top: var(--spacing-md); }
.team-add-form label { display: block; font-size: var(--font-size-sm); font-weight: 500; color: var(--text-secondary); margin-bottom: 4px; }
.team-member-card { display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-md) var(--spacing-lg); border-bottom: 1px solid var(--border-light, #e2e8f0); flex-wrap: wrap; }
.team-member-card:last-child { border-bottom: none; }
.team-member-avatar { width: 42px; height: 42px; border-radius: 50%; background: var(--primary, #2E4872); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 15px; flex-shrink: 0; }
.team-member-info { flex: 1; min-width: 150px; }
.team-member-name { font-weight: 600; color: var(--text-primary); }
.team-member-email { font-size: var(--font-size-sm); color: var(--text-secondary); }
.team-member-actions { display: flex; align-items: center; gap: var(--spacing-sm); flex-wrap: wrap; }
.team-role-select { padding: 6px 10px; border-radius: var(--radius); border: 1px solid var(--border-light, #e2e8f0); font-size: var(--font-size-sm); background: var(--surface, #fff); }
.team-role-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.team-role-badge.manager { background: #dbeafe; color: #1e40af; }
.team-role-badge.employee { background: #dcfce7; color: #166534; }
.team-role-badge.viewer { background: #f3e8ff; color: #6b21a8; }
.team-btn-remove { padding: 6px 12px; border-radius: var(--radius); border: 1px solid #fecaca; background: #fff; color: #dc2626; font-size: var(--font-size-sm); cursor: pointer; transition: all 0.2s; }
.team-btn-remove:hover { background: #fef2f2; border-color: #dc2626; }
.team-perms-toggle { margin-top: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); background: var(--surface-alt, #f8fafc); border-radius: var(--radius); }
.team-perms-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--spacing-xs); }
.team-perm-item { display: flex; align-items: center; gap: 8px; font-size: var(--font-size-sm); padding: 4px 0; }
.team-perm-item input[type="checkbox"] { accent-color: var(--primary, #2E4872); width: 16px; height: 16px; flex-shrink: 0; }
.team-perm-hint { display: block; font-size: 11px; color: var(--text-secondary, #94a3b8); line-height: 1.3; margin-top: 1px; }
.team-member-status { font-size: 11px; margin-top: 2px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.team-status-active { color: #16a34a; }
.team-status-pending { color: #d97706; }
.team-btn-resend { font-size: 11px; padding: 2px 8px; border-radius: 4px; border: 1px solid #d97706; background: #fffbeb; color: #92400e; cursor: pointer; transition: all 0.2s; }
.team-btn-resend:hover { background: #fef3c7; }
.team-empty { text-align: center; padding: var(--spacing-xl); color: var(--text-secondary); }
.team-loading { text-align: center; padding: var(--spacing-xl); color: var(--text-secondary); }
.team-you-badge { font-size: 11px; background: #e0f2fe; color: #0369a1; padding: 1px 6px; border-radius: 8px; margin-left: 6px; }
@media (max-width: 768px) {
.team-add-form .form-row { grid-template-columns: 1fr; }
.team-add-form .form-row-bottom { grid-template-columns: 1fr; }
.team-member-card { padding: var(--spacing-md); }
.team-member-actions { width: 100%; justify-content: flex-end; }
.team-perms-grid { grid-template-columns: 1fr; }
}
/* Tab content */
.ce-tab-content { display: none; padding: var(--spacing-xl); }
.ce-tab-content.active { display: block; }
/* Section headers inside tabs */
.ce-section-title {
font-size: var(--font-size-lg, 1.125rem);
font-weight: 600;
color: var(--text-primary, #303030);
margin: 0 0 var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
background: linear-gradient(90deg, rgba(16, 185, 129, 0.08), transparent);
border-left: 3px solid #10b981;
border-radius: 0 var(--radius) var(--radius) 0;
display: block;
}
/* Form overrides for this page */
.ce-card .form-group { margin-bottom: var(--spacing-lg); }
.ce-card .form-label {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
margin-bottom: var(--spacing-sm);
color: var(--text-primary, #303030);
font-size: var(--font-size-sm);
}
.ce-card .form-label::before {
content: '';
width: 3px;
height: 14px;
border-radius: 2px;
flex-shrink: 0;
}
/* Label accent colors per tab */
#tab-description .form-label::before { background: #6366f1; }
#tab-services .form-label::before { background: #f59e0b; }
#tab-contacts .form-label::before { background: #10b981; }
#tab-social .form-label::before { background: #ec4899; }
.ce-card .form-input {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius, 0.5rem);
font-size: var(--font-size-base, 1rem);
font-family: var(--font-family);
transition: var(--transition);
background: var(--surface, #fff);
color: var(--text-primary, #303030);
box-sizing: border-box;
}
.ce-card .form-input:focus {
outline: none;
border-color: var(--primary, #2E4872);
box-shadow: 0 0 0 3px rgba(46, 72, 114, 0.1);
}
.ce-card .form-input:disabled {
background: var(--background, #EDF0F5);
color: var(--text-secondary);
cursor: not-allowed;
}
.ce-card select.form-input {
cursor: pointer;
appearance: auto;
}
.ce-card textarea.form-input {
resize: vertical;
min-height: 100px;
line-height: 1.6;
}
.ce-card .form-help {
font-size: 0.8rem;
color: var(--text-secondary, #464646);
margin-top: var(--spacing-xs);
padding: 4px 10px;
background: #f8fafc;
border-radius: var(--radius, 0.5rem);
border-left: 2px solid var(--border, #e0e4eb);
}
.ce-card .form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
}
/* Char counter */
.char-counter {
font-size: var(--font-size-sm);
color: var(--text-secondary);
text-align: right;
margin-top: var(--spacing-xs);
}
.char-counter.warning { color: var(--warning, #f59e0b); }
.char-counter.error { color: var(--error, #ef4444); }
/* Fieldset */
fieldset { border: none; padding: 0; margin: 0; }
fieldset[disabled] { opacity: 0.5; pointer-events: none; }
/* Permission warning */
.ce-no-permission {
background: linear-gradient(135deg, #fef3c7, #fde68a20);
border: 1px solid #fde68a;
border-left: 3px solid #f59e0b;
border-radius: var(--radius);
padding: var(--spacing-md);
margin-bottom: var(--spacing-lg);
font-size: var(--font-size-sm);
color: #92400e;
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-weight: 500;
}
.ce-no-permission svg { width: 20px; height: 20px; flex-shrink: 0; color: #f59e0b; }
/* Info box */
.ce-info-box {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
border: 1px solid #bae6fd;
border-left: 3px solid #0ea5e9;
border-radius: var(--radius);
padding: var(--spacing-md);
margin-bottom: var(--spacing-lg);
font-size: var(--font-size-sm);
color: #0369a1;
}
.ce-info-box strong { display: block; margin-bottom: var(--spacing-xs); }
.ce-info-box ul { margin: var(--spacing-xs) 0 0; padding-left: var(--spacing-lg); }
.ce-info-box li { margin-bottom: 2px; }
.ce-info-box a { color: #0369a1; font-weight: 500; }
/* Dynamic rows (contacts, social) */
.ce-dynamic-section {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 2px dashed var(--border, #e0e4eb);
}
.ce-dynamic-title {
font-size: var(--font-size-base);
font-weight: 600;
color: var(--text-primary);
margin: 0 0 var(--spacing-xs);
}
.ce-dynamic-help {
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin: 0 0 var(--spacing-md);
}
.contact-row, .social-row {
display: flex;
gap: var(--spacing-sm);
align-items: center;
margin-bottom: var(--spacing-sm);
padding: var(--spacing-sm);
background: var(--background, #EDF0F5);
border-radius: var(--radius);
border: 1px solid var(--border, #e0e4eb);
}
.contact-type-select, .social-platform-select { flex: 0 0 140px; }
.contact-value-input, .social-url-input { flex: 1; }
.contact-purpose-input { flex: 0 0 170px; }
.btn-remove {
flex: 0 0 36px;
height: 36px;
border: 1px solid var(--border, #e0e4eb);
background: var(--surface, #fff);
color: var(--text-secondary);
border-radius: var(--radius);
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.btn-remove:hover {
background: #fee2e2;
color: #dc2626;
border-color: #fca5a5;
}
.btn-add {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-sm) var(--spacing-md);
background: linear-gradient(135deg, rgba(46, 72, 114, 0.04), rgba(46, 72, 114, 0.08));
border: 1px dashed var(--primary, #2E4872);
border-radius: var(--radius);
color: var(--primary, #2E4872);
font-size: var(--font-size-sm);
font-weight: 500;
font-family: var(--font-family);
cursor: pointer;
transition: all 0.2s;
margin-top: var(--spacing-sm);
}
.btn-add:hover {
background: rgba(46, 72, 114, 0.05);
border-style: solid;
}
.btn-add svg { width: 16px; height: 16px; }
/* Social platform icon hints */
.social-row .social-platform-select { font-weight: 500; }
/* Website type select */
.website-type-select { flex: 0 0 140px; font-weight: 500; }
/* Visibility tab */
.visibility-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md);
border-bottom: 1px solid var(--border, #e0e4eb);
}
.visibility-row:last-child { border-bottom: none; }
.visibility-info { flex: 1; min-width: 0; }
.visibility-label {
display: block;
font-weight: 500;
color: var(--text-primary, #303030);
font-size: var(--font-size-base);
}
.visibility-desc {
display: block;
font-size: var(--font-size-sm);
color: var(--text-secondary, #464646);
margin-top: 2px;
}
.visibility-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius);
background: #ecfdf5;
color: #059669;
font-size: var(--font-size-sm);
font-family: var(--font-family);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
flex-shrink: 0;
}
.visibility-toggle svg { width: 18px; height: 18px; }
.visibility-toggle:hover { opacity: 0.8; }
.visibility-toggle.hidden-state {
background: #fef3c7;
color: #92400e;
border-color: #fde68a;
}
.visibility-toggle.saving {
opacity: 0.5;
pointer-events: none;
}
/* Sub-section rows */
.visibility-sub {
padding-left: calc(var(--spacing-md) + 24px);
background: #f8fafc;
border-bottom-color: #f1f5f9;
}
.visibility-sub .visibility-label {
font-size: var(--font-size-sm);
font-weight: 400;
color: var(--text-secondary, #464646);
}
.visibility-toggle-sub {
padding: 4px 10px;
font-size: 0.8rem;
}
.visibility-toggle-sub svg { width: 15px; height: 15px; }
.visibility-toggle-sub.hidden-state {
background: #fef9c3;
color: #a16207;
border-color: #fde68a;
}
/* Website primary radio */
.website-primary-label {
display: flex;
align-items: center;
gap: 4px;
flex: 0 0 auto;
font-size: var(--font-size-sm);
color: var(--text-secondary);
cursor: pointer;
white-space: nowrap;
}
.website-primary-label input[type="radio"]:checked + span {
color: var(--primary, #2E4872);
font-weight: 600;
}
/* Form actions (sticky bottom) */
.ce-actions {
display: flex;
gap: var(--spacing-md);
padding: var(--spacing-lg) var(--spacing-xl);
border-top: 1px solid var(--border, #e0e4eb);
background: linear-gradient(180deg, #f8fafc, var(--background, #EDF0F5));
}
.ce-actions .btn-primary {
background: linear-gradient(135deg, var(--primary, #2E4872), #4a6999);
border: none;
color: white;
padding: 10px 28px;
font-weight: 600;
border-radius: var(--radius);
cursor: pointer;
font-size: var(--font-size-base);
font-family: var(--font-family);
transition: all 0.2s;
box-shadow: 0 2px 8px rgba(46, 72, 114, 0.25);
}
.ce-actions .btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(46, 72, 114, 0.35);
}
/* Quill editor overrides */
.quill-container {
border: 1px solid var(--border, #e0e4eb);
border-radius: var(--radius, 0.5rem);
overflow: hidden;
background: var(--surface, #fff);
transition: var(--transition);
}
.quill-container:focus-within {
border-color: var(--primary, #2E4872);
box-shadow: 0 0 0 3px rgba(46, 72, 114, 0.1);
}
.quill-container .ql-toolbar {
border: none !important;
border-bottom: 1px solid var(--border, #e0e4eb) !important;
background: linear-gradient(180deg, #f1f5f9, var(--background, #EDF0F5));
font-family: var(--font-family) !important;
padding: 6px 10px !important;
}
.quill-container .ql-toolbar button:hover {
color: var(--primary, #2E4872) !important;
}
.quill-container .ql-toolbar button.ql-active {
color: var(--primary, #2E4872) !important;
}
.quill-container .ql-container {
border: none !important;
font-family: var(--font-family) !important;
font-size: var(--font-size-base, 1rem) !important;
}
.quill-container .ql-editor {
min-height: 120px;
line-height: 1.7;
color: var(--text-primary, #303030);
}
.quill-container .ql-editor.ql-blank::before {
color: var(--text-secondary, #464646);
opacity: 0.5;
font-style: italic;
}
.quill-container.quill-tall .ql-editor {
min-height: 200px;
}
fieldset[disabled] .quill-container {
opacity: 0.5;
pointer-events: none;
}
/* Responsive */
@media (max-width: 1024px) {
.ce-layout {
grid-template-columns: 1fr;
}
.ce-preview {
display: none;
}
.ce-preview-mobile-btn {
display: flex;
}
}
@media (max-width: 768px) {
.ce-header { flex-direction: column; text-align: center; }
.ce-header-actions { margin-left: 0; }
.ce-card .form-row { grid-template-columns: 1fr; }
.contact-row, .social-row { flex-wrap: wrap; }
.contact-type-select, .social-platform-select { flex: 1 1 100%; }
.contact-purpose-input { flex: 1 1 100%; }
.ce-tab { padding: var(--spacing-sm) var(--spacing-md); font-size: 13px; }
.ce-tab span.tab-label { display: none; }
}
</style>
{% endblock %}
{% block content %}
<div class="ce-container">
<!-- Header -->
<div class="ce-header">
<div class="ce-header-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</div>
<div class="ce-header-info">
<h1>{{ company.name }}</h1>
<p>Edytuj dane profilu firmy widoczne w katalogu Izby NORDA</p>
</div>
<div class="ce-header-actions">
<a href="{{ url_for('public.company_detail', company_id=company.id) }}" class="btn btn-outline btn-sm">
← Powrót do profilu
</a>
</div>
</div>
<!-- Permissions info -->
<div style="background: #eff6ff; border: 1px solid #bfdbfe; border-radius: var(--radius-md); padding: 12px 16px; margin-bottom: var(--spacing-lg); font-size: 13px; line-height: 1.6; color: #1e40af;">
<strong>Co możesz zmienić:</strong>
opis firmy, historię, usługi, kompetencje, dane kontaktowe (telefon, e-mail), social media i logo.
<br>
<span style="color: #64748b;">Dane rejestrowe (NIP, REGON, KRS, adres siedziby) są pobierane automatycznie z rejestrów urzędowych i nie wymagają ręcznej edycji.</span>
</div>
<!-- Split layout: editor + preview -->
<div class="ce-layout">
<!-- Main card -->
<div class="ce-card">
<!-- Tabs -->
<div class="ce-tabs">
<button type="button" class="ce-tab active" data-tab="description">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<span class="tab-label">Opis</span>
</button>
<button type="button" class="ce-tab" data-tab="services">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>
<span class="tab-label">Usługi</span>
</button>
<button type="button" class="ce-tab" data-tab="contacts">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
<span class="tab-label">Kontakt</span>
</button>
<button type="button" class="ce-tab" data-tab="social">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
<span class="tab-label">Social Media</span>
</button>
<button type="button" class="ce-tab" data-tab="visibility">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<span class="tab-label">Widoczność</span>
</button>
{% if current_user.can_manage_company(company.id) %}
<button type="button" class="ce-tab" data-tab="team">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span class="tab-label">Zespół</span>
</button>
{% endif %}
</div>
<form method="POST" action="{{ url_for('public.company_edit_save', company_id=company.id) }}" id="companyEditForm" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="active_tab" id="activeTabInput" value="description">
<!-- TAB 1: Opis -->
<div class="ce-tab-content active" id="tab-description">
{% if not permissions.description %}
<div class="ce-no-permission">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
</div>
{% endif %}
<fieldset {% if not permissions.description %}disabled{% endif %}>
<div class="form-group">
<label for="category_id" class="form-label">Kategoria działalności</label>
<select id="category_id" name="category_id" class="form-input">
<option value="">— Wybierz kategorię —</option>
{% for cat in categories %}
{% if cat.parent_id is none %}
{% set children = categories | selectattr('parent_id', 'equalto', cat.id) | list %}
{% if children %}
<optgroup label="{{ cat.name }}">
{% for sub in children %}
<option value="{{ sub.id }}" {% if company.category_id == sub.id %}selected{% endif %}>{{ sub.name }}</option>
{% endfor %}
</optgroup>
{% else %}
<option value="{{ cat.id }}" {% if company.category_id == cat.id %}selected{% endif %}>{{ cat.name }}</option>
{% endif %}
{% endif %}
{% endfor %}
</select>
<p class="form-help">Główna kategoria widoczna przy nazwie firmy w katalogu</p>
</div>
<div class="form-group">
<label class="form-label">Logo firmy</label>
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 8px;">
<div id="logoPreview" style="width: 80px; height: 80px; border-radius: 8px; border: 1px solid var(--border, #e0e4eb); overflow: hidden; display: flex; align-items: center; justify-content: center; background: #fff;">
<img src="{{ url_for('static', filename='img/companies/' ~ company.slug ~ '.webp') }}" alt="Logo"
style="max-width: 100%; max-height: 100%; object-fit: contain;"
onerror="if(!this.dataset.triedSvg){this.dataset.triedSvg='1';this.src=this.src.replace('.webp','.svg')}else{this.parentElement.innerHTML='<span style=\'color:var(--text-secondary,#999);font-size:12px\'>Brak logo</span>'}">
</div>
<div style="flex: 1;">
<input type="file" id="logo_file" name="logo_file" accept="image/png,image/jpeg,image/svg+xml,image/webp" class="form-input" style="padding: 8px;">
<p class="form-help" style="margin-top: 4px;">PNG, JPG, SVG lub WebP. Zalecany rozmiar: min. 200x200px.</p>
</div>
</div>
</div>
<div class="form-group">
<label for="year_established" class="form-label">Rok założenia firmy</label>
<input type="number" id="year_established" name="year_established" class="form-input" style="max-width: 200px;"
min="1800" max="2030" placeholder="np. 2015"
value="{{ company.year_established or '' }}"
{% if company.krs_registration_date or company.business_start_date %}readonly{% endif %}>
{% if company.krs_registration_date or company.business_start_date %}
<p class="form-help">Rok pochodzi z rejestru {% if company.krs_registration_date %}KRS{% else %}CEIDG{% endif %} — aby zmienić, skontaktuj się z administratorem</p>
{% else %}
<p class="form-help">Rok rozpoczęcia działalności firmy</p>
{% endif %}
</div>
<div class="form-group">
<label for="description_short" class="form-label">Krótki opis firmy</label>
<textarea id="description_short" name="description_short" class="form-input" rows="3" maxlength="500" placeholder="Np. Specjalizujemy się w usługach IT dla firm z Pomorza...">{{ company.description_short or '' }}</textarea>
<div class="char-counter" id="shortDescCounter">
<span id="shortDescCount">{{ (company.description_short or '') | length }}</span>/500 znaków
</div>
<p class="form-help">Widoczny na liście firm i w wynikach wyszukiwania · <strong>Wykorzystywany przez NordaGPT</strong></p>
</div>
<div class="form-group">
<label class="form-label">Pełny opis działalności</label>
<div class="quill-container quill-tall" id="quill-description_full"></div>
<textarea id="description_full" name="description_full" style="display:none;">{{ company.description_full or '' }}</textarea>
<p class="form-help">Użyj paska narzędzi do formatowania tekstu · <strong>Wykorzystywany przez NordaGPT</strong></p>
</div>
<div class="form-group">
<label class="form-label">Historia i doświadczenie</label>
<div class="quill-container" id="quill-founding_history"></div>
<textarea id="founding_history" name="founding_history" style="display:none;">{{ company.founding_history or '' }}</textarea>
<p class="form-help"><strong>Wykorzystywany przez NordaGPT</strong> — historia firmy, założyciele, doświadczenie</p>
</div>
<div class="form-group">
<label class="form-label">Wartości i misja</label>
<div class="quill-container" id="quill-core_values"></div>
<textarea id="core_values" name="core_values" style="display:none;">{{ company.core_values or '' }}</textarea>
<p class="form-help"><strong>Wykorzystywany przez NordaGPT</strong> — wartości i misja firmy</p>
</div>
</fieldset>
</div>
<!-- TAB 2: Usługi -->
<div class="ce-tab-content" id="tab-services">
{% if not permissions.services %}
<div class="ce-no-permission">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
</div>
{% endif %}
<fieldset {% if not permissions.services %}disabled{% endif %}>
<div class="form-group">
<label class="form-label">Oferowane usługi i produkty</label>
<div class="quill-container quill-tall" id="quill-services_offered"></div>
<textarea id="services_offered" name="services_offered" style="display:none;">{{ company.services_offered or '' }}</textarea>
<p class="form-help">Użyj paska narzędzi do formatowania tekstu · <strong>Wykorzystywany przez NordaGPT</strong></p>
</div>
<div class="form-group">
<label for="technologies_used" class="form-label">Technologie i specjalizacje</label>
<textarea id="technologies_used" name="technologies_used" class="form-input" rows="4" placeholder="Używane technologie, narzędzia, certyfikaty...">{{ company.technologies_used or '' }}</textarea>
<p class="form-help"><strong>Wykorzystywany przez NordaGPT</strong> — technologie i specjalizacje</p>
</div>
<div class="form-row">
<div class="form-group">
<label for="operational_area" class="form-label">Obszar działania</label>
<input type="text" id="operational_area" name="operational_area" class="form-input" value="{{ company.operational_area or '' }}" placeholder="np. Wejherowo, Trójmiasto, cała Polska" maxlength="500">
<p class="form-help">Gdzie firma świadczy usługi</p>
</div>
<div class="form-group">
<label for="languages_offered" class="form-label">Języki obsługi</label>
<input type="text" id="languages_offered" name="languages_offered" class="form-input" value="{{ company.languages_offered or '' }}" placeholder="np. Polski, Angielski, Niemiecki" maxlength="200">
</div>
</div>
<div class="form-group">
<label for="employee_count_range" class="form-label">Liczba pracowników</label>
<select id="employee_count_range" name="employee_count_range" class="form-input" style="max-width: 300px;">
<option value="">— Wybierz przedział —</option>
{% for range_val in ['1-5', '6-10', '11-25', '26-50', '51-100', '101-250', '250+'] %}
<option value="{{ range_val }}" {% if company.employee_count_range == range_val %}selected{% endif %}>{{ range_val }} pracowników</option>
{% endfor %}
</select>
</div>
</fieldset>
</div>
<!-- TAB 3: Kontakt -->
<div class="ce-tab-content" id="tab-contacts">
{% if not permissions.contacts %}
<div class="ce-no-permission">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
</div>
{% endif %}
<fieldset {% if not permissions.contacts %}disabled{% endif %}>
<span class="ce-section-title">Dane kontaktowe</span>
<div class="form-group" style="margin-bottom: var(--spacing-lg);">
<label class="form-label">Strony internetowe <span style="font-weight: normal; color: var(--text-secondary);">(max 5)</span></label>
<div id="websiteList">
{% for w in company_websites %}
<div class="social-row website-row">
<select name="website_types[]" class="form-input website-type-select">
<option value="website" {% if w.website_type == 'website' or not w.website_type %}selected{% endif %}>Strona firmowa</option>
<option value="store" {% if w.website_type == 'store' %}selected{% endif %}>Sklep</option>
<option value="booking" {% if w.website_type == 'booking' %}selected{% endif %}>Rezerwacje</option>
<option value="blog" {% if w.website_type == 'blog' %}selected{% endif %}>Blog</option>
<option value="portfolio" {% if w.website_type == 'portfolio' %}selected{% endif %}>Portfolio</option>
<option value="other" {% if w.website_type == 'other' %}selected{% endif %}>Inna</option>
</select>
<input type="url" name="website_urls[]" class="form-input social-url-input" value="{{ w.url }}" placeholder="https://www.twojafirma.pl">
<input type="text" name="website_labels[]" class="form-input" style="flex: 0 0 120px;" value="{{ w.label or '' }}" placeholder="Etykieta">
<label class="website-primary-label" title="Strona główna">
<input type="radio" name="website_primary" value="{{ loop.index0 }}" {% if w.is_primary %}checked{% endif %}>
<span>Gł.</span>
</label>
<button type="button" class="btn-remove" onclick="removeWebsiteRow(this)" title="Usuń">&#x2715;</button>
</div>
{% endfor %}
{% if not company_websites %}
{% if company.website %}
<div class="social-row website-row">
<select name="website_types[]" class="form-input website-type-select">
<option value="website" selected>Strona firmowa</option>
<option value="store">Sklep</option>
<option value="booking">Rezerwacje</option>
<option value="blog">Blog</option>
<option value="portfolio">Portfolio</option>
<option value="other">Inna</option>
</select>
<input type="url" name="website_urls[]" class="form-input social-url-input" value="{{ company.website }}" placeholder="https://www.twojafirma.pl">
<input type="text" name="website_labels[]" class="form-input" style="flex: 0 0 120px;" value="" placeholder="Etykieta">
<label class="website-primary-label" title="Strona główna">
<input type="radio" name="website_primary" value="0" checked>
<span>Gł.</span>
</label>
<button type="button" class="btn-remove" onclick="removeWebsiteRow(this)" title="Usuń">&#x2715;</button>
</div>
{% endif %}
{% endif %}
</div>
<button type="button" class="btn-add" id="addWebsiteBtn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Dodaj stronę WWW
</button>
</div>
<div class="form-row">
<div class="form-group">
<label for="email" class="form-label">Email firmowy</label>
<input type="email" id="email" name="email" class="form-input" value="{{ company.email or '' }}" placeholder="kontakt@twojafirma.pl">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="phone" class="form-label">Telefon główny</label>
<input type="tel" id="phone" name="phone" class="form-input" value="{{ company.phone or '' }}" placeholder="+48 58 123 45 67" maxlength="50">
</div>
<div class="form-group"></div>
</div>
<span class="ce-section-title" style="margin-top: var(--spacing-lg);">Adres siedziby</span>
<div class="form-group">
<label for="address_street" class="form-label">Ulica i numer</label>
<input type="text" id="address_street" name="address_street" class="form-input" value="{{ company.address_street or '' }}" placeholder="ul. Dworcowa 7/3" maxlength="255">
</div>
<div class="form-row">
<div class="form-group">
<label for="address_postal" class="form-label">Kod pocztowy</label>
<input type="text" id="address_postal" name="address_postal" class="form-input" value="{{ company.address_postal or '' }}" placeholder="84-200" maxlength="10" style="max-width: 150px;">
</div>
<div class="form-group">
<label for="address_city" class="form-label">Miasto</label>
<input type="text" id="address_city" name="address_city" class="form-input" value="{{ company.address_city or '' }}" placeholder="Wejherowo" maxlength="100">
</div>
</div>
<!-- Dynamic contacts -->
<div class="ce-dynamic-section">
<p class="ce-dynamic-title">Dodatkowe numery i adresy email</p>
<p class="ce-dynamic-help">Dodaj kolejne numery telefonów, adresy email lub fax z opisem przeznaczenia.</p>
<div id="contactsList">
{% for contact in contacts %}
<div class="contact-row">
<select name="contact_types[]" class="form-input contact-type-select">
<option value="phone" {% if contact.contact_type == 'phone' %}selected{% endif %}>📞 Telefon</option>
<option value="mobile" {% if contact.contact_type == 'mobile' %}selected{% endif %}>📱 Komórka</option>
<option value="email" {% if contact.contact_type == 'email' %}selected{% endif %}>✉️ Email</option>
<option value="fax" {% if contact.contact_type == 'fax' %}selected{% endif %}>📠 Fax</option>
</select>
<input type="text" name="contact_values[]" class="form-input contact-value-input" value="{{ contact.value }}" placeholder="Numer lub adres">
<input type="text" name="contact_purposes[]" class="form-input contact-purpose-input" value="{{ contact.purpose or '' }}" placeholder="np. Biuro, Sprzedaż">
<button type="button" class="btn-remove" onclick="this.parentElement.remove()" title="Usuń">&#x2715;</button>
</div>
{% endfor %}
</div>
<button type="button" class="btn-add" id="addContactBtn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Dodaj kontakt
</button>
</div>
</fieldset>
</div>
<!-- TAB 4: Social Media -->
<div class="ce-tab-content" id="tab-social">
{% if not permissions.social %}
<div class="ce-no-permission">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
</div>
{% endif %}
<fieldset {% if not permissions.social %}disabled{% endif %}>
<p class="ce-dynamic-help" style="margin-top: 0;">Dodaj linki do profili firmy w mediach społecznościowych.</p>
<div id="socialList">
{% for sm in social_media %}
{% if sm.source is none or sm.source == 'manual_edit' or sm.source == 'manual' %}
<div class="social-row">
<select name="social_platforms[]" class="form-input social-platform-select">
<option value="facebook" {% if sm.platform == 'facebook' %}selected{% endif %}>Facebook</option>
<option value="linkedin" {% if sm.platform == 'linkedin' %}selected{% endif %}>LinkedIn</option>
<option value="instagram" {% if sm.platform == 'instagram' %}selected{% endif %}>Instagram</option>
<option value="youtube" {% if sm.platform == 'youtube' %}selected{% endif %}>YouTube</option>
<option value="twitter" {% if sm.platform == 'twitter' %}selected{% endif %}>X (Twitter)</option>
<option value="tiktok" {% if sm.platform == 'tiktok' %}selected{% endif %}>TikTok</option>
</select>
<input type="url" name="social_urls[]" class="form-input social-url-input" value="{{ sm.url }}" placeholder="https://facebook.com/twojafirma">
<button type="button" class="btn-remove" onclick="this.parentElement.remove()" title="Usuń">&#x2715;</button>
</div>
{% endif %}
{% endfor %}
</div>
<button type="button" class="btn-add" id="addSocialBtn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Dodaj profil
</button>
{% set ns = namespace(has_auto=false) %}
{% for sm in social_media %}
{% if sm.source is not none and sm.source != 'manual_edit' and sm.source != 'manual' %}
{% set ns.has_auto = true %}
{% endif %}
{% endfor %}
{% if ns.has_auto %}
<div class="ce-info-box" style="margin-top: var(--spacing-lg);">
<strong>Profile wykryte automatycznie</strong>
Te profile zostały wykryte przez system i nie podlegają ręcznej edycji:
<ul>
{% for sm in social_media %}
{% if sm.source is not none and sm.source != 'manual_edit' and sm.source != 'manual' %}
<li><strong>{{ sm.platform | capitalize }}</strong>: <a href="{{ sm.url }}" target="_blank" rel="noopener">{{ sm.url | truncate(60) }}</a></li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endif %}
</fieldset>
</div>
<!-- TAB 5: Widoczność -->
<div class="ce-tab-content" id="tab-visibility">
<div class="ce-info-box">
<strong>Zarządzaj widocznością sekcji profilu</strong>
Ukryte sekcje nie będą widoczne dla odwiedzających Twój profil.
Ty i kadra zarządzająca nadal widzicie ukryte sekcje z oznaczeniem.
</div>
<div id="visibilitySections" style="margin-top: var(--spacing-lg);">
{% for key, label, description, subs in section_definitions %}
<div class="visibility-row" data-section="{{ key }}">
<div class="visibility-info">
<span class="visibility-label">{{ label }}</span>
<span class="visibility-desc">{{ description }}</span>
</div>
<button type="button" class="visibility-toggle {% if company.is_section_hidden(key) %}hidden-state{% endif %}"
onclick="toggleSection('{{ key }}', this)" title="Kliknij aby zmienić widoczność">
{% if company.is_section_hidden(key) %}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
<span>Ukryta</span>
{% else %}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<span>Widoczna</span>
{% endif %}
</button>
</div>
{% if subs %}
{% for sub_key, sub_label in subs %}
<div class="visibility-row visibility-sub" data-section="{{ sub_key }}">
<div class="visibility-info">
<span class="visibility-label">{{ sub_label }}</span>
</div>
<button type="button" class="visibility-toggle visibility-toggle-sub {% if company.is_section_hidden(sub_key) %}hidden-state{% endif %}"
onclick="toggleSection('{{ sub_key }}', this)" title="Kliknij aby zmienić widoczność">
{% if company.is_section_hidden(sub_key) %}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
<span>Ukryta</span>
{% else %}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
<span>Widoczna</span>
{% endif %}
</button>
</div>
{% endfor %}
{% endif %}
{% endfor %}
</div>
<div id="visibilityStatus" style="margin-top: var(--spacing-md); display: none;" class="ce-info-box"></div>
</div>
<!-- Form actions -->
<div class="ce-actions">
<button type="submit" class="btn btn-primary">
Zapisz zmiany
</button>
<a href="{{ url_for('public.company_detail', company_id=company.id) }}" class="btn btn-outline">Anuluj</a>
</div>
</form>
{% if current_user.can_manage_company(company.id) %}
<!-- TAB 6: Zespół (outside form — uses AJAX) -->
<div class="ce-tab-content" id="tab-team">
<div class="team-header">
<h3>Zespół firmy</h3>
<button type="button" class="btn btn-primary btn-sm" onclick="toggleAddForm()">+ Dodaj osobę</button>
</div>
<div class="team-add-form" id="teamAddForm" style="display:none;">
<div class="form-row">
<div>
<label for="teamEmail">Email</label>
<input type="email" id="teamEmail" class="form-input" placeholder="jan@example.com">
</div>
<div>
<label for="teamName">Imię i nazwisko</label>
<input type="text" id="teamName" class="form-input" placeholder="Jan Kowalski">
</div>
</div>
<div class="form-row-bottom">
<div>
<label for="teamRole">Rola</label>
<select id="teamRole" class="form-input">
<option value="EMPLOYEE">Pracownik — edytuje wybrane sekcje profilu firmy</option>
<option value="VIEWER">Obserwator — widzi dashboard, ale nie edytuje</option>
<option value="MANAGER">Kadra zarządzająca — pełna kontrola i zarządzanie zespołem</option>
</select>
</div>
<div>
<button type="button" class="btn btn-primary" onclick="addTeamMember()">Dodaj</button>
<button type="button" class="btn btn-outline" onclick="toggleAddForm()" style="margin-left:4px;">Anuluj</button>
</div>
</div>
<p style="font-size:var(--font-size-sm); color:var(--text-secondary); margin:var(--spacing-sm) 0 0;">
Nowy użytkownik otrzyma email z linkiem do ustawienia hasła.
Istniejący użytkownik portalu zostanie od razu dodany do zespołu.
</p>
</div>
<div id="teamMembersList">
<div class="team-loading">Ładowanie zespołu...</div>
</div>
</div>
{% endif %}
</div>
<div class="ce-preview" id="livePreview">
<div class="ce-preview-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
Podgląd profilu
</div>
<div class="preview-company-name">{{ company.name }}</div>
<div class="preview-short-desc" id="previewShortDesc">{{ company.description_short or 'Brak krótkiego opisu' }}</div>
<div class="preview-section" id="previewDescriptionSection">
<div class="preview-section-label">Opis firmy</div>
<div class="preview-section-content" id="previewDescFull">
{% if company.description_full %}{{ company.description_full | safe }}{% else %}<span class="preview-empty">Uzupełnij opis firmy...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewHistorySection">
<div class="preview-section-label">Historia i doświadczenie</div>
<div class="preview-section-content" id="previewHistory">
{% if company.founding_history %}{{ company.founding_history | safe }}{% else %}<span class="preview-empty">Uzupełnij historię...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewValuesSection">
<div class="preview-section-label">Wartości i misja</div>
<div class="preview-section-content" id="previewValues">
{% if company.core_values %}{{ company.core_values | safe }}{% else %}<span class="preview-empty">Uzupełnij wartości...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewServicesSection">
<div class="preview-section-label">Oferowane usługi</div>
<div class="preview-section-content" id="previewServices">
{% if company.services_offered %}{{ company.services_offered | safe }}{% else %}<span class="preview-empty">Uzupełnij usługi...</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewContactSection">
<div class="preview-section-label">Dane kontaktowe</div>
<div class="preview-section-content" id="previewContact">
{% if company.email %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>{{ company.email }}</div>{% endif %}
{% if company.phone %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>{{ company.phone }}</div>{% endif %}
{% if company.website %}<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>{{ company.website }}</div>{% endif %}
{% if not company.email and not company.phone and not company.website %}<span class="preview-empty">Brak danych kontaktowych</span>{% endif %}
</div>
</div>
<div class="preview-section" id="previewSocialSection">
<div class="preview-section-label">Social Media</div>
<div class="preview-section-content" id="previewSocial">
{% for sm in social_media %}
<span class="preview-social-item">{{ sm.platform | capitalize }}</span>
{% endfor %}
{% if not social_media %}<span class="preview-empty">Brak profili social media</span>{% endif %}
</div>
</div>
</div>
</div><!-- /ce-layout -->
</div>
<!-- Mobile preview button -->
<button type="button" class="ce-preview-mobile-btn" id="mobilePreviewBtn" onclick="openMobilePreview()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
Podgląd
</button>
<!-- Mobile preview bottom sheet -->
<div class="ce-preview-sheet-overlay" id="previewSheetOverlay">
<div class="ce-preview-sheet" id="previewSheet">
<div class="ce-preview-sheet-header">
<span style="font-weight: 600;">Podgląd profilu</span>
<button type="button" class="ce-preview-sheet-close" onclick="closeMobilePreview()">Zamknij</button>
</div>
<div id="mobilePreviewContent"></div>
</div>
</div>
{% endblock %}
{% block extra_js %}
// ============================================
// Quill.js WYSIWYG Initialization
// ============================================
var quillInstances = {};
var QUILL_TOOLBAR = [
['bold', 'italic'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['link'],
['clean']
];
function initQuillEditor(fieldName, placeholder) {
var container = document.getElementById('quill-' + fieldName);
var textarea = document.getElementById(fieldName);
if (!container || !textarea) return null;
var quill = new Quill(container, {
theme: 'snow',
modules: { toolbar: QUILL_TOOLBAR },
placeholder: placeholder || 'Wpisz tekst...'
});
// Load existing content from hidden textarea
var existing = textarea.value.trim();
if (existing) {
quill.root.innerHTML = existing;
}
// Sync to hidden textarea on every change + update preview
quill.on('text-change', function() {
var html = quill.root.innerHTML;
textarea.value = (html === '<p><br></p>') ? '' : html;
updatePreview(fieldName, textarea.value);
});
quillInstances[fieldName] = quill;
return quill;
}
// Initialize all Quill editors
if (typeof Quill !== 'undefined') {
initQuillEditor('description_full', 'Szczegółowy opis tego czym zajmuje się firma...');
initQuillEditor('founding_history', 'Kiedy firma powstała, jakie ma doświadczenie...');
initQuillEditor('core_values', 'Kluczowe wartości firmy, misja...');
initQuillEditor('services_offered', 'Wymień główne usługi i produkty...');
}
// ============================================
// Live Preview Updates
// ============================================
var previewDebounceTimers = {};
function updatePreview(fieldName, value) {
clearTimeout(previewDebounceTimers[fieldName]);
previewDebounceTimers[fieldName] = setTimeout(function() {
doUpdatePreview(fieldName, value);
}, 300);
}
function doUpdatePreview(fieldName, value) {
var mapping = {
'description_short': 'previewShortDesc',
'description_full': 'previewDescFull',
'founding_history': 'previewHistory',
'core_values': 'previewValues',
'services_offered': 'previewServices'
};
var emptyTexts = {
'description_short': 'Brak krótkiego opisu',
'description_full': 'Uzupełnij opis firmy...',
'founding_history': 'Uzupełnij historię...',
'core_values': 'Uzupełnij wartości...',
'services_offered': 'Uzupełnij usługi...'
};
var targetId = mapping[fieldName];
if (!targetId) return;
var el = document.getElementById(targetId);
if (!el) return;
if (value && value.trim() && value !== '<p><br></p>') {
el.innerHTML = value;
el.classList.remove('preview-empty');
} else {
el.innerHTML = '<span class="preview-empty">' + (emptyTexts[fieldName] || '') + '</span>';
}
}
// Hook plain text fields to preview
var shortDescField = document.getElementById('description_short');
if (shortDescField) {
shortDescField.addEventListener('input', function() {
updatePreview('description_short', this.value);
});
}
// Hook contact fields to preview
function updateContactPreview() {
var email = (document.getElementById('email') || {}).value || '';
var phone = (document.getElementById('phone') || {}).value || '';
var el = document.getElementById('previewContact');
if (!el) return;
var html = '';
if (email) html += '<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>' + email + '</div>';
if (phone) html += '<div class="preview-contact-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>' + phone + '</div>';
el.innerHTML = html || '<span class="preview-empty">Brak danych kontaktowych</span>';
}
['email', 'phone'].forEach(function(id) {
var field = document.getElementById(id);
if (field) field.addEventListener('input', updateContactPreview);
});
// Highlight preview section matching active tab
function highlightPreviewTab(tabName) {
var sections = document.querySelectorAll('.ce-preview .preview-section');
sections.forEach(function(s) { s.style.opacity = '0.4'; s.style.transition = 'opacity 0.3s'; });
var tabToSections = {
'description': ['previewDescriptionSection', 'previewHistorySection', 'previewValuesSection'],
'services': ['previewServicesSection'],
'contacts': ['previewContactSection'],
'social': ['previewSocialSection']
};
var active = tabToSections[tabName] || [];
active.forEach(function(id) {
var el = document.getElementById(id);
if (el) el.style.opacity = '1';
});
if (tabName === 'visibility') {
sections.forEach(function(s) { s.style.opacity = '1'; });
}
}
// Initial highlight for default tab
highlightPreviewTab('description');
// Company Edit — tabs, dynamic fields, validation
(function() {
// Tab switching
var tabs = document.querySelectorAll('.ce-tab');
var contents = document.querySelectorAll('.ce-tab-content');
var activeTabInput = document.getElementById('activeTabInput');
tabs.forEach(function(tab) {
tab.addEventListener('click', function() {
var target = this.getAttribute('data-tab');
tabs.forEach(function(t) { t.classList.remove('active'); });
this.classList.add('active');
contents.forEach(function(c) { c.classList.remove('active'); });
document.getElementById('tab-' + target).classList.add('active');
activeTabInput.value = target;
highlightPreviewTab(target);
// Hide form actions and preview on team tab (team saves via AJAX)
var actions = document.querySelector('.ce-actions');
var preview = document.getElementById('livePreview');
if (actions) actions.style.display = target === 'team' ? 'none' : '';
if (preview) preview.style.display = target === 'team' ? 'none' : '';
});
});
// Character counter for short description
var shortDesc = document.getElementById('description_short');
var shortCount = document.getElementById('shortDescCount');
var shortCounter = document.getElementById('shortDescCounter');
if (shortDesc && shortCount) {
shortDesc.addEventListener('input', function() {
var len = this.value.length;
shortCount.textContent = len;
shortCounter.className = 'char-counter' + (len > 450 ? (len >= 500 ? ' error' : ' warning') : '');
});
}
// Helper: create contact row HTML
function makeContactRow() {
var row = document.createElement('div');
row.className = 'contact-row';
row.innerHTML =
'<select name="contact_types[]" class="form-input contact-type-select">' +
'<option value="phone">\uD83D\uDCDE Telefon</option>' +
'<option value="mobile">\uD83D\uDCF1 Kom\u00f3rka</option>' +
'<option value="email">\u2709\uFE0F Email</option>' +
'<option value="fax">\uD83D\uDCE0 Fax</option>' +
'</select>' +
'<input type="text" name="contact_values[]" class="form-input contact-value-input" placeholder="Numer lub adres">' +
'<input type="text" name="contact_purposes[]" class="form-input contact-purpose-input" placeholder="np. Biuro, Sprzeda\u017c">' +
'<button type="button" class="btn-remove" onclick="this.parentElement.remove()" title="Usu\u0144">\u2715</button>';
return row;
}
// Helper: create social row HTML
function makeSocialRow() {
var row = document.createElement('div');
row.className = 'social-row';
row.innerHTML =
'<select name="social_platforms[]" class="form-input social-platform-select">' +
'<option value="facebook">Facebook</option>' +
'<option value="linkedin">LinkedIn</option>' +
'<option value="instagram">Instagram</option>' +
'<option value="youtube">YouTube</option>' +
'<option value="twitter">X (Twitter)</option>' +
'<option value="tiktok">TikTok</option>' +
'</select>' +
'<input type="url" name="social_urls[]" class="form-input social-url-input" placeholder="https://...">' +
'<button type="button" class="btn-remove" onclick="this.parentElement.remove()" title="Usu\u0144">\u2715</button>';
return row;
}
// Add contact
var addContactBtn = document.getElementById('addContactBtn');
if (addContactBtn) {
addContactBtn.addEventListener('click', function() {
document.getElementById('contactsList').appendChild(makeContactRow());
});
}
// Add social
var addSocialBtn = document.getElementById('addSocialBtn');
if (addSocialBtn) {
addSocialBtn.addEventListener('click', function() {
document.getElementById('socialList').appendChild(makeSocialRow());
});
}
// Client-side validation
document.getElementById('companyEditForm').addEventListener('submit', function(e) {
var emailField = document.getElementById('email');
if (emailField && emailField.value.trim()) {
var emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailPattern.test(emailField.value.trim())) {
e.preventDefault();
emailField.focus();
emailField.style.borderColor = 'var(--error, #ef4444)';
emailField.style.boxShadow = '0 0 0 3px rgba(239,68,68,0.1)';
return;
}
}
// Auto-prefix https:// for all website URL inputs
document.querySelectorAll('#websiteList input[name="website_urls[]"]').forEach(function(f) {
if (f.value.trim() && !f.value.match(/^https?:\/\//)) {
f.value = 'https://' + f.value.trim();
}
});
});
})();
// Website list management
function removeWebsiteRow(btn) {
btn.closest('.website-row').remove();
reindexWebsiteRadios();
toggleWebsiteBtn();
}
function reindexWebsiteRadios() {
var rows = document.querySelectorAll('#websiteList .website-row');
var hadChecked = false;
rows.forEach(function(row, i) {
var radio = row.querySelector('input[type="radio"]');
radio.value = i;
if (radio.checked) hadChecked = true;
});
if (!hadChecked && rows.length > 0) {
rows[0].querySelector('input[type="radio"]').checked = true;
}
}
function toggleWebsiteBtn() {
var btn = document.getElementById('addWebsiteBtn');
if (btn) {
btn.style.display = document.querySelectorAll('#websiteList .website-row').length >= 5 ? 'none' : '';
}
}
(function() {
var addBtn = document.getElementById('addWebsiteBtn');
if (addBtn) {
addBtn.addEventListener('click', function() {
var list = document.getElementById('websiteList');
var idx = list.querySelectorAll('.website-row').length;
if (idx >= 5) return;
var row = document.createElement('div');
row.className = 'social-row website-row';
row.innerHTML = '<select name="website_types[]" class="form-input website-type-select"><option value="website">Strona firmowa</option><option value="store">Sklep</option><option value="booking">Rezerwacje</option><option value="blog">Blog</option><option value="portfolio">Portfolio</option><option value="other">Inna</option></select>'
+ '<input type="url" name="website_urls[]" class="form-input social-url-input" value="" placeholder="https://www.twojafirma.pl">'
+ '<input type="text" name="website_labels[]" class="form-input" style="flex: 0 0 120px;" value="" placeholder="Etykieta">'
+ '<label class="website-primary-label" title="Strona główna"><input type="radio" name="website_primary" value="' + idx + '"><span>Gł.</span></label>'
+ '<button type="button" class="btn-remove" onclick="removeWebsiteRow(this)" title="Usuń">&#x2715;</button>';
list.appendChild(row);
if (idx === 0) row.querySelector('input[type="radio"]').checked = true;
toggleWebsiteBtn();
});
toggleWebsiteBtn();
}
})();
// Section visibility toggle (AJAX)
var hiddenSections = {{ company.hidden_sections | tojson }};
function toggleSection(key, btn) {
btn.classList.add('saving');
var idx = hiddenSections.indexOf(key);
if (idx >= 0) {
hiddenSections.splice(idx, 1);
} else {
hiddenSections.push(key);
}
fetch('{{ url_for("public.company_edit_visibility", company_id=company.id) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ hidden_sections: hiddenSections })
})
.then(function(r) { return r.json(); })
.then(function(data) {
btn.classList.remove('saving');
if (data.success) {
hiddenSections = data.hidden_sections;
var isHidden = hiddenSections.indexOf(key) >= 0;
btn.className = 'visibility-toggle' + (isHidden ? ' hidden-state' : '');
btn.innerHTML = isHidden
? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg><span>Ukryta</span>'
: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span>Widoczna</span>';
} else {
// Revert on error
if (idx >= 0) hiddenSections.push(key);
else hiddenSections.splice(hiddenSections.indexOf(key), 1);
}
})
.catch(function() {
btn.classList.remove('saving');
if (idx >= 0) hiddenSections.push(key);
else hiddenSections.splice(hiddenSections.indexOf(key), 1);
});
}
// Mobile preview
function openMobilePreview() {
var mobileContent = document.getElementById('mobilePreviewContent');
var desktopPreview = document.getElementById('livePreview');
if (mobileContent && desktopPreview) {
var clone = desktopPreview.cloneNode(true);
var title = clone.querySelector('.ce-preview-title');
if (title) title.remove();
mobileContent.innerHTML = clone.innerHTML;
}
document.getElementById('previewSheetOverlay').classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeMobilePreview() {
document.getElementById('previewSheetOverlay').classList.remove('active');
document.body.style.overflow = '';
}
var previewOverlay = document.getElementById('previewSheetOverlay');
if (previewOverlay) {
previewOverlay.addEventListener('click', function(e) {
if (e.target === this) closeMobilePreview();
});
}
// Toast notifications
function showToast(message, type, duration) {
type = type || 'info';
duration = duration || 4000;
var toast = document.createElement('div');
toast.className = 'flash flash-' + type;
toast.innerHTML = '<span>' + message + '</span><button class="flash-close" onclick="this.parentElement.remove()">&times;</button>';
toast.style.cssText = 'position:fixed;top:20px;right:20px;z-index:9999;min-width:280px;max-width:480px;animation:fadeIn 0.3s;';
document.body.appendChild(toast);
setTimeout(function() { if (toast.parentElement) toast.remove(); }, duration);
}
{% if current_user.can_manage_company(company.id) %}
// ===== Team Management =====
var TEAM_COMPANY_ID = {{ company.id }};
var teamLoaded = false;
var CSRF_TOKEN = '{{ csrf_token() }}';
var PERM_LABELS = {
'can_edit_description': { label: 'Opis i historia firmy', hint: 'Zakładka „Opis" w profilu firmy' },
'can_edit_services': { label: 'Usługi i kompetencje', hint: 'Zakładka „Usługi" w profilu firmy' },
'can_edit_contacts': { label: 'Telefony i adresy email', hint: 'Zakładka „Kontakt" w profilu firmy' },
'can_edit_social': { label: 'Social media i strona www', hint: 'Zakładka „Social Media" w profilu firmy' },
'can_manage_classifieds': { label: 'Ogłoszenia B2B firmy', hint: 'Publikowanie ogłoszeń na Tablicy B2B' },
'can_post_forum': { label: 'Posty na forum', hint: 'Pisanie postów na forum w imieniu firmy' },
'can_view_analytics': { label: 'Statystyki firmy', hint: 'Wyświetlenia profilu, wyszukiwania, trendy' }
};
var ROLE_INFO = {
'VIEWER': { label: 'Obserwator', desc: 'Widzi dashboard firmy, ale nie może niczego edytować' },
'EMPLOYEE': { label: 'Pracownik', desc: 'Może edytować wybrane sekcje profilu firmy (wg uprawnień poniżej)' },
'MANAGER': { label: 'Kadra zarządzająca', desc: 'Pełna kontrola nad profilem firmy i zarządzanie zespołem' }
};
// Hook into tab switcher — lazy load team on first click
document.addEventListener('click', function(e) {
var tab = e.target.closest('.ce-tab[data-tab="team"]');
if (tab && !teamLoaded) {
teamLoaded = true;
loadTeamMembers();
}
});
function toggleAddForm() {
var form = document.getElementById('teamAddForm');
form.style.display = form.style.display === 'none' ? 'block' : 'none';
if (form.style.display === 'block') document.getElementById('teamEmail').focus();
}
function loadTeamMembers() {
var list = document.getElementById('teamMembersList');
list.innerHTML = '<div class="team-loading">Ładowanie...</div>';
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol')
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.success) { list.innerHTML = '<div class="team-empty">Błąd: ' + (data.error || '') + '</div>'; return; }
if (!data.members.length) { list.innerHTML = '<div class="team-empty">Brak członków zespołu. Dodaj pierwszą osobę.</div>'; return; }
var html = '';
data.members.forEach(function(m) {
var initials = (m.name || '?').split(' ').map(function(w) { return w[0]; }).join('').substring(0, 2).toUpperCase();
var roleClass = m.role === 'MANAGER' ? 'manager' : (m.role === 'EMPLOYEE' ? 'employee' : 'viewer');
var ri = ROLE_INFO[m.role] || { label: m.role, desc: '' };
html += '<div class="team-member-card" data-user-id="' + m.id + '">';
html += '<div class="team-member-avatar">' + initials + '</div>';
html += '<div class="team-member-info">';
html += '<div class="team-member-name">' + escapeHtml(m.name || m.email);
if (m.is_current_user) html += '<span class="team-you-badge">Ty</span>';
html += '</div>';
html += '<div class="team-member-email">' + escapeHtml(m.email) + '</div>';
// Login status
if (!m.is_current_user) {
if (m.last_login) {
html += '<div class="team-member-status team-status-active">Ostatnie logowanie: ' + formatDate(m.last_login) + '</div>';
} else {
html += '<div class="team-member-status team-status-pending">Nie zalogował/a się jeszcze';
html += ' <button type="button" class="team-btn-resend" onclick="resendInvite(' + m.id + ', \'' + escapeHtml(m.email).replace(/'/g, "\\'") + '\')">Wyślij ponownie zaproszenie</button>';
html += '</div>';
}
}
html += '</div>';
html += '<div class="team-member-actions">';
if (!m.is_current_user) {
html += '<select class="team-role-select" onchange="changeRole(' + m.id + ', this.value)">';
html += '<option value="VIEWER"' + (m.role === 'VIEWER' ? ' selected' : '') + '>Obserwator</option>';
html += '<option value="EMPLOYEE"' + (m.role === 'EMPLOYEE' ? ' selected' : '') + '>Pracownik</option>';
html += '<option value="MANAGER"' + (m.role === 'MANAGER' ? ' selected' : '') + '>Kadra zarządzająca</option>';
html += '</select>';
html += '<button type="button" class="team-btn-remove" onclick="removeTeamMember(' + m.id + ', \'' + escapeHtml(m.name || m.email).replace(/'/g, "\\'") + '\')">Usuń</button>';
} else {
html += '<span class="team-role-badge ' + roleClass + '">' + ri.label + '</span>';
}
html += '</div>';
// Role description
html += '<div class="team-role-desc" style="width:100%;font-size:12px;color:var(--text-secondary);margin-top:-4px;padding:0 0 4px 54px;">' + ri.desc + '</div>';
// Permissions for EMPLOYEE
if (m.role === 'EMPLOYEE' && !m.is_current_user && m.permissions) {
html += '<div class="team-perms-toggle" style="width:100%;">';
html += '<div style="font-size:var(--font-size-sm);font-weight:500;margin-bottom:6px;color:var(--text-secondary);">Co może edytować w profilu firmy:</div>';
html += '<div class="team-perms-grid">';
Object.keys(PERM_LABELS).forEach(function(key) {
var p = PERM_LABELS[key];
var checked = m.permissions[key] ? ' checked' : '';
html += '<label class="team-perm-item" title="' + p.hint + '">';
html += '<input type="checkbox"' + checked + ' onchange="togglePermission(' + m.id + ', \'' + key + '\', this.checked)">';
html += '<span>' + p.label + '<span class="team-perm-hint">' + p.hint + '</span></span>';
html += '</label>';
});
html += '</div></div>';
}
html += '</div>';
});
list.innerHTML = html;
})
.catch(function() { list.innerHTML = '<div class="team-empty">Nie udało się załadować zespołu.</div>'; });
}
function addTeamMember() {
var email = document.getElementById('teamEmail').value.trim();
var name = document.getElementById('teamName').value.trim();
var role = document.getElementById('teamRole').value;
if (!email) { showToast('Podaj adres email', 'error'); return; }
if (!name) { showToast('Podaj imię i nazwisko', 'error'); return; }
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol/dodaj', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF_TOKEN },
body: JSON.stringify({ email: email, name: name, role: role })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
showToast(data.message, 'success');
document.getElementById('teamEmail').value = '';
document.getElementById('teamName').value = '';
document.getElementById('teamAddForm').style.display = 'none';
loadTeamMembers();
} else {
showToast(data.error || 'Błąd', 'error');
}
})
.catch(function() { showToast('Błąd połączenia', 'error'); });
}
function changeRole(userId, newRole) {
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol/' + userId + '/rola', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF_TOKEN },
body: JSON.stringify({ role: newRole })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
showToast(data.message, 'success');
loadTeamMembers(); // Re-render to update permissions section
} else {
showToast(data.error || 'Błąd', 'error');
loadTeamMembers(); // Revert select
}
})
.catch(function() { showToast('Błąd połączenia', 'error'); });
}
function togglePermission(userId, permKey, value) {
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol/' + userId + '/uprawnienia', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF_TOKEN },
body: JSON.stringify({ permission: permKey, value: value })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.success) { showToast(data.error || 'Błąd', 'error'); loadTeamMembers(); }
})
.catch(function() { showToast('Błąd połączenia', 'error'); });
}
function removeTeamMember(userId, userName) {
nordaConfirm(
'Usunąć <strong>' + escapeHtml(userName) + '</strong> z zespołu?<br><small style="color:var(--text-secondary)">Konto użytkownika nie zostanie usunięte — tylko powiązanie z firmą.</small>',
function() {
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol/' + userId + '/usun', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF_TOKEN }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) { showToast(data.message, 'success'); loadTeamMembers(); }
else showToast(data.error || 'Błąd', 'error');
})
.catch(function() { showToast('Błąd połączenia', 'error'); });
}
);
}
function resendInvite(userId, email) {
fetch('/firma/' + TEAM_COMPANY_ID + '/zespol/' + userId + '/zaproszenie', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': CSRF_TOKEN }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) showToast('Zaproszenie wysłane ponownie na ' + email, 'success');
else showToast(data.error || 'Błąd', 'error');
})
.catch(function() { showToast('Błąd połączenia', 'error'); });
}
function formatDate(isoStr) {
if (!isoStr) return '';
var d = new Date(isoStr);
var day = d.getDate();
var months = ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'];
return day + ' ' + months[d.getMonth()] + ' ' + d.getFullYear();
}
function escapeHtml(str) {
var div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
{% endif %}
{% endblock %}