feat(company): Add company profile editing for owners and employees
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
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
Allow company owners/employees to edit marketing fields (descriptions, services, contacts, social media) directly without admin intervention. Legal fields (NIP, KRS, name) remain admin-only. Per-tab permission checks with delegated permissions support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b3270bda84
commit
1dc7f20e9b
@ -179,6 +179,9 @@ def register_blueprints(app):
|
||||
# Announcements
|
||||
'announcements_list': 'public.announcements_list',
|
||||
'announcement_detail': 'public.announcement_detail',
|
||||
# Company Edit
|
||||
'company_edit': 'public.company_edit',
|
||||
'company_edit_save': 'public.company_edit_save',
|
||||
})
|
||||
logger.info("Created public endpoint aliases")
|
||||
except ImportError as e:
|
||||
|
||||
@ -12,3 +12,4 @@ bp = Blueprint('public', __name__)
|
||||
from . import routes # noqa: E402, F401
|
||||
from . import routes_zopk # noqa: E402, F401
|
||||
from . import routes_announcements # noqa: E402, F401
|
||||
from . import routes_company_edit # noqa: E402, F401
|
||||
|
||||
246
blueprints/public/routes_company_edit.py
Normal file
246
blueprints/public/routes_company_edit.py
Normal file
@ -0,0 +1,246 @@
|
||||
"""
|
||||
Company Edit Routes
|
||||
===================
|
||||
|
||||
Routes for editing company profiles by authorized users.
|
||||
"""
|
||||
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from flask_login import login_required, current_user
|
||||
from blueprints.public import bp
|
||||
from sqlalchemy import or_
|
||||
from database import SessionLocal, Company, CompanyContact, CompanySocialMedia, Category
|
||||
from utils.helpers import sanitize_input, sanitize_html, validate_email, ensure_url
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EMPLOYEE_COUNT_WHITELIST = ['1-5', '6-10', '11-25', '26-50', '51-100', '101-250', '250+', '']
|
||||
VALID_SOCIAL_PLATFORMS = ['facebook', 'linkedin', 'instagram', 'youtube', 'twitter', 'tiktok']
|
||||
EDITABLE_SOURCES = [None, 'manual_edit', 'manual']
|
||||
|
||||
|
||||
@bp.route('/firma/edytuj')
|
||||
@login_required
|
||||
def company_edit():
|
||||
"""Display the company profile edit form."""
|
||||
if not current_user.can_edit_company():
|
||||
flash('Nie masz uprawnień do edycji profilu firmy.', 'error')
|
||||
return redirect(url_for('public.dashboard'))
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).get(current_user.company_id)
|
||||
if not company:
|
||||
flash('Nie znaleziono firmy.', 'error')
|
||||
return redirect(url_for('public.dashboard'))
|
||||
|
||||
categories = db.query(Category).order_by(Category.name).all()
|
||||
|
||||
contacts = db.query(CompanyContact).filter_by(
|
||||
company_id=company.id
|
||||
).order_by(
|
||||
CompanyContact.contact_type,
|
||||
CompanyContact.is_primary.desc()
|
||||
).all()
|
||||
|
||||
social_media = db.query(CompanySocialMedia).filter_by(
|
||||
company_id=company.id
|
||||
).all()
|
||||
|
||||
permissions = {
|
||||
'description': current_user.can_edit_company_field('description'),
|
||||
'services': current_user.can_edit_company_field('services'),
|
||||
'contacts': current_user.can_edit_company_field('contacts'),
|
||||
'social': current_user.can_edit_company_field('social'),
|
||||
}
|
||||
|
||||
editable_contacts = [c for c in contacts if c.source in EDITABLE_SOURCES]
|
||||
|
||||
return render_template(
|
||||
'company_edit.html',
|
||||
company=company,
|
||||
categories=categories,
|
||||
contacts=editable_contacts,
|
||||
all_contacts=contacts,
|
||||
social_media=social_media,
|
||||
permissions=permissions,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/firma/edytuj', methods=['POST'])
|
||||
@login_required
|
||||
def company_edit_save():
|
||||
"""Save company profile edits."""
|
||||
if not current_user.can_edit_company():
|
||||
flash('Nie masz uprawnień do edycji profilu firmy.', 'error')
|
||||
return redirect(url_for('public.dashboard'))
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).get(current_user.company_id)
|
||||
if not company:
|
||||
flash('Nie znaleziono firmy.', 'error')
|
||||
return redirect(url_for('public.dashboard'))
|
||||
|
||||
active_tab = request.form.get('active_tab', 'description')
|
||||
|
||||
if active_tab == 'description' and current_user.can_edit_company_field('description'):
|
||||
_save_description(db, company)
|
||||
|
||||
elif active_tab == 'services' and current_user.can_edit_company_field('services'):
|
||||
_save_services(company)
|
||||
|
||||
elif active_tab == 'contacts' and current_user.can_edit_company_field('contacts'):
|
||||
_save_contacts(db, company)
|
||||
|
||||
elif active_tab == 'social' and current_user.can_edit_company_field('social'):
|
||||
_save_social_media(db, company)
|
||||
|
||||
db.commit()
|
||||
flash('Dane firmy zostały zaktualizowane.', 'success')
|
||||
return redirect(url_for('public.company_detail', company_id=company.id))
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error saving company edit for company_id={current_user.company_id}: {e}")
|
||||
flash('Wystąpił błąd podczas zapisywania zmian. Spróbuj ponownie.', 'error')
|
||||
return redirect(url_for('public.company_edit'))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _save_description(db, company):
|
||||
"""Save description tab fields."""
|
||||
company.description_short = sanitize_input(
|
||||
request.form.get('description_short', ''), max_length=500
|
||||
) or None
|
||||
company.description_full = sanitize_html(
|
||||
request.form.get('description_full', '')
|
||||
) or None
|
||||
company.founding_history = sanitize_html(
|
||||
request.form.get('founding_history', '')
|
||||
) or None
|
||||
company.core_values = sanitize_html(
|
||||
request.form.get('core_values', '')
|
||||
) or None
|
||||
|
||||
category_id_raw = request.form.get('category_id', '')
|
||||
if category_id_raw:
|
||||
try:
|
||||
category_id = int(category_id_raw)
|
||||
category = db.query(Category).get(category_id)
|
||||
if category:
|
||||
company.category_id = category_id
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
else:
|
||||
company.category_id = None
|
||||
|
||||
|
||||
def _save_services(company):
|
||||
"""Save services tab fields."""
|
||||
company.services_offered = sanitize_html(
|
||||
request.form.get('services_offered', '')
|
||||
) or None
|
||||
company.technologies_used = sanitize_html(
|
||||
request.form.get('technologies_used', '')
|
||||
) or None
|
||||
company.operational_area = sanitize_input(
|
||||
request.form.get('operational_area', ''), max_length=500
|
||||
) or None
|
||||
company.languages_offered = sanitize_input(
|
||||
request.form.get('languages_offered', ''), max_length=200
|
||||
) or None
|
||||
|
||||
employee_count = request.form.get('employee_count_range', '')
|
||||
if employee_count in EMPLOYEE_COUNT_WHITELIST:
|
||||
company.employee_count_range = employee_count or None
|
||||
|
||||
|
||||
def _save_contacts(db, company):
|
||||
"""Save contacts tab fields."""
|
||||
website_raw = sanitize_input(request.form.get('website', ''), max_length=500)
|
||||
company.website = ensure_url(website_raw) if website_raw else None
|
||||
|
||||
email_raw = sanitize_input(request.form.get('email', ''), max_length=255)
|
||||
if email_raw:
|
||||
if validate_email(email_raw):
|
||||
company.email = email_raw
|
||||
else:
|
||||
company.email = None
|
||||
|
||||
phone_raw = sanitize_input(request.form.get('phone', ''), max_length=50)
|
||||
company.phone = phone_raw or None
|
||||
|
||||
company.address_street = sanitize_input(
|
||||
request.form.get('address_street', ''), max_length=255
|
||||
) or None
|
||||
company.address_city = sanitize_input(
|
||||
request.form.get('address_city', ''), max_length=100
|
||||
) or None
|
||||
company.address_postal = sanitize_input(
|
||||
request.form.get('address_postal', ''), max_length=10
|
||||
) or None
|
||||
|
||||
# Delete existing editable contacts (source is NULL, 'manual_edit', or 'manual')
|
||||
db.query(CompanyContact).filter(
|
||||
CompanyContact.company_id == company.id,
|
||||
or_(
|
||||
CompanyContact.source.in_(['manual_edit', 'manual']),
|
||||
CompanyContact.source.is_(None)
|
||||
)
|
||||
).delete(synchronize_session='fetch')
|
||||
|
||||
# Add new contacts from form
|
||||
contact_types = request.form.getlist('contact_types[]')
|
||||
contact_values = request.form.getlist('contact_values[]')
|
||||
contact_purposes = request.form.getlist('contact_purposes[]')
|
||||
|
||||
for i, value in enumerate(contact_values):
|
||||
value = sanitize_input(value, max_length=255)
|
||||
if not value:
|
||||
continue
|
||||
contact_type = sanitize_input(contact_types[i], max_length=20) if i < len(contact_types) else 'phone'
|
||||
purpose = sanitize_input(contact_purposes[i], max_length=100) if i < len(contact_purposes) else ''
|
||||
db.add(CompanyContact(
|
||||
company_id=company.id,
|
||||
contact_type=contact_type,
|
||||
value=value,
|
||||
purpose=purpose or None,
|
||||
source='manual_edit',
|
||||
))
|
||||
|
||||
|
||||
def _save_social_media(db, company):
|
||||
"""Save social media tab fields."""
|
||||
# Delete existing editable social media (source is NULL, 'manual_edit', or 'manual')
|
||||
db.query(CompanySocialMedia).filter(
|
||||
CompanySocialMedia.company_id == company.id,
|
||||
or_(
|
||||
CompanySocialMedia.source.in_(['manual_edit', 'manual']),
|
||||
CompanySocialMedia.source.is_(None)
|
||||
)
|
||||
).delete(synchronize_session='fetch')
|
||||
|
||||
# Add new social media from form
|
||||
social_platforms = request.form.getlist('social_platforms[]')
|
||||
social_urls = request.form.getlist('social_urls[]')
|
||||
|
||||
for i, url in enumerate(social_urls):
|
||||
url = sanitize_input(url, max_length=500)
|
||||
if not url:
|
||||
continue
|
||||
platform = social_platforms[i] if i < len(social_platforms) else ''
|
||||
if platform not in VALID_SOCIAL_PLATFORMS:
|
||||
continue
|
||||
db.add(CompanySocialMedia(
|
||||
company_id=company.id,
|
||||
platform=platform,
|
||||
url=ensure_url(url),
|
||||
source='manual_edit',
|
||||
verified_at=datetime.now(),
|
||||
))
|
||||
@ -572,8 +572,17 @@
|
||||
|
||||
<h1 class="company-name">{{ company.name }}</h1>
|
||||
|
||||
<!-- AI Enrichment Button -->
|
||||
<div style="margin: var(--spacing-md) 0;">
|
||||
<!-- AI Enrichment Button + Edit Profile -->
|
||||
<div style="margin: var(--spacing-md) 0; display: flex; gap: var(--spacing-sm); flex-wrap: wrap; align-items: center;">
|
||||
{% if can_enrich %}
|
||||
<a href="{{ url_for('company_edit') }}" class="ai-enrich-btn" style="text-decoration: none;">
|
||||
<svg width="16" height="16" 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>
|
||||
Edytuj profil
|
||||
</a>
|
||||
{% endif %}
|
||||
<button
|
||||
id="aiEnrichBtn"
|
||||
class="ai-enrich-btn"
|
||||
|
||||
485
templates/company_edit.html
Normal file
485
templates/company_edit.html
Normal file
@ -0,0 +1,485 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Edytuj profil — {{ company.name }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.company-edit-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
.company-edit-header {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
.company-edit-header h1 {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-2xl, 1.5rem);
|
||||
}
|
||||
.company-edit-subtitle {
|
||||
color: var(--text-secondary);
|
||||
margin: var(--spacing-xs) 0 0;
|
||||
font-size: var(--font-size-lg, 1.1rem);
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.edit-tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
border-bottom: 2px solid var(--border-color, #e5e7eb);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.edit-tab {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-base, 0.95rem);
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
.edit-tab:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.edit-tab.active {
|
||||
color: var(--primary, #2563eb);
|
||||
border-bottom-color: var(--primary, #2563eb);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Tab content */
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Disabled fieldset */
|
||||
fieldset[disabled] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
fieldset {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Textarea styling */
|
||||
textarea.form-input {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Contact/Social rows */
|
||||
.contact-row, .social-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
.contact-type-select, .social-platform-select {
|
||||
flex: 0 0 130px;
|
||||
}
|
||||
.contact-value-input, .social-url-input {
|
||||
flex: 1;
|
||||
}
|
||||
.contact-purpose-input {
|
||||
flex: 0 0 160px;
|
||||
}
|
||||
.btn-remove-contact {
|
||||
flex: 0 0 36px;
|
||||
height: 36px;
|
||||
border: 1px solid var(--border-color, #e5e7eb);
|
||||
background: var(--bg-secondary, #f9fafb);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-remove-contact:hover {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
border-color: #fca5a5;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-sm, 0.85rem);
|
||||
}
|
||||
|
||||
/* Contacts section */
|
||||
.contacts-section {
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid var(--border-color, #e5e7eb);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.contact-row, .social-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.contact-type-select, .social-platform-select {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.contact-purpose-input {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="company-edit-container">
|
||||
<div class="company-edit-header">
|
||||
<h1>Edytuj profil firmy</h1>
|
||||
<p class="company-edit-subtitle">{{ company.name }}</p>
|
||||
<a href="{{ url_for('public.company_detail', company_id=company.id) }}" class="btn btn-outline" style="margin-top: var(--spacing-sm);">← Powrót do profilu</a>
|
||||
</div>
|
||||
|
||||
<!-- Tab navigation -->
|
||||
<div class="edit-tabs">
|
||||
<button type="button" class="edit-tab active" data-tab="description">Opis</button>
|
||||
<button type="button" class="edit-tab" data-tab="services">Usługi</button>
|
||||
<button type="button" class="edit-tab" data-tab="contacts">Kontakt</button>
|
||||
<button type="button" class="edit-tab" data-tab="social">Social Media</button>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('public.company_edit_save') }}" id="companyEditForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="active_tab" id="activeTabInput" value="description">
|
||||
|
||||
<!-- TAB 1: Opis -->
|
||||
<div class="tab-content active" id="tab-description">
|
||||
{% if not permissions.description %}
|
||||
<div class="info-box" style="background: #fff3cd; border-left: 4px solid #ffc107;">
|
||||
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset {% if not permissions.description %}disabled{% endif %}>
|
||||
<!-- category_id select -->
|
||||
<div class="form-group">
|
||||
<label for="category_id" class="form-label">Kategoria</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 %}
|
||||
<optgroup label="{{ cat.name }}">
|
||||
{% for sub in categories if sub.parent_id == cat.id %}
|
||||
<option value="{{ sub.id }}" {% if company.category_id == sub.id %}selected{% endif %}>{{ sub.name }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{# Also show top-level categories as direct options if they have no children #}
|
||||
{% for cat in categories %}
|
||||
{% if cat.parent_id is none %}
|
||||
{% set has_children = categories | selectattr('parent_id', 'equalto', cat.id) | list | length > 0 %}
|
||||
{% if not has_children %}
|
||||
<option value="{{ cat.id }}" {% if company.category_id == cat.id %}selected{% endif %}>{{ cat.name }}</option>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- description_short textarea -->
|
||||
<div class="form-group">
|
||||
<label for="description_short" class="form-label">Krótki opis</label>
|
||||
<textarea id="description_short" name="description_short" class="form-input" rows="3" maxlength="500" placeholder="Krótki opis firmy (do 500 znaków)">{{ company.description_short or '' }}</textarea>
|
||||
<p class="form-help">Widoczny na liście firm i w wynikach wyszukiwania. Max 500 znaków.</p>
|
||||
</div>
|
||||
|
||||
<!-- description_full textarea -->
|
||||
<div class="form-group">
|
||||
<label for="description_full" class="form-label">Pełny opis</label>
|
||||
<textarea id="description_full" name="description_full" class="form-input" rows="8" placeholder="Szczegółowy opis działalności firmy...">{{ company.description_full or '' }}</textarea>
|
||||
<p class="form-help">Pełny opis na stronie profilu firmy. Możesz użyć formatowania HTML.</p>
|
||||
</div>
|
||||
|
||||
<!-- founding_history textarea -->
|
||||
<div class="form-group">
|
||||
<label for="founding_history" class="form-label">Historia firmy</label>
|
||||
<textarea id="founding_history" name="founding_history" class="form-input" rows="5" placeholder="Historia powstania i rozwoju firmy...">{{ company.founding_history or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- core_values textarea -->
|
||||
<div class="form-group">
|
||||
<label for="core_values" class="form-label">Wartości i misja</label>
|
||||
<textarea id="core_values" name="core_values" class="form-input" rows="4" placeholder="Kluczowe wartości i misja firmy...">{{ company.core_values or '' }}</textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- TAB 2: Uslugi -->
|
||||
<div class="tab-content" id="tab-services">
|
||||
{% if not permissions.services %}
|
||||
<div class="info-box" style="background: #fff3cd; border-left: 4px solid #ffc107;">
|
||||
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 for="services_offered" class="form-label">Oferowane usługi</label>
|
||||
<textarea id="services_offered" name="services_offered" class="form-input" rows="6" placeholder="Lista usług oferowanych przez firmę...">{{ company.services_offered or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="technologies_used" class="form-label">Technologie</label>
|
||||
<textarea id="technologies_used" name="technologies_used" class="form-input" rows="4" placeholder="Używane technologie i narzędzia...">{{ company.technologies_used or '' }}</textarea>
|
||||
</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, Pomorskie" maxlength="500">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="languages_offered" class="form-label">Języki</label>
|
||||
<input type="text" id="languages_offered" name="languages_offered" class="form-input" value="{{ company.languages_offered or '' }}" placeholder="np. Polski, Angielski" 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">
|
||||
<option value="">— Wybierz —</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 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- TAB 3: Kontakt -->
|
||||
<div class="tab-content" id="tab-contacts">
|
||||
{% if not permissions.contacts %}
|
||||
<div class="info-box" style="background: #fff3cd; border-left: 4px solid #ffc107;">
|
||||
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset {% if not permissions.contacts %}disabled{% endif %}>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="website" class="form-label">Strona WWW</label>
|
||||
<input type="url" id="website" name="website" class="form-input" value="{{ company.website or '' }}" placeholder="https://www.example.com">
|
||||
</div>
|
||||
<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@firma.pl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="phone" class="form-label">Telefon</label>
|
||||
<input type="tel" id="phone" name="phone" class="form-input" value="{{ company.phone or '' }}" placeholder="+48 123 456 789" maxlength="50">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<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. Przykladowa 1" maxlength="255">
|
||||
</div>
|
||||
</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">
|
||||
</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 section -->
|
||||
<div class="contacts-section">
|
||||
<h3 style="margin-top: var(--spacing-lg); margin-bottom: var(--spacing-sm); font-size: var(--font-size-lg);">Dodatkowe kontakty</h3>
|
||||
<p class="form-help" style="margin-bottom: var(--spacing-md);">Dodaj numery telefonów, emaile i inne dane kontaktowe.</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="Wartosc">
|
||||
<input type="text" name="contact_purposes[]" class="form-input contact-purpose-input" value="{{ contact.purpose or '' }}" placeholder="Cel (np. Biuro, Sprzedaz)">
|
||||
<button type="button" class="btn-remove-contact" onclick="this.parentElement.remove()" title="Usun">✕</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline btn-sm" id="addContactBtn">+ Dodaj kontakt</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- TAB 4: Social Media -->
|
||||
<div class="tab-content" id="tab-social">
|
||||
{% if not permissions.social %}
|
||||
<div class="info-box" style="background: #fff3cd; border-left: 4px solid #ffc107;">
|
||||
Nie masz uprawnień do edycji tej sekcji. Skontaktuj się z managerem firmy.
|
||||
</div>
|
||||
{% endif %}
|
||||
<fieldset {% if not permissions.social %}disabled{% endif %}>
|
||||
<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://...">
|
||||
<button type="button" class="btn-remove-contact" onclick="this.parentElement.remove()" title="Usun">✕</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% 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="info-box" style="margin-top: var(--spacing-md);">
|
||||
<strong>Profile wykryte automatycznie</strong> (nie podlegają ręcznej edycji):
|
||||
<ul style="margin: var(--spacing-xs) 0 0 var(--spacing-md); padding: 0;">
|
||||
{% for sm in social_media %}
|
||||
{% if sm.source is not none and sm.source != 'manual_edit' and sm.source != 'manual' %}
|
||||
<li>{{ sm.platform | capitalize }}: <a href="{{ sm.url }}" target="_blank" rel="noopener">{{ sm.url | truncate(50) }}</a> <span style="color: var(--text-secondary); font-size: var(--font-size-sm);">({{ sm.source }})</span></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<button type="button" class="btn btn-outline btn-sm" id="addSocialBtn" style="margin-top: var(--spacing-md);">+ Dodaj profil</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Form actions -->
|
||||
<div class="form-actions" style="margin-top: var(--spacing-lg); padding-top: var(--spacing-md); border-top: 1px solid var(--border-color, #e5e7eb);">
|
||||
<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>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
// Company Edit - Tab switching and dynamic fields
|
||||
|
||||
(function() {
|
||||
// Tab switching
|
||||
var tabs = document.querySelectorAll('.edit-tab');
|
||||
var contents = document.querySelectorAll('.tab-content');
|
||||
var activeTabInput = document.getElementById('activeTabInput');
|
||||
|
||||
tabs.forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
var targetTab = this.getAttribute('data-tab');
|
||||
|
||||
// Update tab buttons
|
||||
tabs.forEach(function(t) { t.classList.remove('active'); });
|
||||
this.classList.add('active');
|
||||
|
||||
// Update content
|
||||
contents.forEach(function(c) { c.classList.remove('active'); });
|
||||
document.getElementById('tab-' + targetTab).classList.add('active');
|
||||
|
||||
// Update hidden input
|
||||
activeTabInput.value = targetTab;
|
||||
});
|
||||
});
|
||||
|
||||
// Add Contact button
|
||||
var addContactBtn = document.getElementById('addContactBtn');
|
||||
if (addContactBtn) {
|
||||
addContactBtn.addEventListener('click', function() {
|
||||
var list = document.getElementById('contactsList');
|
||||
var row = document.createElement('div');
|
||||
row.className = 'contact-row';
|
||||
row.innerHTML = '<select name="contact_types[]" class="form-input contact-type-select">' +
|
||||
'<option value="phone">Telefon</option>' +
|
||||
'<option value="mobile">Kom\u00f3rka</option>' +
|
||||
'<option value="email">Email</option>' +
|
||||
'<option value="fax">Fax</option>' +
|
||||
'</select>' +
|
||||
'<input type="text" name="contact_values[]" class="form-input contact-value-input" placeholder="Warto\u015b\u0107">' +
|
||||
'<input type="text" name="contact_purposes[]" class="form-input contact-purpose-input" placeholder="Cel (np. Biuro, Sprzeda\u017c)">' +
|
||||
'<button type="button" class="btn-remove-contact" onclick="this.parentElement.remove()" title="Usu\u0144">\u2715</button>';
|
||||
list.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Add Social Media button
|
||||
var addSocialBtn = document.getElementById('addSocialBtn');
|
||||
if (addSocialBtn) {
|
||||
addSocialBtn.addEventListener('click', function() {
|
||||
var list = document.getElementById('socialList');
|
||||
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-contact" onclick="this.parentElement.remove()" title="Usu\u0144">\u2715</button>';
|
||||
list.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Client-side validation
|
||||
document.getElementById('companyEditForm').addEventListener('submit', function(e) {
|
||||
var emailField = document.getElementById('email');
|
||||
if (emailField && emailField.value) {
|
||||
var emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
if (!emailPattern.test(emailField.value)) {
|
||||
e.preventDefault();
|
||||
alert('Nieprawid\u0142owy format adresu email.');
|
||||
emailField.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var websiteField = document.getElementById('website');
|
||||
if (websiteField && websiteField.value) {
|
||||
var val = websiteField.value;
|
||||
if (val && !val.match(/^https?:\/\//)) {
|
||||
websiteField.value = 'https://' + val;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue
Block a user