feat(homepage): redesign events section with 2-column grid, forum topic and new members
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
Add admitted_at_meeting_id to Company model linking firms to board meetings. Homepage now shows 2 events (left column) + latest forum topic and new members admitted at the last board meeting (right column). Responsive single-column on mobile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
110d971dca
commit
4ec8e15c56
@ -139,7 +139,7 @@ def index():
|
||||
'user_registered': registered,
|
||||
'user_can_attend': can_attend,
|
||||
})
|
||||
if len(upcoming_events) >= 6:
|
||||
if len(upcoming_events) >= 2:
|
||||
break
|
||||
|
||||
# Backward compat — next_event used by other parts
|
||||
@ -182,6 +182,29 @@ def index():
|
||||
all_releases = _get_releases()
|
||||
latest_release = all_releases[0] if all_releases else None
|
||||
|
||||
# Latest forum topic for homepage
|
||||
latest_forum_topic = None
|
||||
try:
|
||||
from database import ForumTopic
|
||||
latest_forum_topic = db.query(ForumTopic).filter(
|
||||
ForumTopic.is_deleted == False
|
||||
).order_by(ForumTopic.created_at.desc()).first()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# New members admitted at last board meeting
|
||||
latest_admitted = []
|
||||
last_meeting = None
|
||||
try:
|
||||
from database import BoardMeeting
|
||||
last_meeting = db.query(BoardMeeting).order_by(BoardMeeting.meeting_date.desc()).first()
|
||||
if last_meeting:
|
||||
latest_admitted = db.query(Company).filter(
|
||||
Company.admitted_at_meeting_id == last_meeting.id
|
||||
).order_by(Company.name).all()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return render_template(
|
||||
'index.html',
|
||||
companies=companies,
|
||||
@ -195,7 +218,10 @@ def index():
|
||||
zopk_facts=zopk_facts,
|
||||
latest_release=latest_release,
|
||||
company_children=company_children,
|
||||
company_parent=company_parent
|
||||
company_parent=company_parent,
|
||||
latest_forum_topic=latest_forum_topic,
|
||||
latest_admitted=latest_admitted,
|
||||
last_meeting=last_meeting
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@ -848,6 +848,7 @@ class Company(Base):
|
||||
norda_biznes_url = Column(String(500))
|
||||
norda_biznes_member_id = Column(String(50))
|
||||
member_since = Column(Date) # Data przystąpienia do Izby NORDA
|
||||
admitted_at_meeting_id = Column(Integer, ForeignKey('board_meetings.id'), nullable=True)
|
||||
previous_years_debt = Column(Numeric(10, 2), default=0) # Zaległości z lat poprzednich (ręcznie wpisane)
|
||||
|
||||
# Metadata
|
||||
@ -887,6 +888,9 @@ class Company(Base):
|
||||
krs_last_audit_at = Column(DateTime) # Data ostatniego audytu KRS
|
||||
krs_pdf_path = Column(Text) # Ścieżka do pliku PDF
|
||||
|
||||
# Board meeting where company was admitted
|
||||
admitted_at_meeting = relationship('BoardMeeting', foreign_keys=[admitted_at_meeting_id])
|
||||
|
||||
# Relationships
|
||||
category = relationship('Category', back_populates='companies')
|
||||
services = relationship('CompanyService', back_populates='company', cascade='all, delete-orphan')
|
||||
|
||||
5
database/migrations/098_add_admitted_at_meeting_id.sql
Normal file
5
database/migrations/098_add_admitted_at_meeting_id.sql
Normal file
@ -0,0 +1,5 @@
|
||||
-- Migration 098: Add admitted_at_meeting_id to companies
|
||||
-- Links companies to the board meeting where they were admitted as members
|
||||
|
||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS admitted_at_meeting_id INTEGER REFERENCES board_meetings(id);
|
||||
GRANT ALL ON TABLE companies TO nordabiz_app;
|
||||
@ -193,7 +193,19 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Homepage 2-column grid */
|
||||
.homepage-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.homepage-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.events-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@ -1080,62 +1092,120 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Event Banners - Najbliższe wydarzenia -->
|
||||
{% if upcoming_events %}
|
||||
<div class="events-filter" data-animate="fadeIn">
|
||||
<span class="events-filter-label">Pokaż:</span>
|
||||
<button class="events-filter-btn active" data-filter="all" onclick="filterEvents('all', this)">Wszystkie</button>
|
||||
<button class="events-filter-btn" data-filter="norda" onclick="filterEvents('norda', this)">🏢 Norda Biznes</button>
|
||||
<button class="events-filter-btn" data-filter="external" onclick="filterEvents('external', this)">🌐 Zewnętrzne</button>
|
||||
</div>
|
||||
<div class="events-row" data-animate="fadeIn">
|
||||
{% for ue in upcoming_events %}
|
||||
{% set ev = ue.event %}
|
||||
<a href="{{ url_for('calendar.calendar_event', event_id=ev.id) }}" class="event-banner" data-event-type="{{ 'external' if ev.is_external else 'norda' }}">
|
||||
<div class="event-banner-top">
|
||||
<div class="event-banner-icon">📅</div>
|
||||
<div class="event-banner-content">
|
||||
{% if loop.first %}
|
||||
<div class="events-row-label">Najbliższe wydarzenia – Kto weźmie udział?</div>
|
||||
{% endif %}
|
||||
<div class="event-banner-title">
|
||||
{{ ev.title }} →
|
||||
{% if ev.is_external and ev.external_source %}
|
||||
<span style="display:inline-block; background:rgba(255,255,255,0.2); color:#fff; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:600; vertical-align:middle; margin-left:6px;">🌐 {{ ev.external_source }}</span>
|
||||
{% endif %}
|
||||
{% if ev.access_level == 'admin_only' %}
|
||||
<span style="display:inline-block; background:#ef4444; color:#fff; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:600; vertical-align:middle; margin-left:6px;">UKRYTE</span>
|
||||
{% elif ev.access_level == 'rada_only' %}
|
||||
<span style="display:inline-block; background:#f59e0b; color:#92400e; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:600; vertical-align:middle; margin-left:6px;">IZBA</span>
|
||||
<!-- Homepage Grid: Events + Forum + New Members -->
|
||||
{% if upcoming_events or latest_forum_topic or latest_admitted %}
|
||||
<div class="homepage-grid" data-animate="fadeIn">
|
||||
|
||||
<!-- LEFT COLUMN: 2 Events -->
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-md);">
|
||||
{% if upcoming_events %}
|
||||
{% for ue in upcoming_events[:2] %}
|
||||
{% set ev = ue.event %}
|
||||
<a href="{{ url_for('calendar.calendar_event', event_id=ev.id) }}" class="event-banner" data-event-type="{{ 'external' if ev.is_external else 'norda' }}" style="margin: 0;">
|
||||
<div class="event-banner-top">
|
||||
<div class="event-banner-icon">📅</div>
|
||||
<div class="event-banner-content">
|
||||
{% if loop.first %}
|
||||
<div class="events-row-label">Najbliższe wydarzenia – Kto weźmie udział?</div>
|
||||
{% endif %}
|
||||
<div class="event-banner-title">
|
||||
{{ ev.title }} →
|
||||
{% if ev.is_external and ev.external_source %}
|
||||
<span style="display:inline-block; background:rgba(255,255,255,0.2); color:#fff; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:600; vertical-align:middle; margin-left:6px;">🌐 {{ ev.external_source }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="event-banner-meta">
|
||||
<span>📆 {{ ev.event_date.strftime('%d.%m.%Y') }} ({{ ['Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob', 'Nd'][ev.event_date.weekday()] }})</span>
|
||||
{% if ev.time_start %}
|
||||
<span>🕕 {{ ev.time_start.strftime('%H:%M') }}</span>
|
||||
{% endif %}
|
||||
{% if ev.location %}
|
||||
<span>📍 {{ ev.location[:30] }}{% if ev.location|length > 30 %}...{% endif %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-banner-meta">
|
||||
<span>📆 {{ ev.event_date.strftime('%d.%m.%Y') }} ({{ ['Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob', 'Nd'][ev.event_date.weekday()] }})</span>
|
||||
{% if ev.time_start %}
|
||||
<span>🕕 {{ ev.time_start.strftime('%H:%M') }}</span>
|
||||
{% endif %}
|
||||
{% if ev.location %}
|
||||
<span>📍 {{ ev.location[:30] }}{% if ev.location|length > 30 %}...{% endif %}</span>
|
||||
</div>
|
||||
<div class="event-banner-bottom">
|
||||
<div class="event-banner-attendees">
|
||||
👥 Zapisanych: {{ ev.attendee_count }} {% if ev.attendee_count == 1 %}osoba{% elif ev.attendee_count in [2,3,4] %}osoby{% else %}osób{% endif %}
|
||||
</div>
|
||||
<div class="event-banner-action">
|
||||
{% if ue.user_registered %}
|
||||
<span class="btn-light btn-registered">✓ Jesteś zapisany/a</span>
|
||||
{% elif ue.user_can_attend %}
|
||||
<button type="button" class="btn-light" onclick="rsvpAndGo(event, {{ ev.id }})">Zapisz się →</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-banner-bottom">
|
||||
<div class="event-banner-attendees">
|
||||
👥 Zapisanych: {{ ev.attendee_count }} {% if ev.attendee_count == 1 %}osoba{% elif ev.attendee_count in [2,3,4] %}osoby{% else %}osób{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN: Forum + New Members -->
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-md);">
|
||||
|
||||
<!-- Latest Forum Topic -->
|
||||
{% if latest_forum_topic %}
|
||||
<a href="{{ url_for('forum.forum_topic', topic_id=latest_forum_topic.id) }}" style="text-decoration: none; display: block; background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--spacing-md); transition: all 0.2s; min-height: 120px;" onmouseover="this.style.borderColor='var(--primary)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.08)'" onmouseout="this.style.borderColor='var(--border)';this.style.boxShadow='none'">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="font-size: 1.2rem;">💬</span>
|
||||
<span style="font-size: var(--font-size-xs); font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px;">Najnowszy wpis na forum</span>
|
||||
</div>
|
||||
<div class="event-banner-action">
|
||||
{% if ue.user_registered %}
|
||||
<span class="btn-light btn-registered">✓ Jesteś zapisany/a</span>
|
||||
{% elif ue.user_can_attend %}
|
||||
<button type="button" class="btn-light" onclick="rsvpAndGo(event, {{ ev.id }})">Zapisz się →</button>
|
||||
{% elif ev.access_level == 'rada_only' %}
|
||||
<span class="btn-light" style="background: #fef3c7; color: #92400e; border: 1px solid #fde68a;">🔒 Rada Izby</span>
|
||||
<div style="font-weight: 600; color: var(--text-primary); font-size: var(--font-size-base); line-height: 1.4; margin-bottom: 8px;">
|
||||
{{ latest_forum_topic.title }}
|
||||
</div>
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">
|
||||
{{ latest_forum_topic.author.name if latest_forum_topic.author else 'Anonim' }} · {{ latest_forum_topic.created_at|local_time('%d.%m.%Y %H:%M') }}
|
||||
{% if latest_forum_topic.reply_count is defined and latest_forum_topic.reply_count > 0 %}
|
||||
· {{ latest_forum_topic.reply_count }} {{ 'odpowiedź' if latest_forum_topic.reply_count == 1 else ('odpowiedzi' if latest_forum_topic.reply_count < 5 else 'odpowiedzi') }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- New Members -->
|
||||
{% if latest_admitted %}
|
||||
<a href="{{ url_for('public.new_members') }}" style="text-decoration: none; display: block; background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--spacing-md); transition: all 0.2s; min-height: 120px;" onmouseover="this.style.borderColor='var(--primary)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.08)'" onmouseout="this.style.borderColor='var(--border)';this.style.boxShadow='none'">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="font-size: 1.2rem;">🏢</span>
|
||||
<span style="font-size: var(--font-size-xs); font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px;">Nowi członkowie Izby</span>
|
||||
{% if last_meeting %}
|
||||
<span style="font-size: var(--font-size-xs); color: var(--text-muted);">· Rada {{ last_meeting.meeting_date.strftime('%d.%m.%Y') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
{% for company in latest_admitted[:4] %}
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<span style="color: var(--success); font-size: 14px;">✓</span>
|
||||
<span style="font-weight: 500; color: var(--text-primary); font-size: var(--font-size-sm);">
|
||||
{{ company.name }}
|
||||
</span>
|
||||
{% if company.status != 'active' %}
|
||||
<span style="font-size: var(--font-size-xs); color: var(--text-muted); font-style: italic;">· profil w trakcie uzupełniania</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if latest_admitted|length > 4 %}
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-muted);">+ {{ latest_admitted|length - 4 }} więcej</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="margin-top: 8px; font-size: var(--font-size-sm); color: var(--primary); font-weight: 500;">
|
||||
Zobacz wszystkich nowych członków →
|
||||
</div>
|
||||
</a>
|
||||
{% elif last_meeting %}
|
||||
<!-- No companies admitted yet at last meeting, show placeholder -->
|
||||
<div style="background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--spacing-md); min-height: 120px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<span style="font-size: 1.2rem;">🏢</span>
|
||||
<span style="font-size: var(--font-size-xs); font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px;">Nowi członkowie Izby</span>
|
||||
</div>
|
||||
<div style="color: var(--text-muted); font-size: var(--font-size-sm);">Brak nowych firm przyjętych na ostatnim posiedzeniu Rady.</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user