diff --git a/blueprints/public/routes.py b/blueprints/public/routes.py index 7b98c7c..00a6c2e 100644 --- a/blueprints/public/routes.py +++ b/blueprints/public/routes.py @@ -277,6 +277,44 @@ def api_upcoming_events(): db.close() +@bp.route('/izba/wladze') +@login_required +def chamber_authorities(): + """Władze Izby — Zarząd, Rada, Komisja Rewizyjna, Sąd Koleżeński""" + from utils.decorators import member_required + db = SessionLocal() + try: + # Group users by chamber role + all_with_roles = db.query(User).filter( + User.chamber_role.isnot(None), + User.is_active == True + ).order_by(User.name).all() + + groups = { + 'zarzad': {'title': 'Zarząd Izby', 'icon': '👔', 'members': []}, + 'rada': {'title': 'Rada Izby', 'icon': '🏛️', 'members': []}, + 'komisja': {'title': 'Komisja Rewizyjna', 'icon': '📋', 'members': []}, + 'sad': {'title': 'Sąd Koleżeński', 'icon': '⚖️', 'members': []}, + } + + for u in all_with_roles: + if u.chamber_role in ('prezes', 'wiceprezes'): + groups['zarzad']['members'].append(u) + elif u.chamber_role == 'czlonek_rady': + groups['rada']['members'].append(u) + elif u.chamber_role == 'komisja_rewizyjna': + groups['komisja']['members'].append(u) + elif u.chamber_role == 'sad_kolezenski': + groups['sad']['members'].append(u) + + # Sort zarząd: prezes first, then wiceprezesi + groups['zarzad']['members'].sort(key=lambda u: (0 if u.chamber_role == 'prezes' else 1, u.name)) + + return render_template('chamber_authorities.html', groups=groups) + finally: + db.close() + + @bp.route('/company/') def company_detail(company_id): """Company detail page - requires login and NORDA membership""" diff --git a/database.py b/database.py index ae6e44f..d07fdcc 100644 --- a/database.py +++ b/database.py @@ -293,6 +293,7 @@ class User(Base, UserMixin): is_admin = Column(Boolean, default=False) # DEPRECATED: synced by set_role() for backward compat. Use has_role(SystemRole.ADMIN) instead. Will be removed in future migration. is_norda_member = Column(Boolean, default=False) is_rada_member = Column(Boolean, default=False) # Member of Rada Izby (Board Council) + chamber_role = Column(String(50)) # prezes, wiceprezes, czlonek_rady, komisja_rewizyjna, sad_kolezenski avatar_path = Column(String(500)) # Path to profile photo (relative to static/uploads/) # Timestamps @@ -343,6 +344,17 @@ class User(Base, UserMixin): # === ROLE SYSTEM HELPER METHODS === + @property + def chamber_role_label(self): + labels = { + 'prezes': 'Prezes Izby', + 'wiceprezes': 'Wiceprezes Izby', + 'czlonek_rady': 'Członek Rady Izby', + 'komisja_rewizyjna': 'Komisja Rewizyjna', + 'sad_kolezenski': 'Sąd Koleżeński', + } + return labels.get(self.chamber_role) + @property def system_role(self) -> SystemRole: """Get the user's SystemRole enum value.""" diff --git a/database/migrations/089_chamber_role.sql b/database/migrations/089_chamber_role.sql new file mode 100644 index 0000000..82ec67d --- /dev/null +++ b/database/migrations/089_chamber_role.sql @@ -0,0 +1,26 @@ +BEGIN; +ALTER TABLE users ADD COLUMN IF NOT EXISTS chamber_role VARCHAR(50); + +-- Zarząd +UPDATE users SET chamber_role = 'prezes' WHERE email = 'leszek.glaza@nordatools.pl'; + +-- Find Paweł Kwidziński, Janusz Masiak, Artur Wiertel by name and set wiceprezes +UPDATE users SET chamber_role = 'wiceprezes' WHERE name ILIKE '%Kwidziński%' OR name ILIKE '%Kwi%ziński%'; +UPDATE users SET chamber_role = 'wiceprezes' WHERE name ILIKE '%Masiak%'; +UPDATE users SET chamber_role = 'wiceprezes' WHERE name ILIKE '%Wiertel%' AND name ILIKE '%Artur%'; + +-- Komisja Rewizyjna +UPDATE users SET chamber_role = 'komisja_rewizyjna' WHERE name ILIKE '%Nurzyński%'; +UPDATE users SET chamber_role = 'komisja_rewizyjna' WHERE name ILIKE '%Więcek%' AND name ILIKE '%Marek%'; +UPDATE users SET chamber_role = 'komisja_rewizyjna' WHERE name ILIKE '%Karnikowska%'; + +-- Sąd Koleżeński +UPDATE users SET chamber_role = 'sad_kolezenski' WHERE name ILIKE '%Morske%'; +UPDATE users SET chamber_role = 'sad_kolezenski' WHERE name ILIKE '%Mizak%'; +UPDATE users SET chamber_role = 'sad_kolezenski' WHERE name ILIKE '%Domachowska%'; + +-- Rada Izby members (already have is_rada_member=true) - set role for those without specific role +UPDATE users SET chamber_role = 'czlonek_rady' WHERE is_rada_member = true AND chamber_role IS NULL; + +GRANT ALL ON TABLE users TO nordabiz_app; +COMMIT; diff --git a/templates/chamber_authorities.html b/templates/chamber_authorities.html new file mode 100644 index 0000000..40fe02e --- /dev/null +++ b/templates/chamber_authorities.html @@ -0,0 +1,180 @@ +{% extends "base.html" %} + +{% block title %}Władze Izby - Norda Biznes Partner{% endblock %} + +{% block extra_css %} + .authorities-container { + max-width: 900px; + margin: 0 auto; + } + + .authorities-header { + text-align: center; + margin-bottom: var(--spacing-2xl); + } + + .authorities-header h1 { + font-size: var(--font-size-2xl); + color: var(--text-primary); + margin-bottom: var(--spacing-xs); + } + + .authorities-header p { + color: var(--text-secondary); + font-size: var(--font-size-sm); + } + + .authority-group { + margin-bottom: var(--spacing-xl); + } + + .authority-group-header { + display: flex; + align-items: center; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-md); + padding-bottom: var(--spacing-sm); + border-bottom: 2px solid var(--border-color, #e5e7eb); + } + + .authority-group-header h2 { + font-size: var(--font-size-lg); + color: var(--text-primary); + margin: 0; + } + + .authority-group-icon { + font-size: 1.5em; + } + + .authority-members { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: var(--spacing-md); + } + + .authority-card { + display: flex; + align-items: center; + gap: var(--spacing-md); + padding: var(--spacing-md); + background: white; + border-radius: var(--radius-lg); + border: 1px solid var(--border-color, #e5e7eb); + text-decoration: none; + color: inherit; + transition: var(--transition); + } + + .authority-card:hover { + border-color: var(--primary); + box-shadow: 0 4px 12px rgba(46, 72, 114, 0.1); + transform: translateY(-1px); + } + + .authority-avatar { + width: 52px; + height: 52px; + border-radius: 50%; + flex-shrink: 0; + overflow: hidden; + background: linear-gradient(135deg, var(--primary), #8b5cf6); + display: flex; + align-items: center; + justify-content: center; + } + + .authority-avatar img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .authority-avatar-initial { + color: white; + font-weight: 700; + font-size: 18px; + } + + .authority-info { + flex: 1; + min-width: 0; + } + + .authority-name { + font-weight: 600; + font-size: var(--font-size-base); + color: var(--text-primary); + } + + .authority-role { + font-size: 11px; + font-weight: 600; + padding: 2px 8px; + border-radius: 4px; + display: inline-block; + margin-top: 3px; + } + + .role-prezes { background: #fef3c7; color: #92400e; } + .role-wiceprezes { background: #dbeafe; color: #1d4ed8; } + .role-czlonek_rady { background: #ecfdf5; color: #065f46; } + .role-komisja_rewizyjna { background: #f3e8ff; color: #6b21a8; } + .role-sad_kolezenski { background: #f3f4f6; color: #4b5563; } + + .authority-company { + font-size: var(--font-size-xs); + color: var(--text-secondary); + margin-top: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + @media (max-width: 640px) { + .authority-members { + grid-template-columns: 1fr; + } + } +{% endblock %} + +{% block content %} +
+
+

Władze Izby Norda Biznes

+

Kadencja 2023–2026

+
+ + {% for key, group in groups.items() %} + {% if group.members %} + + {% endif %} + {% endfor %} +
+{% endblock %} diff --git a/templates/company_detail.html b/templates/company_detail.html index b74a93e..58890c8 100755 --- a/templates/company_detail.html +++ b/templates/company_detail.html @@ -1720,6 +1720,10 @@ {% elif uc.role == 'EMPLOYEE' %} Pracownik {% endif %} + {% if uc.user.chamber_role_label %} + {% set _cr = uc.user.chamber_role %} + {{ uc.user.chamber_role_label }} + {% endif %} {% if uc.user.is_norda_member %} Norda {% endif %} diff --git a/templates/person_detail.html b/templates/person_detail.html index 38e4c6b..3f8cc43 100644 --- a/templates/person_detail.html +++ b/templates/person_detail.html @@ -245,8 +245,11 @@

{{ person.full_name() }} - {% if is_rada_member %} - Rada Izby + {% if person.user and person.user.chamber_role_label %} + {% set _cr = person.user.chamber_role %} + {{ person.user.chamber_role_label }} + {% elif is_rada_member %} + Rada Izby {% endif %}

{% set unique_company_ids = company_roles|map(attribute='company_id')|list|unique|list %} diff --git a/templates/user_profile.html b/templates/user_profile.html index 1e73358..310c5b0 100644 --- a/templates/user_profile.html +++ b/templates/user_profile.html @@ -219,8 +219,11 @@

{{ profile_user.name or 'Użytkownik' }} - {% if profile_user.is_rada_member %} - Rada Izby + {% if profile_user.chamber_role_label %} + {% set _cr = profile_user.chamber_role %} + {{ profile_user.chamber_role_label }} + {% elif profile_user.is_rada_member %} + Rada Izby {% endif %}