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
v1.41.0 (Mar 7): "Nowe" badges on dashboard, event access labels, 4 events grid v1.42.0 (Mar 11): messaging overhaul - attachments, autocomplete, read receipts, thread view, color-coded status pills, redesigned inbox/sent v1.43.0 (Mar 12): "Co nowego" banner on homepage, privacy policy & ToS pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1757 lines
89 KiB
Python
1757 lines
89 KiB
Python
"""
|
|
Public Routes
|
|
=============
|
|
|
|
Public-facing routes: index, company profiles, search, events, new members,
|
|
connections map, release notes, dashboard.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta, date
|
|
|
|
from flask import render_template, request, redirect, url_for, flash, session, current_app, Response
|
|
from flask_login import login_required, current_user
|
|
from sqlalchemy import or_, func
|
|
|
|
from . import bp
|
|
from database import (
|
|
SessionLocal,
|
|
Company,
|
|
Category,
|
|
User,
|
|
CompanyRecommendation,
|
|
CompanyEvent,
|
|
CompanyDigitalMaturity,
|
|
CompanyWebsiteAnalysis,
|
|
CompanyQualityTracking,
|
|
CompanyWebsiteContent,
|
|
CompanyAIInsights,
|
|
CompanySocialMedia,
|
|
CompanyContact,
|
|
Person,
|
|
CompanyPerson,
|
|
GBPAudit,
|
|
ITAudit,
|
|
CompanyPKD,
|
|
NordaEvent,
|
|
EventAttendee,
|
|
AIChatConversation,
|
|
AIChatMessage,
|
|
UserSession,
|
|
SearchQuery,
|
|
MembershipApplication,
|
|
Announcement,
|
|
ForumTopic,
|
|
Classified,
|
|
UserNotification,
|
|
UserCompany,
|
|
)
|
|
from utils.helpers import sanitize_input
|
|
from extensions import limiter
|
|
from search_service import search_companies
|
|
|
|
# Logger
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Global constant (same as in app.py)
|
|
COMPANY_COUNT_MARKETING = 150
|
|
|
|
|
|
@bp.route('/')
|
|
def index():
|
|
"""Homepage - landing page for guests, company directory for logged in users"""
|
|
if not current_user.is_authenticated:
|
|
# Landing page for guests with public company tiles
|
|
db = SessionLocal()
|
|
try:
|
|
companies = db.query(Company).filter_by(status='active').order_by(Company.name).all()
|
|
total_companies = len(companies)
|
|
total_categories = db.query(Category).count()
|
|
|
|
# Load social media for all companies in one query
|
|
company_social = {}
|
|
if companies:
|
|
social_records = db.query(CompanySocialMedia).filter(
|
|
CompanySocialMedia.company_id.in_([c.id for c in companies])
|
|
).all()
|
|
for sm in social_records:
|
|
company_social.setdefault(sm.company_id, []).append(sm)
|
|
|
|
return render_template(
|
|
'landing.html',
|
|
companies=companies,
|
|
company_social=company_social,
|
|
total_companies=total_companies,
|
|
total_categories=total_categories
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
# Company directory for logged in users
|
|
db = SessionLocal()
|
|
try:
|
|
from datetime import date
|
|
companies = db.query(Company).filter_by(status='active').order_by(Company.name).all()
|
|
|
|
# Get hierarchical categories (main categories with subcategories)
|
|
main_categories = db.query(Category).filter(
|
|
Category.parent_id.is_(None)
|
|
).order_by(Category.display_order, Category.name).all()
|
|
|
|
# All categories for backwards compatibility
|
|
categories = db.query(Category).order_by(Category.sort_order).all()
|
|
|
|
total_companies = len(companies)
|
|
total_categories = len([c for c in categories if db.query(Company).filter_by(category_id=c.id).count() > 0])
|
|
|
|
# Najbliższe wydarzenia (dla bannera "Kto weźmie udział?")
|
|
all_upcoming = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= date.today()
|
|
).order_by(NordaEvent.event_date.asc()).all()
|
|
|
|
upcoming_events = []
|
|
for event in all_upcoming:
|
|
if event.can_user_view(current_user):
|
|
registered = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event.id,
|
|
EventAttendee.user_id == current_user.id
|
|
).first() is not None
|
|
can_attend = event.can_user_attend(current_user)
|
|
upcoming_events.append({
|
|
'event': event,
|
|
'user_registered': registered,
|
|
'user_can_attend': can_attend,
|
|
})
|
|
if len(upcoming_events) >= 4:
|
|
break
|
|
|
|
# Backward compat — next_event used by other parts
|
|
next_event = upcoming_events[0]['event'] if upcoming_events else None
|
|
|
|
# ZOPK Knowledge facts — admin only widget
|
|
zopk_facts = []
|
|
if current_user.is_admin:
|
|
try:
|
|
from database import ZOPKKnowledgeFact, ZOPKNews
|
|
zopk_facts = db.query(ZOPKKnowledgeFact).join(ZOPKNews).filter(
|
|
ZOPKKnowledgeFact.confidence_score >= 0.5
|
|
).order_by(func.random()).limit(3).all()
|
|
except Exception:
|
|
pass
|
|
|
|
# Sprawdź czy użytkownik ma deklarację członkowską w toku
|
|
pending_application = None
|
|
if not current_user.is_norda_member and not current_user.company_id:
|
|
pending_application = db.query(MembershipApplication).filter(
|
|
MembershipApplication.user_id == current_user.id,
|
|
MembershipApplication.status.in_(['draft', 'submitted', 'under_review', 'pending_user_approval', 'changes_requested'])
|
|
).first()
|
|
|
|
# Latest release for "Co nowego" banner
|
|
all_releases = _get_releases()
|
|
latest_release = all_releases[0] if all_releases else None
|
|
|
|
return render_template(
|
|
'index.html',
|
|
companies=companies,
|
|
categories=categories,
|
|
main_categories=main_categories,
|
|
total_companies=total_companies,
|
|
total_categories=total_categories,
|
|
next_event=next_event,
|
|
upcoming_events=upcoming_events,
|
|
pending_application=pending_application,
|
|
zopk_facts=zopk_facts,
|
|
latest_release=latest_release
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/company/<int:company_id>')
|
|
def company_detail(company_id):
|
|
"""Company detail page - requires login and NORDA membership"""
|
|
# Sprawdź czy użytkownik jest zalogowany
|
|
if not current_user.is_authenticated:
|
|
flash('Zaloguj się, aby zobaczyć szczegóły firmy.', 'warning')
|
|
return redirect(url_for('auth.login'))
|
|
|
|
# Sprawdź czy użytkownik jest członkiem NORDA (ma firmę lub flagę is_norda_member)
|
|
if not current_user.is_norda_member and not current_user.company_id:
|
|
flash('Dostęp do katalog firm jest dostępny tylko dla członków Izby NORDA. Złóż deklarację członkowską, aby uzyskać pełny dostęp.', 'info')
|
|
return redirect(url_for('membership.apply'))
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
company = db.query(Company).filter_by(id=company_id).first()
|
|
if not company:
|
|
flash('Firma nie znaleziona.', 'error')
|
|
return redirect(url_for('index'))
|
|
|
|
# Load digital maturity data if available
|
|
maturity_data = db.query(CompanyDigitalMaturity).filter_by(company_id=company_id).first()
|
|
# Get all website analyses sorted by audit date (latest first)
|
|
all_analyses = db.query(CompanyWebsiteAnalysis).filter_by(
|
|
company_id=company_id
|
|
).order_by(CompanyWebsiteAnalysis.seo_audited_at.desc()).all()
|
|
|
|
# Build dict: company_website_id → analysis (latest first wins)
|
|
analyses_by_website = {}
|
|
website_analysis = None # backward compat: keep "main" analysis
|
|
for a in all_analyses:
|
|
if a.company_website_id and a.company_website_id not in analyses_by_website:
|
|
analyses_by_website[a.company_website_id] = a
|
|
if website_analysis is None:
|
|
website_analysis = a # first = latest = main analysis
|
|
|
|
# Load quality tracking data
|
|
quality_data = db.query(CompanyQualityTracking).filter_by(company_id=company_id).first()
|
|
|
|
# Load company events (latest 10)
|
|
events = db.query(CompanyEvent).filter_by(company_id=company_id).order_by(
|
|
CompanyEvent.event_date.desc(),
|
|
CompanyEvent.created_at.desc()
|
|
).limit(10).all()
|
|
|
|
# Load website scraping data (most recent)
|
|
website_content = db.query(CompanyWebsiteContent).filter_by(company_id=company_id).order_by(
|
|
CompanyWebsiteContent.scraped_at.desc()
|
|
).first()
|
|
|
|
# Load AI insights
|
|
ai_insights = db.query(CompanyAIInsights).filter_by(company_id=company_id).first()
|
|
|
|
# Load social media profiles
|
|
social_media = db.query(CompanySocialMedia).filter_by(company_id=company_id).all()
|
|
|
|
# Load company contacts (phones, emails with sources)
|
|
contacts = db.query(CompanyContact).filter_by(company_id=company_id).order_by(
|
|
CompanyContact.contact_type,
|
|
CompanyContact.is_primary.desc()
|
|
).all()
|
|
|
|
# Load recommendations (approved only, with recommender details)
|
|
recommendations = db.query(CompanyRecommendation).filter_by(
|
|
company_id=company_id,
|
|
status='approved'
|
|
).join(User, CompanyRecommendation.user_id == User.id).order_by(
|
|
CompanyRecommendation.created_at.desc()
|
|
).all()
|
|
|
|
# Load people connected to company (zarząd, wspólnicy, prokurenci)
|
|
people = db.query(CompanyPerson).filter_by(
|
|
company_id=company_id
|
|
).join(Person, CompanyPerson.person_id == Person.id).order_by(
|
|
CompanyPerson.role_category,
|
|
Person.nazwisko
|
|
).all()
|
|
|
|
# Load portal users linked to this company (for "Osoby kontaktowe" section)
|
|
company_users = db.query(UserCompany).filter_by(
|
|
company_id=company_id
|
|
).join(User, UserCompany.user_id == User.id).filter(
|
|
User.is_active == True
|
|
).order_by(
|
|
UserCompany.role.desc(), # MANAGER first, then EMPLOYEE
|
|
User.name
|
|
).all()
|
|
# Eager-load user attributes before session close
|
|
for uc in company_users:
|
|
_ = uc.user.name, uc.user.email, uc.user.phone, uc.user.is_norda_member
|
|
_ = uc.user.privacy_show_phone, uc.user.privacy_show_email
|
|
_ = uc.user.contact_prefer_email, uc.user.contact_prefer_phone
|
|
_ = uc.user.contact_prefer_portal, uc.user.contact_note
|
|
|
|
# Load GBP audit (most recent)
|
|
gbp_audit = db.query(GBPAudit).filter_by(
|
|
company_id=company_id
|
|
).order_by(GBPAudit.audit_date.desc()).first()
|
|
|
|
# Load IT audit (most recent)
|
|
it_audit = db.query(ITAudit).filter_by(
|
|
company_id=company_id
|
|
).order_by(ITAudit.audit_date.desc()).first()
|
|
|
|
# Load PKD codes (all - primary first)
|
|
pkd_codes = db.query(CompanyPKD).filter_by(
|
|
company_id=company_id
|
|
).order_by(CompanyPKD.is_primary.desc(), CompanyPKD.pkd_code).all()
|
|
|
|
# Check if current user can enrich company data (user with company edit rights)
|
|
can_enrich = False
|
|
can_edit_profile = False
|
|
company_managers = []
|
|
if current_user.is_authenticated:
|
|
can_enrich = current_user.can_edit_company(company.id)
|
|
can_edit_profile = current_user.can_manage_company(company.id)
|
|
|
|
# If user is a member but not manager, load managers for contact modal
|
|
if not can_edit_profile:
|
|
is_company_member = any(
|
|
assoc.company_id == company.id
|
|
for assoc in (current_user.company_associations or [])
|
|
) or current_user.company_id == company.id
|
|
|
|
if is_company_member:
|
|
company_managers = db.query(User).join(UserCompany).filter(
|
|
UserCompany.company_id == company.id,
|
|
UserCompany.role == 'MANAGER'
|
|
).all()
|
|
for m in company_managers:
|
|
_ = m.name, m.email # force-load before session close
|
|
|
|
# For child brands — inherit NIP from parent for display/enrichment
|
|
effective_nip = company.nip
|
|
if not effective_nip and company.parent_company_id:
|
|
parent = db.query(Company).filter_by(id=company.parent_company_id).first()
|
|
if parent:
|
|
effective_nip = parent.nip
|
|
|
|
return render_template('company_detail.html',
|
|
company=company,
|
|
company_id=company.id, # For analytics conversion tracking
|
|
effective_nip=effective_nip,
|
|
maturity_data=maturity_data,
|
|
website_analysis=website_analysis,
|
|
analyses_by_website=analyses_by_website,
|
|
quality_data=quality_data,
|
|
events=events,
|
|
website_content=website_content,
|
|
ai_insights=ai_insights,
|
|
social_media=social_media,
|
|
contacts=contacts,
|
|
recommendations=recommendations,
|
|
people=people,
|
|
company_users=company_users,
|
|
gbp_audit=gbp_audit,
|
|
it_audit=it_audit,
|
|
pkd_codes=pkd_codes,
|
|
can_enrich=can_enrich,
|
|
can_edit_profile=can_edit_profile,
|
|
company_managers=company_managers,
|
|
is_admin=current_user.is_authenticated and current_user.is_admin
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/company/<slug>')
|
|
def company_detail_by_slug(slug):
|
|
"""Company detail page by slug - requires login"""
|
|
db = SessionLocal()
|
|
try:
|
|
company = db.query(Company).filter_by(slug=slug).first()
|
|
if not company:
|
|
flash('Firma nie znaleziona.', 'error')
|
|
return redirect(url_for('index'))
|
|
# Redirect to canonical int ID route
|
|
return redirect(url_for('company_detail', company_id=company.id))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/osoba/<int:person_id>')
|
|
def person_detail(person_id):
|
|
"""Person detail page - shows registry data and portal data if available"""
|
|
db = SessionLocal()
|
|
try:
|
|
# Get person with their company relationships
|
|
person = db.query(Person).filter_by(id=person_id).first()
|
|
if not person:
|
|
flash('Osoba nie znaleziona.', 'error')
|
|
return redirect(url_for('index'))
|
|
|
|
# Get company roles with company details (only active companies)
|
|
company_roles = db.query(CompanyPerson).filter_by(
|
|
person_id=person_id
|
|
).join(Company, CompanyPerson.company_id == Company.id).filter(
|
|
Company.status == 'active'
|
|
).order_by(
|
|
CompanyPerson.role_category,
|
|
Company.name
|
|
).all()
|
|
|
|
# Try to find matching user account by name (for portal data)
|
|
# This is a simple match - in production might need more sophisticated matching
|
|
portal_user = None
|
|
name_parts = person.full_name().upper().split()
|
|
if len(name_parts) >= 2:
|
|
# Try to find user where first/last name matches
|
|
potential_users = db.query(User).filter(
|
|
User.name.isnot(None)
|
|
).all()
|
|
for u in potential_users:
|
|
if u.name:
|
|
user_name_parts = u.name.upper().split()
|
|
# Check if at least first and last name match
|
|
if len(user_name_parts) >= 2:
|
|
if (user_name_parts[-1] in name_parts and # Last name match
|
|
any(part in user_name_parts for part in name_parts[:-1])): # First name match
|
|
portal_user = u
|
|
break
|
|
|
|
return render_template('person_detail.html',
|
|
person=person,
|
|
company_roles=company_roles,
|
|
portal_user=portal_user
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/company/<slug>/recommend', methods=['GET', 'POST'])
|
|
def company_recommend(slug):
|
|
"""Create recommendation for a company - requires login"""
|
|
db = SessionLocal()
|
|
try:
|
|
# Get company
|
|
company = db.query(Company).filter_by(slug=slug).first()
|
|
if not company:
|
|
flash('Firma nie znaleziona.', 'error')
|
|
return redirect(url_for('index'))
|
|
|
|
# Handle POST (form submission)
|
|
if request.method == 'POST':
|
|
recommendation_text = request.form.get('recommendation_text', '').strip()
|
|
service_category = sanitize_input(request.form.get('service_category', ''), 200)
|
|
show_contact = request.form.get('show_contact') == '1'
|
|
|
|
# Validation
|
|
if not recommendation_text or len(recommendation_text) < 50:
|
|
flash('Rekomendacja musi mieć co najmniej 50 znaków.', 'error')
|
|
return render_template('company/recommend.html', company=company)
|
|
|
|
if len(recommendation_text) > 2000:
|
|
flash('Rekomendacja może mieć maksymalnie 2000 znaków.', 'error')
|
|
return render_template('company/recommend.html', company=company)
|
|
|
|
# Prevent self-recommendation
|
|
if current_user.company_id == company.id:
|
|
flash('Nie możesz polecać własnej firmy.', 'error')
|
|
return redirect(url_for('company_detail', company_id=company.id))
|
|
|
|
# Check for duplicate (user already recommended this company)
|
|
existing = db.query(CompanyRecommendation).filter_by(
|
|
user_id=current_user.id,
|
|
company_id=company.id
|
|
).first()
|
|
|
|
if existing:
|
|
flash('Już poleciłeś tę firmę. Możesz edytować swoją wcześniejszą rekomendację.', 'error')
|
|
return redirect(url_for('company_detail', company_id=company.id))
|
|
|
|
# Create recommendation
|
|
recommendation = CompanyRecommendation(
|
|
company_id=company.id,
|
|
user_id=current_user.id,
|
|
recommendation_text=recommendation_text,
|
|
service_category=service_category if service_category else None,
|
|
show_contact=show_contact,
|
|
status='pending'
|
|
)
|
|
db.add(recommendation)
|
|
db.commit()
|
|
|
|
flash('Dziękujemy! Twoja rekomendacja została przesłana i oczekuje na moderację.', 'success')
|
|
return redirect(url_for('company_detail', company_id=company.id))
|
|
|
|
# Handle GET (show form)
|
|
return render_template('company/recommend.html', company=company)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/search')
|
|
@login_required
|
|
def search():
|
|
"""Search companies and people with advanced matching - requires login"""
|
|
query = request.args.get('q', '')
|
|
category_id = request.args.get('category', type=int)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
# Use new SearchService with synonym expansion, NIP/REGON lookup, and fuzzy matching
|
|
results = search_companies(db, query, category_id, limit=50)
|
|
|
|
# Extract companies from SearchResult objects
|
|
companies = [r.company for r in results]
|
|
|
|
# Log search to analytics (SearchQuery table)
|
|
search_query_id = None
|
|
if query:
|
|
try:
|
|
analytics_session_id = session.get('analytics_session_id')
|
|
session_db_id = None
|
|
if analytics_session_id:
|
|
user_session = db.query(UserSession).filter_by(session_id=analytics_session_id).first()
|
|
if user_session:
|
|
session_db_id = user_session.id
|
|
|
|
search_query = SearchQuery(
|
|
session_id=session_db_id,
|
|
user_id=current_user.id if current_user.is_authenticated else None,
|
|
query=query[:500],
|
|
query_normalized=query.lower().strip()[:500],
|
|
results_count=len(companies),
|
|
has_results=len(companies) > 0,
|
|
search_type='main',
|
|
filters_used={'category_id': category_id} if category_id else None
|
|
)
|
|
db.add(search_query)
|
|
db.commit()
|
|
search_query_id = search_query.id
|
|
except Exception as e:
|
|
logger.error(f"Search logging error: {e}")
|
|
db.rollback()
|
|
search_query_id = None
|
|
|
|
# For debugging/analytics - log search stats
|
|
if query:
|
|
match_types = {}
|
|
for r in results:
|
|
match_types[r.match_type] = match_types.get(r.match_type, 0) + 1
|
|
logger.info(f"Search '{query}': {len(companies)} results, types: {match_types}")
|
|
|
|
# Search people by name (partial match)
|
|
people_results = []
|
|
if query and len(query) >= 2:
|
|
q = f"%{query}%"
|
|
people_results = db.query(Person).filter(
|
|
or_(
|
|
Person.imiona.ilike(q),
|
|
Person.nazwisko.ilike(q),
|
|
func.concat(Person.imiona, ' ', Person.nazwisko).ilike(q)
|
|
)
|
|
).limit(20).all()
|
|
|
|
# For each person, get their company connections count
|
|
for person in people_results:
|
|
person.company_count = len(set(
|
|
r.company_id for r in person.company_roles
|
|
if r.company and r.company.status == 'active'
|
|
))
|
|
|
|
logger.info(f"Search '{query}': {len(people_results)} people found")
|
|
|
|
return render_template(
|
|
'search_results.html',
|
|
companies=companies,
|
|
people=people_results,
|
|
query=query,
|
|
category_id=category_id,
|
|
result_count=len(companies),
|
|
search_query_id=search_query_id
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/aktualnosci')
|
|
@login_required
|
|
def events():
|
|
"""Company events and news - latest updates from member companies"""
|
|
event_type_filter = request.args.get('type', '')
|
|
company_id = request.args.get('company', type=int)
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = 20
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
# Build query
|
|
query = db.query(CompanyEvent).join(Company)
|
|
|
|
# Apply filters
|
|
if event_type_filter:
|
|
query = query.filter(CompanyEvent.event_type == event_type_filter)
|
|
if company_id:
|
|
query = query.filter(CompanyEvent.company_id == company_id)
|
|
|
|
# Order by date (newest first)
|
|
query = query.order_by(
|
|
CompanyEvent.event_date.desc(),
|
|
CompanyEvent.created_at.desc()
|
|
)
|
|
|
|
# Pagination
|
|
total_events = query.count()
|
|
events = query.limit(per_page).offset((page - 1) * per_page).all()
|
|
|
|
# Get companies with events for filter dropdown
|
|
companies_with_events = db.query(Company).join(CompanyEvent).distinct().order_by(Company.name).all()
|
|
|
|
# Event type statistics
|
|
event_types = db.query(
|
|
CompanyEvent.event_type,
|
|
func.count(CompanyEvent.id)
|
|
).group_by(CompanyEvent.event_type).all()
|
|
|
|
return render_template(
|
|
'events.html',
|
|
events=events,
|
|
companies_with_events=companies_with_events,
|
|
event_types=event_types,
|
|
event_type_filter=event_type_filter,
|
|
company_id=company_id,
|
|
page=page,
|
|
per_page=per_page,
|
|
total_events=total_events,
|
|
total_pages=(total_events + per_page - 1) // per_page
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/nowi-czlonkowie')
|
|
@login_required
|
|
def new_members():
|
|
"""Lista nowych firm członkowskich"""
|
|
days = request.args.get('days', 90, type=int)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
cutoff_date = datetime.now() - timedelta(days=days)
|
|
|
|
new_companies = db.query(Company).filter(
|
|
Company.status == 'active',
|
|
Company.created_at >= cutoff_date
|
|
).order_by(Company.created_at.desc()).all()
|
|
|
|
return render_template('new_members.html',
|
|
companies=new_companies,
|
|
days=days,
|
|
total=len(new_companies)
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/mapa-polaczen')
|
|
def connections_map():
|
|
"""Company-person connections visualization page"""
|
|
return render_template('connections_map.html')
|
|
|
|
|
|
@bp.route('/dashboard')
|
|
@login_required
|
|
def dashboard():
|
|
"""User dashboard"""
|
|
db = SessionLocal()
|
|
try:
|
|
# Get user's conversations
|
|
conversations = db.query(AIChatConversation).filter_by(
|
|
user_id=current_user.id
|
|
).order_by(AIChatConversation.updated_at.desc()).limit(10).all()
|
|
|
|
# Stats
|
|
total_conversations = db.query(AIChatConversation).filter_by(user_id=current_user.id).count()
|
|
total_messages = db.query(AIChatMessage).join(AIChatConversation).filter(
|
|
AIChatConversation.user_id == current_user.id
|
|
).count()
|
|
|
|
# New stats
|
|
unread_notifications = db.query(UserNotification).filter(
|
|
UserNotification.user_id == current_user.id,
|
|
UserNotification.is_read == False
|
|
).count()
|
|
|
|
upcoming_events_count = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= date.today()
|
|
).count()
|
|
|
|
user_forum_topics_count = db.query(ForumTopic).filter(
|
|
ForumTopic.author_id == current_user.id,
|
|
ForumTopic.is_deleted == False
|
|
).count()
|
|
|
|
# Check for membership application status
|
|
has_pending_application = False
|
|
has_draft_application = False
|
|
pending_application = None
|
|
|
|
try:
|
|
from database import MembershipApplication
|
|
pending_application = db.query(MembershipApplication).filter(
|
|
MembershipApplication.user_id == current_user.id,
|
|
MembershipApplication.status.in_(['submitted', 'under_review', 'changes_requested'])
|
|
).first()
|
|
has_pending_application = pending_application is not None
|
|
|
|
if not has_pending_application:
|
|
draft = db.query(MembershipApplication).filter(
|
|
MembershipApplication.user_id == current_user.id,
|
|
MembershipApplication.status == 'draft'
|
|
).first()
|
|
has_draft_application = draft is not None
|
|
except Exception:
|
|
pass # MembershipApplication table may not exist yet
|
|
|
|
# Load user's company associations (multi-company support)
|
|
from sqlalchemy.orm import joinedload
|
|
user_companies = db.query(UserCompany).options(
|
|
joinedload(UserCompany.company)
|
|
).filter_by(
|
|
user_id=current_user.id
|
|
).order_by(UserCompany.is_primary.desc(), UserCompany.created_at.asc()).all()
|
|
# Force-load company names before session closes
|
|
for uc in user_companies:
|
|
_ = uc.company.name if uc.company else None
|
|
|
|
# Managers map for companies where user is EMPLOYEE (for edit permission modal)
|
|
company_managers_map = {}
|
|
employee_company_ids = [uc.company_id for uc in user_companies if uc.role == 'EMPLOYEE']
|
|
if employee_company_ids:
|
|
managers = db.query(User, UserCompany.company_id).join(UserCompany).filter(
|
|
UserCompany.company_id.in_(employee_company_ids),
|
|
UserCompany.role == 'MANAGER'
|
|
).all()
|
|
for mgr, cid in managers:
|
|
company_managers_map.setdefault(cid, []).append({'name': mgr.name or 'Brak imienia', 'email': mgr.email or ''})
|
|
|
|
# Onboarding progress widget
|
|
from utils.onboarding import compute_onboarding_steps
|
|
onboarding = compute_onboarding_steps(current_user, user_companies, current_app.root_path)
|
|
|
|
# Widget 1: Upcoming events (3 nearest future events)
|
|
upcoming_events = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= date.today()
|
|
).order_by(NordaEvent.event_date.asc()).limit(3).all()
|
|
|
|
# Batch RSVP lookup for current user + attendee counts
|
|
user_event_ids = set()
|
|
event_attendee_counts = {}
|
|
if upcoming_events:
|
|
event_ids = [e.id for e in upcoming_events]
|
|
rsvps = db.query(EventAttendee.event_id).filter(
|
|
EventAttendee.event_id.in_(event_ids),
|
|
EventAttendee.user_id == current_user.id,
|
|
EventAttendee.status == 'confirmed'
|
|
).all()
|
|
user_event_ids = {r[0] for r in rsvps}
|
|
|
|
# Count confirmed attendees per event
|
|
from sqlalchemy import func
|
|
counts = db.query(EventAttendee.event_id, func.count(EventAttendee.id)).filter(
|
|
EventAttendee.event_id.in_(event_ids),
|
|
EventAttendee.status == 'confirmed'
|
|
).group_by(EventAttendee.event_id).all()
|
|
event_attendee_counts = {eid: cnt for eid, cnt in counts}
|
|
|
|
# Widget 2: Recent announcements (3 latest published, pinned first, not expired)
|
|
recent_announcements = db.query(Announcement).filter(
|
|
Announcement.status == 'published',
|
|
or_(Announcement.expires_at == None, Announcement.expires_at > datetime.now())
|
|
).order_by(Announcement.is_pinned.desc(), Announcement.published_at.desc()).limit(3).all()
|
|
|
|
# Widget 3: Recent forum topics (5 latest active)
|
|
recent_forum_topics = db.query(ForumTopic).filter(
|
|
ForumTopic.is_deleted == False
|
|
).order_by(ForumTopic.updated_at.desc()).limit(5).all()
|
|
|
|
# Widget 4: Recent classifieds (3 active, not test, not expired)
|
|
recent_classifieds = db.query(Classified).filter(
|
|
Classified.is_active == True,
|
|
Classified.is_test == False,
|
|
or_(Classified.expires_at == None, Classified.expires_at > datetime.now())
|
|
).order_by(Classified.created_at.desc()).limit(3).all()
|
|
|
|
# Widget 5: New companies (3 newest)
|
|
new_companies = db.query(Company).order_by(
|
|
Company.created_at.desc()
|
|
).limit(3).all()
|
|
|
|
# Admin KPI widget (only computed for admins)
|
|
admin_kpi = None
|
|
if current_user.can_access_admin_panel():
|
|
from sqlalchemy import func as sqlfunc
|
|
from database import UserSession, PageView, SecurityAlert
|
|
from datetime import timedelta as td
|
|
now = datetime.now()
|
|
week_ago = now - td(days=7)
|
|
prev_week = now - td(days=14)
|
|
|
|
# Active members (logged in last 7d)
|
|
active_7d = db.query(sqlfunc.count(sqlfunc.distinct(UserSession.user_id))).filter(
|
|
UserSession.started_at >= week_ago,
|
|
UserSession.user_id.isnot(None),
|
|
UserSession.is_bot == False
|
|
).scalar() or 0
|
|
|
|
# Total registered users
|
|
total_users = db.query(sqlfunc.count(User.id)).filter(User.is_active == True).scalar() or 0
|
|
|
|
# Sessions this week
|
|
sessions_7d = db.query(sqlfunc.count(UserSession.id)).filter(
|
|
UserSession.started_at >= week_ago,
|
|
UserSession.is_bot == False
|
|
).scalar() or 0
|
|
|
|
# Active problems (security alerts + locked accounts)
|
|
active_problems = db.query(sqlfunc.count(SecurityAlert.id)).filter(
|
|
SecurityAlert.created_at >= week_ago
|
|
).scalar() or 0
|
|
|
|
# Never logged in
|
|
never_logged = db.query(sqlfunc.count(User.id)).filter(
|
|
User.is_active == True,
|
|
User.last_login.is_(None)
|
|
).scalar() or 0
|
|
|
|
admin_kpi = {
|
|
'active_users': active_7d,
|
|
'total_users': total_users,
|
|
'active_pct': round(active_7d / total_users * 100) if total_users > 0 else 0,
|
|
'sessions': sessions_7d,
|
|
'problems': active_problems,
|
|
'never_logged': never_logged,
|
|
}
|
|
|
|
return render_template(
|
|
'dashboard.html',
|
|
admin_kpi=admin_kpi,
|
|
conversations=conversations,
|
|
total_conversations=total_conversations,
|
|
total_messages=total_messages,
|
|
has_pending_application=has_pending_application,
|
|
has_draft_application=has_draft_application,
|
|
pending_application=pending_application,
|
|
user_companies=user_companies,
|
|
unread_notifications=unread_notifications,
|
|
upcoming_events_count=upcoming_events_count,
|
|
user_forum_topics_count=user_forum_topics_count,
|
|
upcoming_events=upcoming_events,
|
|
user_event_ids=user_event_ids,
|
|
event_attendee_counts=event_attendee_counts,
|
|
recent_announcements=recent_announcements,
|
|
recent_forum_topics=recent_forum_topics,
|
|
recent_classifieds=recent_classifieds,
|
|
new_companies=new_companies,
|
|
company_managers_map=company_managers_map,
|
|
onboarding=onboarding
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def _get_releases():
|
|
"""Return list of all release notes entries."""
|
|
return [
|
|
{
|
|
'version': 'v1.43.0',
|
|
'date': '12 marca 2026',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Nowości na stronie głównej</strong> - pod nagłówkiem katalogu firm wyświetla się informacja o najnowszych zmianach na platformie z linkiem do pełnej listy',
|
|
'<strong>Polityka prywatności i Regulamin</strong> - platforma ma teraz własne dokumenty prawne dostępne w stopce strony',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.42.0',
|
|
'date': '11 marca 2026',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'★ <strong>Załączniki do wiadomości</strong> - do każdej wiadomości prywatnej można dołączyć pliki (dokumenty, zdjęcia, PDF) zarówno przy wysyłaniu nowej wiadomości, jak i przy odpowiedzi',
|
|
'★ <strong>Wyszukiwanie odbiorcy</strong> - zamiast rozwijanej listy wystarczy zacząć wpisywać imię, nazwisko lub nazwę firmy, a portal podpowie pasujące osoby',
|
|
'<strong>Podgląd profilu odbiorcy</strong> - po wybraniu odbiorcy wyświetla się karta z jego danymi i firmą, dzięki czemu wiadomo do kogo trafia wiadomość',
|
|
'<strong>Potwierdzenie odczytania</strong> - w skrzynce wysłanych widoczne jest, czy odbiorca przeczytał wiadomość',
|
|
'<strong>Historia korespondencji</strong> - po otwarciu wiadomości widoczny jest cały wątek rozmowy z daną osobą w jednym miejscu',
|
|
],
|
|
'improve': [
|
|
'<strong>Kolorowe statusy wiadomości</strong> - wiadomości nowe, wysłane i przeczytane oznaczone są kolorowymi etykietami dla łatwiejszego przeglądania',
|
|
'<strong>Nowy wygląd skrzynki</strong> - skrzynka odbiorcza i wysłane zostały przebudowane dla lepszej czytelności na komputerze i telefonie',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.41.0',
|
|
'date': '7 marca 2026',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'<strong>Oznaczenie „Nowe"</strong> - na pulpicie widoczne jest, które wydarzenia i ogłoszenia pojawiły się od ostatniego logowania',
|
|
'<strong>Oznaczenia dostępu do wydarzeń</strong> - wydarzenia z ograniczonym dostępem (tylko dla Rady lub ukryte) mają widoczne etykiety',
|
|
],
|
|
'improve': [
|
|
'<strong>Więcej wydarzeń na stronie głównej</strong> - zamiast dwóch wyświetlają się cztery najbliższe wydarzenia w przejrzystej siatce',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.40.0',
|
|
'date': '7 marca 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Sekcja Aktualności</strong> - ogłoszenia i informacje dla członków Izby z wyróżnionymi wydarzeniami, zdjęciami i załącznikami',
|
|
'<strong>Ulepszona sztuczna inteligencja</strong> - NordaGPT korzysta teraz z najnowszej generacji modeli Google Gemini 3.1 z lepszym rozumieniem pytań',
|
|
],
|
|
'improve': [
|
|
'<strong>Szybsze audyty SEO</strong> - analiza stron internetowych firm korzysta z wydajniejszego parsera HTML',
|
|
'<strong>Płynniejsze korzystanie z portalu</strong> - automatyczne odświeżanie powiadomień i wiadomości nie wpływa już na limit zapytań',
|
|
],
|
|
'fix': [
|
|
'<strong>Kalendarz</strong> - naprawiono błąd przy wpisaniu nieprawidłowego roku w adresie strony',
|
|
'<strong>Audyt SEO</strong> - naprawiono zapis danych o wykorzystaniu zapytań do Google PageSpeed',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.39.0',
|
|
'date': '23 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Powiadomienia e-mail o wiadomościach</strong> - po otrzymaniu wiadomości prywatnej przychodzi e-mail z podglądem treści i linkiem do odpowiedzi',
|
|
'<strong>Zarządzanie powiadomieniami</strong> - w ustawieniach prywatności można włączyć lub wyłączyć powiadomienia e-mail o nowych wiadomościach',
|
|
'★ <strong>Instalacja aplikacji na telefonie</strong> - na Androidzie wystarczy jedno kliknięcie „Zainstaluj", na iPhonie instrukcja krok po kroku z obrazkami',
|
|
],
|
|
'improve': [
|
|
'<strong>Klikalne linki na forum</strong> - adresy stron internetowych wklejone we wpisach na forum automatycznie stają się klikalnymi linkami',
|
|
'<strong>Godziny otwarcia na żywo</strong> - status „otwarte/zamknięte" na profilu firmy obliczany jest na bieżąco na podstawie aktualnej godziny',
|
|
],
|
|
'fix': [
|
|
'<strong>Formatowanie wpisów na forum</strong> - poprawiono zbyt duże odstępy między wierszami i prawidłowe wyświetlanie list',
|
|
'<strong>Podwójne wysyłanie odpowiedzi</strong> - naprawiono błąd umożliwiający przypadkowe podwójne wysłanie odpowiedzi na forum',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.38.0',
|
|
'date': '22 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Osoby kontaktowe na profilu firmy</strong> - na profilu każdej firmy widoczne są osoby powiązane z firmą wraz z danymi kontaktowymi i możliwością wysłania wiadomości',
|
|
'<strong>Wiadomości w menu głównym</strong> - nowy skrót do wiadomości prywatnych w górnym menu z licznikiem nieprzeczytanych',
|
|
'<strong>Powiadomienia o nowych wiadomościach</strong> - po otrzymaniu wiadomości prywatnej pojawia się powiadomienie w dzwoneczku',
|
|
],
|
|
'improve': [
|
|
'<strong>Karta kontaktowa na profilu firmy</strong> - wyraźne przyciski do wysłania e-maila i wiadomości prywatnej na portalu',
|
|
'<strong>Oznaczenie członków Norda</strong> - osoby powiązane z firmami członkowskimi mają widoczny znaczek „Norda"',
|
|
'<strong>Podgląd reakcji na forum</strong> - po najechaniu na reakcję widoczna lista osób, które zareagowały',
|
|
],
|
|
'fix': [
|
|
'<strong>Podgląd profilu na forum</strong> - naprawiono wyświetlanie statystyk po najechaniu na autora wpisu',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.37.0',
|
|
'date': '21 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Wyszukiwanie stron internetowych firm</strong> - automatyczne znajdowanie stron WWW dla firm, które ich nie mają w katalogu',
|
|
'<strong>Audyt SEO portalu</strong> - panel do sprawdzania jakości SEO samej platformy Norda Biznes z historią wyników',
|
|
'<strong>Firmy na stronie głównej</strong> - kafelki firm członkowskich widoczne na stronie startowej portalu',
|
|
'<strong>Nowy wygląd e-maili</strong> - ujednolicony, profesjonalny szablon wszystkich wiadomości e-mail z portalu',
|
|
],
|
|
'improve': [
|
|
'<strong>Jakość danych firm</strong> - nowy panel z oceną kompletności danych, podpowiedziami i hurtowym uzupełnianiem informacji',
|
|
'<strong>Poprawne adresy stron firm</strong> - linki do stron internetowych działają teraz prawidłowo we wszystkich przypadkach',
|
|
],
|
|
'fix': [
|
|
'<strong>Poprawny adres i telefon Izby</strong> - zaktualizowano dane kontaktowe na stronie portalu',
|
|
'<strong>Linki do Facebooka</strong> - naprawiono wyświetlanie odnośnika do strony Norda Biznes na Facebooku',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.36.0',
|
|
'date': '20 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Wykresy analityki postów</strong> - 6 wykresów pokazujących zaangażowanie, aktywność publikacji, typy postów i najlepsze dni/godziny',
|
|
'<strong>Ranking Top 5 postów</strong> - najlepiej performujące posty z Facebooka z linkami do oryginału',
|
|
'<strong>Paginacja listy postów</strong> - wybór wyświetlania 10, 20, 50, 100 lub wszystkich postów na stronie',
|
|
'<strong>Zwijane sekcje</strong> - każdą sekcję można zwinąć lub rozwinąć, a ustawienie jest zapamiętywane między wizytami',
|
|
'<strong>Przycisk "Zaktualizuj dane"</strong> - ponowne pobranie wszystkich danych firmy z rejestrów, audytów i mediów społecznościowych',
|
|
],
|
|
'improve': [
|
|
'<strong>Audyt wizytówki Google</strong> - czytelniejszy widok wyników i weryfikacja czy znaleziona wizytówka należy do szukanej firmy',
|
|
'<strong>Wykrywanie profili społecznościowych</strong> - naprawiono zapisywanie znalezionych profili Facebook, Instagram, YouTube, LinkedIn i TikTok',
|
|
'<strong>Pobieranie danych z CEIDG</strong> - poprawiono wyszukiwanie jednoosobowych firm w rejestrze CEIDG z pełnym mapowaniem pól',
|
|
'<strong>Automatyczne wykresy</strong> - wykresy wyświetlają się natychmiast po wejściu na stronę bez konieczności klikania',
|
|
'<strong>Nowa kolejność sekcji</strong> - wykresy na górze, posty z aplikacji w środku, posty z Facebooka na dole',
|
|
],
|
|
'fix': [
|
|
'<strong>Logo firm w formacie SVG</strong> - naprawiono wyświetlanie logo zapisanych w formacie wektorowym',
|
|
'<strong>Pobieranie logo</strong> - naprawiono błąd uniemożliwiający pobranie logo z niektórych stron internetowych',
|
|
'<strong>Duplikaty postów z Facebooka</strong> - naprawiono problem z pobieraniem tych samych postów wielokrotnie',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.35.0',
|
|
'date': '19 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Moduł Social Media</strong> - nowy moduł do zarządzania obecnością firmy w mediach społecznościowych',
|
|
'<strong>Tworzenie postów z pomocą AI</strong> - generowanie treści i hashtagów przez sztuczną inteligencję',
|
|
'<strong>Wybór tonu komunikacji</strong> - 5 stylów pisania od autentycznego przez ekspercki po inspirujący',
|
|
'<strong>Wybór silnika AI</strong> - możliwość wyboru modelu do generowania treści',
|
|
'<strong>Publikowanie na Facebooku</strong> - tworzenie i publikowanie postów bezpośrednio z platformy',
|
|
'<strong>Widoczność postów</strong> - przełączanie między trybem testowym a publicznym dla każdego posta',
|
|
'<strong>Posty z Facebooka ze statystykami</strong> - wyświetlanie polubień, komentarzy, udostępnień i reakcji',
|
|
'<strong>Diagram cyklu życia posta</strong> - wizualizacja procesu od szkicu po publikację',
|
|
'<strong>Synchronizacja danych z Facebooka</strong> - automatyczne pobieranie kategorii, telefonu i adresu ze strony firmowej',
|
|
'<strong>Integracje dla kierowników</strong> - strona integracji dostępna dla osób z rolą Zarządzający',
|
|
],
|
|
'improve': [
|
|
'<strong>Linki social media w postach AI</strong> - generowane treści uwzględniają profile firmy w mediach społecznościowych',
|
|
'<strong>Monitorowanie kosztów AI</strong> - informacja o zużyciu zasobów przy generowaniu treści',
|
|
'<strong>Liczba uczestników przy wydarzeniach</strong> - widoczna na pulpicie obok nadchodzących wydarzeń',
|
|
'<strong>Klikalna karta "Twoja firma"</strong> - na pulpicie prowadzi do profilu firmy',
|
|
'<strong>Ochrona adresów Facebook</strong> - identyfikator numeryczny nie nadpisuje czytelnego adresu firmy',
|
|
],
|
|
'fix': [
|
|
'<strong>Połączenie z Facebookiem</strong> - naprawiono przechowywanie danych autoryzacji dla niezawodnego publikowania',
|
|
'<strong>Ponowna próba publikacji</strong> - nieudane posty można opublikować ponownie',
|
|
'<strong>Diagnostyka połączenia z Facebookiem</strong> - dodano informacje pomagające rozwiązać problemy z połączeniem',
|
|
'<strong>Opisy wydarzeń w kalendarzu</strong> - poprawiono wyświetlanie formatowanego tekstu w widoku listy',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.34.0',
|
|
'date': '18 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Edytor z podglądem na żywo</strong> - edycja opisów firmy z formatowaniem tekstu i natychmiastowym podglądem wyniku',
|
|
'★ <strong>Automatyczne pobieranie logo</strong> - system pobiera logo ze strony internetowej firmy i proponuje kandydatów z oceną jakości',
|
|
'<strong>Galeria kandydatów na logo</strong> - porównanie wielu propozycji logo przed wyborem najlepszego',
|
|
'<strong>Notatki administracyjne o firmach</strong> - wewnętrzne notatki widoczne tylko dla administratorów',
|
|
'<strong>Kontrola ról nowych użytkowników</strong> - nowi użytkownicy otrzymują ograniczoną rolę do czasu zatwierdzenia przez admina',
|
|
'<strong>Zmiana roli użytkownika z listy</strong> - administrator może zmieniać rolę bezpośrednio w panelu użytkowników',
|
|
],
|
|
'improve': [
|
|
'<strong>Nowy wygląd strony edycji firmy</strong> - czytelniejszy układ z kolorową organizacją sekcji',
|
|
'<strong>Porównanie logo przed zmianą</strong> - podgląd dotychczasowego i nowego logo obok siebie',
|
|
'<strong>Uprawnienia kierowników</strong> - osoby zarządzające automatycznie otrzymują pełne uprawnienia przy tworzeniu konta',
|
|
'<strong>Powiadomienie o nowej rejestracji</strong> - naprawiono wysyłanie emaila z alertem o oczekujących zatwierdzeniach',
|
|
],
|
|
'fix': [
|
|
'<strong>Pobieranie danych z CEIDG</strong> - naprawiono pobieranie i mapowanie pól z rejestru jednoosobowych firm',
|
|
'<strong>Audyt SEO</strong> - poprawna obsługa wolnych połączeń i stron, które nie istnieją',
|
|
'<strong>Wyświetlanie formatowanych opisów firm</strong> - opisy z edytora wyświetlają się poprawnie na profilu firmy',
|
|
'<strong>Audyt social media</strong> - usunięto źródło generujące zbyt wiele fałszywych wyników',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.33.0',
|
|
'date': '17 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Wiele stron internetowych na profilu firmy</strong> - możliwość dodania do 5 stron z oznaczeniem typu (główna, e-commerce, blog, portfolio)',
|
|
'<strong>Kolorowe przyciski typów stron</strong> - każdy typ strony ma inny kolor z podpowiedzią po najechaniu',
|
|
'<strong>Osobne wyniki audytu dla każdej strony</strong> - karta wyników SEO wyświetlana oddzielnie dla każdej strony firmy',
|
|
'<strong>Kontrola widoczności sekcji profilu</strong> - właściciel firmy może ukrywać lub pokazywać poszczególne sekcje (rejestry, social media, SEO)',
|
|
'<strong>Szczegółowa kontrola sekcji rejestrów</strong> - oddzielne przełączniki dla KRS, CEIDG i Białej Listy VAT',
|
|
'<strong>Rozpoznawanie typu profilu LinkedIn</strong> - audyt rozróżnia profil firmowy od osobistego',
|
|
],
|
|
'improve': [
|
|
'<strong>Dokładniejsze wykrywanie LinkedIn</strong> - lepsze rozpoznawanie profili firmowych w wynikach wyszukiwania',
|
|
'<strong>Pasek postępu audytu social media</strong> - dodatkowy krok wyszukiwania widoczny w pasku postępu',
|
|
],
|
|
'fix': [
|
|
'<strong>Usuwanie stron internetowych firmy</strong> - naprawiono usuwanie wszystkich stron przy zapisywaniu zmian',
|
|
'<strong>Rozdzielanie połączonych adresów</strong> - system prawidłowo rozdziela adresy zapisane w jednym polu',
|
|
'<strong>Wykrywanie LinkedIn</strong> - poprawiono dokładność sprawdzania profili LinkedIn',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.32.0',
|
|
'date': '14 lutego 2026',
|
|
'badges': ['improve', 'fix'],
|
|
'improve': [
|
|
'<strong>Powiadomienia na telefonie</strong> - dropdown wyświetla się jako panel od dołu ekranu zamiast być obcinanym',
|
|
'<strong>Dodawanie zdjęć na forum z telefonu</strong> - przycisk "Dodaj zdjęcie z galerii" zamiast "Ctrl+V" na urządzeniach mobilnych',
|
|
],
|
|
'fix': [
|
|
'<strong>Usuwanie odpowiedzi na forum</strong> - naprawiono błąd uniemożliwiający usunięcie odpowiedzi, które były już przeczytane przez innych',
|
|
'<strong>Wysyłanie załączników z telefonu</strong> - naprawiono problem z nieprzesyłaniem zdjęć wybranych z galerii na urządzeniach mobilnych',
|
|
'<strong>Pulpit użytkownika</strong> - naprawiono błąd 500 przy wyświetlaniu dashboardu',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.31.0',
|
|
'date': '13 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Widget postępu wdrożenia na pulpicie</strong> - widoczny stan konfiguracji firmy po zalogowaniu',
|
|
'<strong>Zdjęcia w wydarzeniach</strong> - wydarzenia mogą zawierać zdjęcia w opisie',
|
|
'<strong>Nowa firma: UNIMOT Energia i Gaz</strong> - logotyp nowej firmy członkowskiej',
|
|
'<strong>Weryfikacja certyfikatu SSL w audycie SEO</strong> - sprawdzanie ważności certyfikatu strony',
|
|
],
|
|
'improve': [
|
|
'<strong>Czytelniejsze opisy wydarzeń</strong> - poprawione formatowanie tekstu i wyświetlanie obrazów w kalendarzu',
|
|
'<strong>Walidacja NIP przy rejestracji</strong> - sprawdzanie sumy kontrolnej numeru NIP',
|
|
],
|
|
'fix': [
|
|
'<strong>Audyt SEO</strong> - naprawiono ładowanie kluczy API przy uruchamianiu audytu',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.30.0',
|
|
'date': '12 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>3 najbliższe wydarzenia na stronie głównej</strong> - szybki podgląd kalendarza bez wchodzenia w szczegóły',
|
|
'<strong>Nowe firmy: Alter Energy, Fiume Studio</strong> - logotypy nowych firm członkowskich',
|
|
'<strong>Marki zależne dziedziczą NIP</strong> - marki podrzędne automatycznie wyświetlają NIP firmy macierzystej',
|
|
],
|
|
'improve': [
|
|
'<strong>Lepsza detekcja profili Facebook w audycie</strong> - wykluczenie pixeli śledzących i parametrów Meta z wyników',
|
|
],
|
|
'fix': [
|
|
'<strong>Synchronizacja użytkownik-firma</strong> - naprawiono powiązania w tabeli user_companies i nazwy pól kalendarza',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.29.0',
|
|
'date': '9 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Analiza planu rozwoju Izby przez AI</strong> - sztuczna inteligencja ocenia postęp realizacji strategii i wskazuje braki',
|
|
'<strong>Panel wiedzy ZOPK na stronie głównej</strong> - wybrane wpisy bazy wiedzy widoczne bez wchodzenia w sekcję ZOPK',
|
|
'<strong>Przyciski zarządzania na stronach ZOPK</strong> - administratorzy mogą edytować treści bezpośrednio ze strony publicznej',
|
|
'<strong>Baza wiedzy ZOPK w czasie rzeczywistym</strong> - postęp wyszukiwania i ekstrakcji widoczny na żywo w przeglądarce',
|
|
'<strong>4-etapowy panel zarządzania bazą wiedzy ZOPK</strong> - workflow od wyszukiwania przez scraping po ekstrakcję',
|
|
'<strong>Rozszerzenie Google API do ~100%</strong> - GBP Performance i Google Search Console w pełni zintegrowane',
|
|
'<strong>Kontakt z przełożonym dla pracowników</strong> - pracownik widzi dane kontaktowe osoby zarządzającej firmą',
|
|
'<strong>Pełna matryca uprawnień w panelu dostępu</strong> - widoczne wszystkie uprawnienia dla każdej roli',
|
|
'<strong>Bezpośrednie ustawianie hasła przez admina</strong> - bez konieczności wysyłania linku resetującego',
|
|
'<strong>Kolumna ostatniego logowania</strong> w liście użytkowników',
|
|
],
|
|
'improve': [
|
|
'<strong>Automatyczne odświeżanie tokenów OAuth</strong> - strona integracji nie wymaga ponownego logowania po wygaśnięciu tokenu',
|
|
'<strong>Stylizowane okno potwierdzenia rozłączenia OAuth</strong> - zamiast standardowego okna przeglądarki',
|
|
'<strong>Wydzielone przyciski audytu na profilu firmy</strong> - przeniesione do osobnego wiersza dla lepszej czytelności',
|
|
'<strong>Link "Mój pulpit" na profilu firmy</strong> - szybki powrót do panelu użytkownika',
|
|
'<strong>Dostęp do audytów ograniczony do wyznaczonego użytkownika</strong>',
|
|
'<strong>Menu uproszczone dla zwykłych użytkowników</strong> - ukrycie opcji widocznych tylko dla właściciela platformy',
|
|
],
|
|
'fix': [
|
|
'<strong>Polskie znaki w audytach</strong> - naprawiono wyświetlanie polskich znaków diakrytycznych w szablonach audytu',
|
|
'<strong>Baza wiedzy ZOPK</strong> - naprawiono model embeddingów, scraper treści i polskie komunikaty błędów',
|
|
'<strong>Pulpit użytkownika</strong> - naprawiono błąd wylogowywania przy wyświetlaniu dashboardu z powiązanymi firmami',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.28.0',
|
|
'date': '8 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Audyt SEO: dane z Google Search Console</strong> - kliknięcia, wyświetlenia i średnia pozycja z Google',
|
|
'<strong>Audyt SEO: dane CrUX</strong> - rzeczywiste pomiary szybkości od użytkowników Chrome',
|
|
'<strong>Audyt SEO: nagłówki bezpieczeństwa</strong> - sprawdzanie konfiguracji HTTPS i nagłówków ochronnych',
|
|
'<strong>Audyt SEO: formaty obrazów</strong> - analiza wykorzystania nowoczesnych formatów WebP/AVIF',
|
|
'<strong>Audyt SEO: pasek postępu i podsumowanie wyników</strong> - czytelna wizualizacja stanu strony',
|
|
'<strong>Audyt SEO: instrukcje konfiguracji Search Console</strong> - krok po kroku dla firm bez połączonego konta',
|
|
'<strong>Audyt GBP: migracja na Places API (New)</strong> - nowsze i dokładniejsze dane o firmach w Google Maps',
|
|
'<strong>Audyt GBP: link do recenzji, wskazówki dojazdu, status otwarcia</strong>',
|
|
'<strong>Audyt AI: analiza sentymentu recenzji i benchmarki branżowe</strong>',
|
|
'<strong>Integracje OAuth</strong> - framework do łączenia kont Google i Meta z platformą',
|
|
],
|
|
'improve': [
|
|
'<strong>Migracja FID na INP</strong> - nowy standard Google do pomiaru interaktywności strony',
|
|
'<strong>Rozszerzone wyświetlanie danych GBP i Social Media</strong> w panelach audytu',
|
|
],
|
|
'fix': [
|
|
'<strong>Audyt GBP</strong> - naprawiono fałszywe wykrywanie zdjęć i logo',
|
|
'<strong>Audyt SEO</strong> - naprawiono zamianę klucza fid_ms na inp_ms w trasie',
|
|
'<strong>Search Console</strong> - obsługa formatu domain property (sc-domain:)',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.27.0',
|
|
'date': '6 lutego 2026',
|
|
'badges': ['security', 'new', 'improve', 'fix'],
|
|
'security': [
|
|
'<strong>Przegląd bezpieczeństwa platformy</strong> - naprawiono 8 wykrytych luk (1 krytyczna, 7 średnich)',
|
|
'<strong>Ochrona wyszukiwarki i bazy wiedzy ZOPK</strong> - zabezpieczenie przed atakami przez złośliwe zapytania',
|
|
'<strong>Bezpieczne zapisywanie treści</strong> - oczyszczanie HTML w ogłoszeniach, wydarzeniach i protokołach Rady',
|
|
'<strong>Ochrona kluczy dostępowych</strong> - klucze API nie są już widoczne w logach systemowych',
|
|
'<strong>Zabezpieczenie formularzy</strong> - dodanie ochrony przed nieautoryzowanym wysyłaniem w chacie i 3 formularzach',
|
|
],
|
|
'new': [
|
|
# Forum
|
|
'<strong>Powiadomienia email z forum</strong> - otrzymujesz email gdy ktoś odpowie w temacie, w którym uczestniczysz',
|
|
'<strong>Automatyczna subskrypcja tematów</strong> - po dodaniu odpowiedzi automatycznie śledzisz dalszą dyskusję',
|
|
'<strong>Rezygnacja z powiadomień</strong> - link w każdym emailu pozwala wyłączyć powiadomienia dla danego tematu',
|
|
# NordaGPT
|
|
'<strong>NordaGPT zna Izbę NORDA</strong> - chatbot odpowiada na pytania o misję, zarząd (16 osób), Akademię NORDA i Chwilę dla Biznesu',
|
|
'<strong>Strategia 2026-2031 w NordaGPT</strong> - chatbot zna 3 kierunki rozwoju Izby, cel 30-lecia i kontekst regionalny Kaszub',
|
|
'<strong>Projekty członkowskie w NordaGPT</strong> - chatbot zna projekty Energo Velo i Żarnowiecki Ring',
|
|
# Dashboard
|
|
'<strong>Sekcja "Co nowego w Izbie?" na pulpicie</strong> - po zalogowaniu widzisz: wydarzenia, ogłoszenia, tematy forum, oferty B2B i nowe firmy',
|
|
'<strong>Aktualne dane na pulpicie</strong> - liczba nieprzeczytanych powiadomień i nadchodzących wydarzeń zamiast pustych statystyk',
|
|
# Profil firmy - edycja
|
|
'★ <strong>Edycja profilu firmy przez właściciela</strong> - właściciel może sam edytować opisy, usługi, kontakty i social media bez pomocy administratora',
|
|
'<strong>Podział uprawnień w edycji</strong> - dane formalne (NIP, KRS, nazwa) zmienia tylko administrator; dane marketingowe mogą edytować uprawnieni pracownicy',
|
|
'<strong>Więcej informacji na profilu firmy</strong> - wyświetlanie usług, technologii, zasięgu działania, języków, historii i wartości firmy',
|
|
# Rejestry urzędowe
|
|
'★ <strong>Pobieranie danych z rejestrów urzędowych</strong> - administrator jednym kliknięciem pobiera dane firmy z KRS, Białej Listy VAT lub CEIDG',
|
|
'<strong>Automatyczny dobór rejestru</strong> - system sam wybiera właściwy rejestr: KRS dla spółek, CEIDG dla jednoosobowych działalności',
|
|
'<strong>Import zarządu i branż z KRS</strong> - pobierane są osoby w zarządzie i kody PKD (branże działalności)',
|
|
# Social audit
|
|
'<strong>Ostrzeżenie o adresie Facebook</strong> - audyt wykrywa firmy używające numerycznego ID zamiast własnej nazwy na Facebooku',
|
|
'<strong>Zalecenia dla Facebooka</strong> - podpowiedź aby przekształcić profil osobisty w stronę firmową z czytelnym adresem',
|
|
'<strong>Zalecenia w panelu admina</strong> - administrator widzi kolorowe zalecenia dla każdej firmy (np. brak Facebooka, brak Instagrama)',
|
|
# Loga firm
|
|
'<strong>Nowe firmy: Termo i Studio N°33</strong> - dodano logotypy nowych firm członkowskich',
|
|
],
|
|
'improve': [
|
|
'<strong>Nowy wygląd formularza edycji firmy</strong> - czytelniejszy układ z zakładkami, ikonami i licznikiem znaków',
|
|
'<strong>Czytelniejsze podpowiedzi</strong> - po najechaniu na awatar na forum i w ogłoszeniach B2B widać czytelną etykietkę z imieniem',
|
|
'<strong>Lepsza detekcja adresów Facebook</strong> - poprawne rozpoznawanie profili z numerycznym ID i nietypowych adresów',
|
|
'<strong>Zalecenia social media nawet gdy wszystko jest OK</strong> - wyświetlanie podpowiedzi np. o zmianie adresu Facebook, nawet gdy firma ma wszystkie platformy',
|
|
'<strong>Kolumna zaleceń w panelu Social Audit</strong> - kolorowe etykiety (czerwone, pomarańczowe, szare, zielone) dla szybkiej oceny',
|
|
'<strong>Pulpit z dwukolumnowym układem</strong> - widgety z aktywnością Izby czytelnie rozmieszczone na ekranie',
|
|
],
|
|
'fix': [
|
|
'<strong>Zatwierdzanie propozycji AI</strong> - naprawiono błąd przy klikaniu "Akceptuj i dodaj do profilu"',
|
|
'<strong>Linki do Facebooka</strong> - naprawiono błędne linki dla firm z numerycznym ID na Facebooku',
|
|
'<strong>Audyt SEO ponownie dostępny</strong> - przywrócono działanie usługi audytu SEO po reorganizacji kodu',
|
|
'<strong>Audyt Google Business Profile ponownie dostępny</strong> - przywrócono działanie usługi audytu GBP',
|
|
'<strong>Wyświetlanie profilu firmy</strong> - naprawiono błąd uniemożliwiający otwarcie niektórych profili firm',
|
|
'<strong>Podpowiedzi na awatarach</strong> - poprawiona czytelność etykietek z imionami na forum i w ogłoszeniach',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.26.0',
|
|
'date': '5 lutego 2026',
|
|
'badges': ['security', 'improve'],
|
|
'security': [
|
|
'<strong>System uprawnień: 154 trasy zabezpieczone</strong> - każda strona administracyjna wymaga teraz odpowiedniego poziomu dostępu',
|
|
'<strong>6-poziomowa hierarchia ról</strong> - od zwykłego użytkownika przez członka, pracownika, kierownika po administratora',
|
|
'<strong>Menu dostosowane do roli</strong> - kierownik biura widzi tylko te opcje, do których ma uprawnienia',
|
|
'<strong>Wybór roli przy tworzeniu użytkownika</strong> - zamiast prostego "tak/nie" administrator wybiera konkretny poziom dostępu',
|
|
],
|
|
'improve': [
|
|
'<strong>23 testy automatyczne dla systemu ról</strong> - weryfikacja poprawności uprawnień na każdym poziomie',
|
|
'<strong>Trwałe usuwanie firm</strong> - zarchiwizowane firmy mogą być trwale usunięte przez administratora (nieodwracalne)',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.25.0',
|
|
'date': '4 lutego 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Strefa RADA</strong> - zamknięta sekcja dla członków Rady Izby z listą posiedzeń i członków',
|
|
'<strong>Zarządzanie posiedzeniami Rady</strong> - program, lista obecności i protokół w jednym miejscu',
|
|
'<strong>Edytor protokołu</strong> - zapisywanie ustaleń, decyzji i zadań z osobą odpowiedzialną i terminem',
|
|
'<strong>Pobieranie PDF</strong> - program posiedzenia i protokół do pobrania jako dokument PDF',
|
|
'<strong>Lista obecności z kworum</strong> - automatyczne liczenie obecnych i sprawdzanie kworum',
|
|
'<strong>Publikowanie programu i protokołu</strong> - osobne publikowanie każdego dokumentu',
|
|
'<strong>Korzyści dla Członków</strong> - oferty partnerskie (WisprFlow AI) dostępne dla członków Izby',
|
|
'<strong>Strona korzyści</strong> - przegląd ofert partnerskich z linkami do wersji demonstracyjnych',
|
|
'<strong>Ulepszona rejestracja</strong> - po weryfikacji email automatyczne zalogowanie i przekierowanie',
|
|
'<strong>Wydarzenia Rady</strong> widoczne tylko dla członków Izby',
|
|
'<strong>Status wniosku członkowskiego</strong> - po złożeniu wniosku widać jego aktualny stan',
|
|
'<strong>Powiadomienie dla administratora</strong> o nowym wniosku członkowskim',
|
|
'<strong>Szczegóły profilu firmy</strong> widoczne tylko dla członków Izby',
|
|
],
|
|
'improve': [
|
|
'<strong>Statusy posiedzeń jako klikalne linki</strong> do programu i protokołu',
|
|
'<strong>Czytelne wyświetlanie przebiegu posiedzenia</strong> z decyzjami i zadaniami',
|
|
'<strong>Środowisko testowe</strong> oznaczone wizualnie, aby nie pomylić z produkcją',
|
|
'Zablokowane wersje bibliotek dla stabilności platformy',
|
|
'Aktualizacja bibliotek systemowych',
|
|
'<strong>Strefa RADA uproszczona</strong> - skupiona na posiedzeniach',
|
|
'<strong>Korzyści</strong> - dane o prowizjach widoczne tylko dla właściciela oferty',
|
|
'<strong>Trwałe usuwanie firm</strong> - administrator może nieodwracalnie usunąć zarchiwizowane firmy',
|
|
],
|
|
'fix': [
|
|
'<strong>Naprawiono zabezpieczenie formularzy</strong> publikacji programu i protokołu',
|
|
'Naprawiono wyświetlanie posiedzeń bez programu lub punktów obrad',
|
|
'Naprawiono przycisk potwierdzenia udziału w wydarzeniach',
|
|
'Naprawiono link do składania wniosku członkowskiego',
|
|
'Obsługa sytuacji gdy generowanie PDF jest tymczasowo niedostępne',
|
|
'<strong>Naprawiono błąd przy usuwaniu użytkowników</strong> powiązanych z innymi danymi',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.24.0',
|
|
'date': '2 lutego 2026',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'<strong>Środowisko testowe</strong> - osobny serwer do sprawdzania zmian przed wdrożeniem',
|
|
'<strong>Automatyczne testy</strong> - każda zmiana w kodzie jest automatycznie sprawdzana',
|
|
'<strong>Testy logowania i sesji</strong> użytkowników',
|
|
'<strong>Testy bezpieczeństwa</strong> - weryfikacja ochrony przed najczęstszymi atakami',
|
|
'<strong>Testy w przeglądarce</strong> - automatyczne sprawdzanie działania strony',
|
|
'<strong>Automatyczna weryfikacja</strong> po każdym wdrożeniu na produkcję',
|
|
],
|
|
'improve': [
|
|
'Automatyczna kontrola jakości kodu przed zapisaniem zmian',
|
|
'Narzędzia do utrzymania spójności kodu',
|
|
'Wskaźnik statusu testów widoczny na stronie projektu',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.23.0',
|
|
'date': '1 lutego 2026',
|
|
'badges': ['security', 'new', 'improve', 'fix'],
|
|
'security': [
|
|
'<strong>6 poziomów dostępu</strong> - od gościa przez członka, pracownika, kierownika po administratora',
|
|
'<strong>NordaGPT dostępny tylko dla członków Izby</strong>',
|
|
'<strong>Wiadomości prywatne</strong> tylko dla członków Izby',
|
|
'<strong>Tablica ogłoszeń B2B</strong> tylko dla członków Izby',
|
|
'<strong>Dane kontaktowe firm</strong> widoczne tylko dla członków Izby',
|
|
],
|
|
'new': [
|
|
'★ <strong>Składanie wniosków o członkostwo</strong> - formularz, weryfikacja danych, zatwierdzanie przez admina',
|
|
'<strong>Automatyczne wyszukiwanie danych firmy</strong> po numerze NIP',
|
|
'<strong>Porównanie danych</strong> podanych przez użytkownika z danymi z rejestrów urzędowych',
|
|
'<strong>Zatwierdzanie danych z rejestrów</strong> - użytkownik może zatwierdzić lub odrzucić pobrane dane',
|
|
'<strong>Historia procesu</strong> - oś czasu wszystkich kroków od złożenia do zatwierdzenia wniosku',
|
|
'<strong>Powiadomienie dla administratora</strong> o decyzji użytkownika ws. danych z rejestrów',
|
|
'<strong>Sekcja "Dane z rejestrów urzędowych"</strong> na profilu firmy (KRS lub CEIDG)',
|
|
'<strong>Pełne dane z KRS</strong> - kapitał zakładowy, sposób reprezentacji, wspólnicy',
|
|
'<strong>Automatyczny dobór rejestru</strong> - KRS dla spółek, CEIDG dla jednoosobowych firm',
|
|
'<strong>Automatyczne pobieranie danych z KRS</strong> przy zatwierdzaniu wniosku',
|
|
'Strona promocyjna NordaGPT dla osób niebędących członkami',
|
|
'Osobne uprawnienia dla kierownika biura Izby',
|
|
'Funkcje portalu wymagające członkostwa w Izbie',
|
|
'Panel przypisywania ról użytkownikom',
|
|
'<strong>Automatyczna aktualizacja opisów firm</strong> - AI analizuje strony internetowe członków',
|
|
],
|
|
'improve': [
|
|
'<strong>Czytelniejszy profil firmy</strong> - usunięcie powtarzających się informacji',
|
|
'Dane kontaktowe zebrane w jednym miejscu na profilu',
|
|
'Podział funkcji administracyjnych według poziomu uprawnień',
|
|
'Moderacja forum dostępna dla uprawnionych osób',
|
|
'Menu dostosowane do uprawnień użytkownika',
|
|
'Usunięcie automatycznie generowanych sekcji z profilu firmy',
|
|
'Tymczasowe ukrycie sekcji rekomendacji',
|
|
],
|
|
'fix': [
|
|
'<strong>Naprawiono zapisywanie adresu</strong> przy tworzeniu nowej firmy',
|
|
'Naprawiono linki do profili firm',
|
|
'Naprawiono zabezpieczenie formularzy członkostwa',
|
|
'Naprawiono błąd przy składaniu wniosku członkowskiego',
|
|
'Naprawiono zapisywanie historii procesu członkostwa',
|
|
'Naprawiono okno potwierdzenia, które traciło dane po zamknięciu',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.22.0',
|
|
'date': '31 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'★ <strong>Tablica B2B: Przycisk "Jestem zainteresowany"</strong> - wyrażenie zainteresowania ofertą',
|
|
'<strong>Tablica B2B: Publiczne pytania i odpowiedzi</strong> pod ogłoszeniami',
|
|
'<strong>Tablica B2B: Wysyłanie wiadomości</strong> bezpośrednio z ogłoszenia',
|
|
'Tablica B2B: Autor widzi kto jest zainteresowany jego ofertą',
|
|
'Tablica B2B: Oznaczenie wiadomości powiązanych z ogłoszeniem',
|
|
'<strong>Forum: Informacja kto przeczytał</strong> każdą odpowiedź',
|
|
'<strong>Tablica B2B: Informacja kto widział</strong> ogłoszenie',
|
|
'<strong>Panel admina: Zarządzanie firmami</strong> - lista, edycja, statystyki',
|
|
'<strong>Panel admina: Zarządzanie osobami</strong> - dane z KRS i powiązania z firmami',
|
|
'<strong>Panel admina: Przegląd stanu platformy</strong> - certyfikaty, bezpieczeństwo',
|
|
'<strong>Rejestr logowań</strong> - kto i kiedy się logował do platformy',
|
|
'<strong>Forum: Reakcje emoji</strong> na wpisy i odpowiedzi',
|
|
'<strong>Forum: Śledzenie tematów</strong> z powiadomieniami o nowych odpowiedziach',
|
|
'<strong>Forum: Edycja własnych wpisów</strong> (do 24 godzin)',
|
|
'<strong>Forum: Zgłaszanie nieodpowiednich treści</strong>',
|
|
'<strong>Forum: Oznaczanie najlepszej odpowiedzi</strong> jako rozwiązanie',
|
|
'Forum: Statystyki aktywności użytkownika',
|
|
'Forum: Formatowanie tekstu (pogrubienie, listy, linki)',
|
|
'Forum: Oznaczanie @użytkowników z powiadomieniami',
|
|
'<strong>Panel admina: Analityka forum</strong> - wykresy aktywności i ranking użytkowników',
|
|
'Panel admina: Eksport aktywności forum do arkusza',
|
|
'Panel admina: Zbiorcze zarządzanie tematami forum',
|
|
'Panel admina: Przenoszenie tematów między kategoriami',
|
|
'Panel admina: Łączenie powiązanych tematów forum',
|
|
'Panel admina: Wyszukiwarka z dostępem do usuniętych treści',
|
|
'Panel admina: Historia aktywności użytkowników na forum',
|
|
'Panel admina: Przywracanie usuniętych wpisów na forum',
|
|
'Menu admina: Szybki dostęp do Forum, Ogłoszeń i Analityki AI',
|
|
],
|
|
'improve': [
|
|
'<strong>Reorganizacja kodu platformy</strong> dla łatwiejszego rozwoju',
|
|
'Forum: Oznaczenie "(Ty)" przy własnym awatarze',
|
|
'Czytelniejszy układ informacji o certyfikatach bezpieczeństwa',
|
|
'Porządki w kodzie platformy',
|
|
],
|
|
'fix': [
|
|
'<strong>NordaGPT: Naprawiono pole wpisywania wiadomości</strong>, które było ucięte',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.21.0',
|
|
'date': '30 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Moje konto</strong> - edycja danych osobowych, ustawienia prywatności i bezpieczeństwa',
|
|
'<strong>Moderacja forum</strong> - administrator może usuwać, przypinać i blokować wpisy',
|
|
'<strong>Moderacja ogłoszeń B2B</strong> - administrator może usuwać i dezaktywować ogłoszenia',
|
|
'Podgląd hasła - ikonka oka pozwala zobaczyć wpisywane hasło',
|
|
'Ładniejsze okna potwierdzenia na forum',
|
|
'Ładniejsze okna potwierdzenia w ogłoszeniach B2B',
|
|
'Wątek na forum do zgłaszania pomysłów i uwag',
|
|
],
|
|
'improve': [
|
|
'Poprawna nazwa platformy na stronie rejestracji',
|
|
'Przyjazna strona informacyjna podczas aktualizacji platformy',
|
|
],
|
|
'fix': [
|
|
'<strong>Reset hasła</strong> nie wymaga już ponownej weryfikacji adresu email',
|
|
'Usunięto tymczasowe wideo z sekcji edukacyjnej',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.20.0',
|
|
'date': '29 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>NordaGPT: Nowy silnik AI</strong> - Google Gemini 3 Flash z lepszym rozumieniem pytań',
|
|
'<strong>NordaGPT: Dwa tryby</strong> - podstawowy (bezpłatny) i zaawansowany (dokładniejszy)',
|
|
'NordaGPT: 7x lepsze rozumowanie i dokładniejsze odpowiedzi',
|
|
'NordaGPT: Informacja o szacowanym koszcie użytkowania',
|
|
'★ <strong>Aplikacja mobilna</strong> - portal można zainstalować na telefonie jak aplikację (iOS/Android)',
|
|
'Aktualności: Ogłoszenie może należeć do kilku kategorii jednocześnie',
|
|
'Aktualności: Nowe kategorie - Wewnętrzne, Zewnętrzne, Wydarzenie, Okazja biznesowa, Partnerstwo',
|
|
'Sekcja edukacyjna: Materiały wideo do obejrzenia na portalu',
|
|
'Film powitalny "Wprowadzenie do Norda Biznes Partner"',
|
|
'<strong>Administrator otrzymuje email</strong> o każdej nowej rejestracji',
|
|
],
|
|
'improve': [
|
|
'Nowa ikona NordaGPT na stronie głównej',
|
|
'Porządki w stopce strony',
|
|
],
|
|
'fix': [
|
|
'Naprawiono błąd przy dodawaniu ogłoszeń B2B',
|
|
'Naprawiono błąd przy dodawaniu wydarzeń do kalendarza',
|
|
'Naprawiono nawigację w module kontaktów',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.19.0',
|
|
'date': '28 stycznia 2026',
|
|
'badges': ['new', 'improve', 'security'],
|
|
'new': [
|
|
'<strong>Ukrywanie telefonu i email</strong> - można wybrać w ustawieniach, co jest widoczne na profilu',
|
|
'<strong>Blokowanie użytkowników</strong> - zablokowana osoba nie może wysyłać wiadomości',
|
|
'Wybór preferowanego sposobu kontaktu (email, telefon, portal)',
|
|
'<strong>Kategorie branżowe</strong> - 4 główne grupy z podkategoriami',
|
|
'Oznaczenie firm z niekompletnym profilem do uzupełnienia',
|
|
'Nowe podkategorie branżowe: Budownictwo, Produkcja, Usługi finansowe',
|
|
'<strong>Nowa sekcja Edukacja</strong> w menu platformy',
|
|
'Panel zbierania opinii i pomysłów od użytkowników',
|
|
'Rozszerzony monitoring stanu platformy',
|
|
],
|
|
'improve': [
|
|
'Katalog: Wyraźne oznaczenie wybranej kategorii',
|
|
'Kategorie posortowane od największej liczby firm',
|
|
],
|
|
'security': [
|
|
'<strong>Ochrona danych osobowych</strong> - chatbot automatycznie ukrywa numery PESEL, karty i IBAN',
|
|
'<strong>Prywatność rozmów</strong> - każdy użytkownik widzi tylko swoje rozmowy z chatbotem',
|
|
'Anonimowe statystyki rozmów z chatbotem w panelu admina',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.17.0',
|
|
'date': '26 stycznia 2026',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Sekcja Aktualności</strong> - wiadomości i ogłoszenia dla członków Izby',
|
|
'Panel zarządzania aktualnościami dla administratora',
|
|
'Kategorie aktualności, możliwość przypinania ważnych ogłoszeń',
|
|
'Załączniki PDF i linki w aktualnościach',
|
|
'Pierwsze ogłoszenia: Baza noclegowa ARP, Konkurs Tytani Przedsiębiorczości',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.16.0',
|
|
'date': '14 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Ochrona geograficzna</strong> - blokowanie dostępu z krajów wysokiego ryzyka',
|
|
'<strong>Własna domena email</strong> - wiadomości wysyłane z adresu @nordabiznes.pl',
|
|
'<strong>Raporty</strong> - staż członkostwa, obecność w social media, struktura branżowa',
|
|
'Data przystąpienia do Izby na profilu firmy z informacją o stażu',
|
|
'Pobieranie danych jednoosobowych firm z rejestru CEIDG',
|
|
'Panel bezpieczeństwa z oceną poziomu ochrony platformy',
|
|
],
|
|
'improve': [
|
|
'Uzupełniono rok założenia dla 71 firm (64% katalogowanych)',
|
|
'Uzupełniono daty przystąpienia do Izby dla 57 firm (od 1997 roku)',
|
|
],
|
|
'fix': [
|
|
'Naprawiono wyświetlanie polskich znaków w statystykach',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.15.0',
|
|
'date': '13 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>NordaGPT zna więcej danych</strong> - rekomendacje, kalendarz, ogłoszenia B2B, forum i dane KRS',
|
|
'<strong>NordaGPT: Klikalne linki</strong> i adresy email w odpowiedziach chatbota',
|
|
'<strong>NordaGPT: Szybki dostęp</strong> do chatbota ze strony głównej',
|
|
'<strong>Kalendarz: Widok miesięczny</strong> z szybkim potwierdzaniem udziału',
|
|
'Najbliższe wydarzenie widoczne na stronie głównej z listą uczestników',
|
|
'<strong>Wzbogacanie profili firm przez AI</strong> - automatyczne uzupełnianie informacji z internetu',
|
|
'<strong>Sprawdzanie danych KRS</strong> z raportami postępu',
|
|
'<strong>Panel analityki</strong> - statystyki odwiedzin i aktywności użytkowników',
|
|
'Profil firmy: Pełna lista branż (kody PKD) i dane właściciela',
|
|
'Zielone oznaczenie przy osobach zweryfikowanych w rejestrze KRS',
|
|
],
|
|
'improve': [
|
|
'Czytelniejsze formatowanie odpowiedzi NordaGPT',
|
|
'Możliwość zwinięcia bannera NordaGPT na stronie głównej',
|
|
],
|
|
'fix': [
|
|
'Zwiększony limit logowań i audytów SEO',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.14.0',
|
|
'date': '12 stycznia 2026',
|
|
'badges': ['new', 'improve', 'fix'],
|
|
'new': [
|
|
'<strong>Audyt wizytówki Google</strong> - sprawdzanie kompletności profilu Google dla każdej firmy',
|
|
'Poradnik "Jak działa wizytówka Google?" w sekcji audytu',
|
|
'Wyniki audytów widoczne bezpośrednio na profilu firmy',
|
|
],
|
|
'improve': [
|
|
'Jednolita 5-stopniowa skala ocen we wszystkich audytach',
|
|
'Wynik audytu social media jako procent zamiast liczby platform',
|
|
],
|
|
'fix': [
|
|
'Kategorie Google wyświetlane po polsku',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.13.0',
|
|
'date': '11 stycznia 2026',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'★ <strong>Mapa Powiązań</strong> - interaktywna wizualizacja powiązań między firmami i osobami',
|
|
'<strong>Profile osób</strong> - dane z rejestrów urzędowych i portalu',
|
|
'<strong>NordaGPT uczy się z opinii użytkowników</strong> i poprawia odpowiedzi',
|
|
'Wyszukiwanie osób po częściowym imieniu lub nazwisku',
|
|
'Logo firm widoczne w wynikach wyszukiwania',
|
|
'Panel użycia AI - statystyki rozmów dla każdego użytkownika',
|
|
],
|
|
'improve': [
|
|
'Mapa Powiązań: pełnoekranowy widok z podpowiedziami po najechaniu',
|
|
'Ładniejsze powiadomienia zamiast systemowych okien',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.11.0',
|
|
'date': '10 stycznia 2026',
|
|
'badges': ['new', 'improve', 'security'],
|
|
'new': [
|
|
'<strong>Forum: Wstawianie zdjęć</strong> - przeciągnij, wklej ze schowka, do 10 plików',
|
|
'<strong>Forum: Kategorie wpisów</strong> - Propozycja, Błąd, Pytanie',
|
|
'<strong>Kompletna dokumentacja techniczna</strong> platformy',
|
|
],
|
|
'improve': [
|
|
'Bezpieczne przesyłanie plików ze sprawdzaniem zawartości',
|
|
],
|
|
'security': [
|
|
'<strong>Usunięcie haseł z kodu źródłowego</strong>',
|
|
'Zmiana hasła bazy danych na serwerze produkcyjnym',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.9.0',
|
|
'date': '9 stycznia 2026',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'<strong>Audyt wizytówek Google</strong> - przegląd profili Google Business wszystkich firm',
|
|
'<strong>Audyt Social Media</strong> - sprawdzanie obecności firm w mediach społecznościowych',
|
|
'<strong>Tworzenie użytkowników przez AI</strong> - wystarczy wkleić tekst lub zrzut ekranu',
|
|
],
|
|
'improve': [
|
|
'Nowy pasek administracyjny z pogrupowanymi funkcjami',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.8.0',
|
|
'date': '8 stycznia 2026',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Audyt IT</strong> - sprawdzanie infrastruktury informatycznej firm członkowskich',
|
|
'Eksport wyników audytu IT do arkusza kalkulacyjnego',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.7.0',
|
|
'date': '6 stycznia 2026',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Audyt SEO</strong> - analiza widoczności stron internetowych firm w wyszukiwarkach',
|
|
'<strong>Ocena szybkości stron</strong> przez Google PageSpeed',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.6.0',
|
|
'date': '29 grudnia 2025',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Wzmianki medialne</strong> - automatyczne wyszukiwanie artykułów o firmach członkowskich',
|
|
'Panel moderacji wzmianek dla administratora',
|
|
'Wyszukiwanie wzmianek przez wyszukiwarkę Brave',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.5.0',
|
|
'date': '15 grudnia 2025',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'<strong>Panel Social Media</strong> - zarządzanie profilami firm w mediach społecznościowych',
|
|
'Sprawdzanie czy profile social media firm są aktywne',
|
|
],
|
|
'improve': [
|
|
'Sekcja Social Media na profilu firmy',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.4.0',
|
|
'date': '1 grudnia 2025',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Rekomendacje</strong> - firmy mogą polecać się nawzajem',
|
|
'<strong>Panel składek członkowskich</strong>',
|
|
'<strong>Kalendarz wydarzeń</strong> Izby',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.3.0',
|
|
'date': '28 listopada 2025',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'★ <strong>Chatbot NordaGPT</strong> - asystent AI znający wszystkie firmy członkowskie',
|
|
'<strong>Wyszukiwarka firm</strong> - rozpoznaje synonimy i literówki',
|
|
],
|
|
'improve': [
|
|
'Szybsza i dokładniejsza wyszukiwarka',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.2.0',
|
|
'date': '25 listopada 2025',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>System wiadomości prywatnych</strong> między użytkownikami',
|
|
'Powiadomienia o nowych wiadomościach',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.1.0',
|
|
'date': '24 listopada 2025',
|
|
'badges': ['new', 'improve'],
|
|
'new': [
|
|
'<strong>Rejestracja i logowanie</strong> użytkowników',
|
|
'Profile użytkowników powiązane z firmami',
|
|
],
|
|
'improve': [
|
|
'Strona dostosowana do telefonów i tabletów',
|
|
],
|
|
},
|
|
{
|
|
'version': 'v1.0.0',
|
|
'date': '23 listopada 2025',
|
|
'badges': ['new'],
|
|
'new': [
|
|
'<strong>Oficjalny start platformy Norda Biznes Partner</strong>',
|
|
'<strong>Katalog 111 firm członkowskich</strong>',
|
|
'Wyszukiwarka firm po nazwie, branży i usługach',
|
|
'Profile firm z pełnymi danymi kontaktowymi',
|
|
],
|
|
},
|
|
]
|
|
|
|
|
|
@bp.route('/release-notes')
|
|
def release_notes():
|
|
"""Historia zmian platformy."""
|
|
releases = _get_releases()
|
|
|
|
# Statystyki (używa globalnej stałej COMPANY_COUNT_MARKETING)
|
|
db = SessionLocal()
|
|
try:
|
|
stats = {
|
|
'companies': COMPANY_COUNT_MARKETING,
|
|
'categories': db.query(Category).filter(Category.parent_id.isnot(None)).count(),
|
|
}
|
|
finally:
|
|
db.close()
|
|
|
|
return render_template('release_notes.html', releases=releases, stats=stats)
|
|
|
|
|
|
@bp.route('/polityka-prywatnosci')
|
|
def polityka_prywatnosci():
|
|
"""Polityka prywatności platformy."""
|
|
return render_template('polityka_prywatnosci.html')
|
|
|
|
|
|
@bp.route('/regulamin')
|
|
def regulamin():
|
|
"""Regulamin platformy."""
|
|
return render_template('regulamin.html')
|
|
|
|
|
|
@bp.route('/zainstaluj-aplikacje')
|
|
def pwa_install():
|
|
"""Instrukcja instalacji PWA na telefonie."""
|
|
return render_template('pwa_install.html')
|
|
|
|
|
|
@bp.route('/sw.js')
|
|
def service_worker():
|
|
"""Service worker served from root scope for PWA installability."""
|
|
return current_app.send_static_file('sw.js'), 200, {
|
|
'Content-Type': 'application/javascript',
|
|
'Service-Worker-Allowed': '/'
|
|
}
|
|
|
|
|
|
@bp.route('/robots.txt')
|
|
def robots_txt():
|
|
"""Robots.txt for search engine crawlers."""
|
|
content = """User-agent: *
|
|
Allow: /
|
|
Disallow: /admin/
|
|
Disallow: /api/
|
|
Disallow: /chat
|
|
Disallow: /login
|
|
Disallow: /register
|
|
Disallow: /dashboard
|
|
Sitemap: https://nordabiznes.pl/sitemap.xml
|
|
"""
|
|
return Response(content, mimetype='text/plain')
|
|
|
|
|
|
@bp.route('/sitemap.xml')
|
|
def sitemap_xml():
|
|
"""Sitemap XML for search engines."""
|
|
today = date.today().isoformat()
|
|
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
<url>
|
|
<loc>https://nordabiznes.pl/</loc>
|
|
<lastmod>{today}</lastmod>
|
|
<changefreq>daily</changefreq>
|
|
<priority>1.0</priority>
|
|
</url>
|
|
<url>
|
|
<loc>https://nordabiznes.pl/release-notes</loc>
|
|
<lastmod>{today}</lastmod>
|
|
<changefreq>weekly</changefreq>
|
|
<priority>0.5</priority>
|
|
</url>
|
|
<url>
|
|
<loc>https://nordabiznes.pl/zainstaluj-aplikacje</loc>
|
|
<lastmod>{today}</lastmod>
|
|
<changefreq>monthly</changefreq>
|
|
<priority>0.4</priority>
|
|
</url>
|
|
<url>
|
|
<loc>https://nordabiznes.pl/polityka-prywatnosci</loc>
|
|
<lastmod>{today}</lastmod>
|
|
<changefreq>monthly</changefreq>
|
|
<priority>0.3</priority>
|
|
</url>
|
|
<url>
|
|
<loc>https://nordabiznes.pl/regulamin</loc>
|
|
<lastmod>{today}</lastmod>
|
|
<changefreq>monthly</changefreq>
|
|
<priority>0.3</priority>
|
|
</url>
|
|
</urlset>"""
|
|
return Response(xml, mimetype='application/xml')
|