Skip to content

feat(panel-admin): Marketing cluster with Discord Dashboard#260

Merged
danielhe4rt merged 7 commits into
4.xfrom
feat/admin-base-marketing
May 19, 2026
Merged

feat(panel-admin): Marketing cluster with Discord Dashboard#260
danielhe4rt merged 7 commits into
4.xfrom
feat/admin-base-marketing

Conversation

@danielhe4rt
Copy link
Copy Markdown
Contributor

@danielhe4rt danielhe4rt commented May 19, 2026

Summary

  • Add Marketing cluster in admin panel sidebar with dedicated sub-navigation
  • Add Discord Dashboard page with real-time community analytics (messages, voice, users)
  • Move Meeting Showcase page into Marketing cluster
  • Add 6 query classes (ActivityPerDay, VoicePerDay, MessageHeatmap, VoiceHeatmap, TopChannels, PeriodStats) with proper UTC→BRT timezone handling
  • Extend x-he4rt::dashboard.bar-chart component with backward-compatible stacked mode
  • Add CONTEXT.md and ADR-0001 for panel-admin module

Dashboard widgets

  • Stats overview — Filament StatsOverviewWidget with sparklines and rolling comparison
  • Period breakdown — 5 switchable views (summary with line chart + narrative, table, cards, stacked bars, donut)
  • Activity timeline — Chart.js line chart (messages + voice + users)
  • Heatmap — SVG day-of-week × hour matrix
  • Activity by DOW — horizontal bar chart with All/Msgs/Voice toggle (stacked mode)
  • Top Channels — ranked horizontal bar chart

Performance

  • PeriodStats uses CASE WHEN conditional aggregates to fetch all sub-periods in 2 queries instead of N×3
  • once() memoization prevents duplicate query execution within a request
  • Heatmap data queried once and shared between heatmap + DOW widgets

Test plan

  • Navigate to /admin/marketing/discord and verify all widgets render with real data
  • Switch range selector (7d, 14d, 30d) and confirm data updates
  • Verify heatmap shows correct day/hour assignments (Mon 22h SP activity should show on Mon, not Tue)
  • Toggle Activity by DOW between All/Msgs/Voice modes
  • Switch period breakdown between all 5 views (summary, table, cards, bars, donut)
  • Verify Meeting Showcase still works at /admin/marketing/meeting-showcase
  • Check sidebar navigation shows Marketing cluster with back button when inside cluster

Description

This PR adds a Marketing cluster to the admin panel with a comprehensive Discord Dashboard providing real-time community analytics across messages, voice activity, and user engagement. Six query classes handle activity metrics with UTC↔BRT timezone conversion, the bar-chart component is extended with backward-compatible stacked mode support, and two new pages deliver Discord Dashboard analytics and Meeting Showcase participant visualization. Includes documentation, English/Portuguese-Brazil translations, and performance optimizations using conditional aggregates and memoization.

References

  • Related: feat: voice event persistence and Discord gateway data lake (#259)
  • Related commit: edda785 (wire Discord Dashboard to real queries and fix PHPStan)

Dependencies & Requirements

  • Timezone: America/Sao_Paulo (BRT/UTC) for all dashboard queries
  • JavaScript: Chart.js via CDN for timeline and chart visualization
  • Frontend: Alpine.js for reactive dashboard state management
  • Export: html2canvas library for PNG export functionality
  • Database: Added occurred_at timestamp (indexed) to discord_event_logs table
  • Framework: Filament admin panel components and pages

Contributor Summary

Contributor Lines Added Lines Removed Files Changed
PR Author 2,250 49 25

Changes Summary

File Path Change Description
.gitignore Added /storage/private/dumps directory to ignore patterns
CONTEXT-MAP.md Registered Panel Admin bounded context entry
app-modules/bot-discord/src/Events/RawGatewayEvent.php Refactored user_id extraction and payload JSON encoding to flatten structure
app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php Updated payload assertions for flattened structure; removed obsolete fallback tests
app-modules/he4rt/resources/views/components/dashboard/bar-chart.blade.php Extended with stacked bar support: added stacked, legend, segments props with conditional rendering
app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php Added indexed occurred_at timestamp column
app-modules/integration-discord/src/Models/DiscordEventLog.php Added occurred_at to fillable and datetime casting
app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php Updated tests to populate occurred_at during record creation
app-modules/panel-admin/CONTEXT.md Added 64-line context document with module boundaries, glossary, and navigation rules
app-modules/panel-admin/docs/adr/0001-discord-dashboard-architecture.md Added ADR documenting dashboard design, query layer, widget architecture, and alternatives
app-modules/panel-admin/lang/en/marketing.php Added English translations for Marketing cluster navigation
app-modules/panel-admin/lang/pt_BR/marketing.php Added Portuguese-Brazil translations for Marketing cluster navigation
app-modules/panel-admin/resources/views/marketing/discord-dashboard.blade.php Created 666-line dashboard template with stats, timeline chart, heatmap, DOW activity, and top channels
app-modules/panel-admin/resources/views/pages/meeting-showcase.blade.php Created 471-line showcase page with customizable grid, presets, and PNG export functionality
app-modules/panel-admin/src/Marketing/MarketingCluster.php Created Filament cluster class with navigation configuration
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/DiscordDashboard.php Created page controller managing dashboard state, data loading, and formatting logic
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/ActivityPerDay.php Created query class for daily message count and unique user aggregation
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php Created query class for 7-day × 24-hour message heatmap with DOW/hour grouping
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php Created query class with conditional aggregates for multi-period statistics and sparklines
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php Created query class for top 10 channels by message volume and distinct users
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php Created query class for voice join activity heatmap with DOW/hour breakdown
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php Created query class for daily voice join counts converted to hours (0.75 multiplier)
app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php Created page with participant loader, Discord metadata extraction, and avatar URL fallback logic
app-modules/panel-admin/src/Marketing/Widgets/DiscordStatsWidget.php Created stats widget with metrics, trend indicators, and sparkline charts
app-modules/panel-admin/src/PanelAdminServiceProvider.php Updated service provider to register Marketing cluster pages and navigation builder

Review Change Stack

…ta lake

Adds a discord_event_logs table and a raw gateway listener that
persists every Discord event (MESSAGE_CREATE, VOICE_STATE_UPDATE,
GUILD_MEMBER_ADD, etc.) with full payload for future analytics.
Registered via Laracord AFTER_BOOT hook on the Discord client.
Introduce a Marketing navigation cluster in the admin panel with two pages:
Discord Dashboard (community analytics) and Meeting Showcase (moved from
top-level pages). The dashboard surfaces Discord activity metrics with
configurable time ranges, rolling period comparison, heatmap, and channel
rankings using hardcoded data as a prototype for the query layer.

- Add MarketingCluster with dedicated sidebar navigation
- Add DiscordDashboard page with stats widget, timeline chart, heatmap,
  activity-by-DOW with All/Msgs/Voice toggle, top channels, and period
  breakdown with 5 switchable views (summary, table, cards, bars, donut)
- Add DiscordStatsWidget using Filament StatsOverviewWidget with sparklines
- Extend bar-chart Blade component with stacked mode (segments + legend)
- Move MeetingShowcasePage into Marketing cluster
- Add CONTEXT.md and ADR-0001 for panel-admin module
…Stan

Replace hardcoded dashboard data with query classes that hit the database.
Each query uses AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo' for
correct timezone conversion on timestamp-without-timezone columns.

- Add 6 query classes: ActivityPerDay, VoicePerDay, MessageHeatmap,
  VoiceHeatmap, TopChannels, PeriodStats
- PeriodStats uses conditional aggregates (CASE WHEN) to fetch all
  sub-periods in 2 queries instead of N×3, with once() memoization
- Fix division-by-zero in DiscordStatsWidget when previous period is zero
- Fix MeetingShowcasePage PHPStan error (groupBy returns stdClass, not Model)
- Fix RawGatewayEvent unnecessary nullsafe operator before null coalescing
- Remove prototype HTML file
- Add ADR reference to panel-admin CONTEXT.md
- Remove meeting-showcase.html and voice-chat-2026-05-11.json from tracked files
- Add PHPDoc object shape annotation to fix undefined property access on groupBy result
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@danielhe4rt has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 13 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7a69ae17-23b2-4349-b5fe-acee0c3f9908

📥 Commits

Reviewing files that changed from the base of the PR and between 209156d and 9d1ee92.

📒 Files selected for processing (2)
  • app-modules/bot-discord/src/Events/RawGatewayEvent.php
  • app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
📝 Walkthrough

Walkthrough

This PR introduces a complete marketing admin panel alongside the existing moderation interface. The implementation adds a Discord activity dashboard featuring timeline and heatmap visualizations, rolling-period comparisons with stat cards, per-day-of-week activity breakdowns, and top-channel rankings. A meeting showcase feature enables participants to be loaded from message data by date and channel, rendered in a customizable grid, and exported as PNG. The bar chart component gains stacked segment support with optional legend rendering. Event persistence is enhanced with an occurred_at timestamp column on discord_event_logs. Documentation includes a module context file, an architecture decision record defining the dashboard design, and translations for the marketing section.

Suggested reviewers

  • BrunoSFreschi
  • GabrielFVDev
  • davicbtoliveira
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a Marketing cluster to the admin panel with a Discord Dashboard as the primary feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php (1)

36-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Extend user_id extraction to support d.user.id field path and add regression test.

The current implementation only extracts from d.user_id and d.author.id, but Discord Gateway events with a nested user object use d.user.id for the user identity. Update line 20 of RawGatewayEvent.php to include this fallback: $payload->d->user_id ?? $payload->d->author->id ?? $payload->d->user->id ?? null, then add a test case covering the d.user.id extraction path to prevent regression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php` around
lines 36 - 55, RawGatewayEvent currently only checks d->user_id and
d->author->id when extracting the user id; update the extraction in
RawGatewayEvent (where it builds $userId from the payload) to also fallback to
$payload->d->user->id (i.e. $payload->d->user_id ?? $payload->d->author->id ??
$payload->d->user->id ?? null) and add a PHPUnit test in the RawGatewayEventTest
that constructs a payload with d.user.id set and asserts the created
DiscordEventLog->user_id matches that value to prevent regression.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app-modules/bot-discord/src/Events/RawGatewayEvent.php`:
- Line 20: Restore the missing fallback for extracting user_id in
RawGatewayEvent.php: when building the 'user_id' field (currently using
$payload->d->user_id ?? $payload->d->author->id ?? null), add the
$payload->d->user->id fallback so member events (GUILD_MEMBER_ADD /
GUILD_MEMBER_UPDATE) are handled; update the expression that constructs
'user_id' to include $payload->d->user->id in the fallback chain (reference: the
'user_id' assignment in RawGatewayEvent.php).

In
`@app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php`:
- Line 23: The migration defines the occurred_at column using timestamp(), which
creates a timezone-naive TIMESTAMP; change the column definition to use
timestampTz() (i.e., replace the call to timestamp('occurred_at') with
timestampTz('occurred_at')->index()) so PostgreSQL uses TIMESTAMP WITH TIME ZONE
and avoids UTC/BRT conversion errors when reading/writing the occurred_at field
in the Discord event logs migration.

In `@app-modules/panel-admin/CONTEXT.md`:
- Around line 28-54: The fenced code block in CONTEXT.md (the directory tree
example) is missing a language tag which triggers MD040; update the opening
fence from ``` to ```text so the block is explicitly marked as plain text (leave
the closing fence as ```), ensuring the lint rule passes and the docs render
consistently.

In `@app-modules/panel-admin/docs/adr/0001-discord-dashboard-architecture.md`:
- Line 48: Update the ADR text that currently references the old query location
so it matches the implemented classes: change the documented path
"Marketing/Discord/Queries/" to the actual location
"Marketing/Pages/Discord/Dashboard/Queries/"; keep the rest of the sentence
about each widget having a final readonly query class with a builder() method
and the Page instantiating queries and combining heatmap data from separate
message/voice query classes so the documentation correctly points to the
implemented classes used by the Page.

In
`@app-modules/panel-admin/resources/views/marketing/discord-dashboard.blade.php`:
- Around line 148-154: The chart data is truncating fractional voice-hours by
casting $b['voice'] to (int); update every occurrence (e.g., the combined
'value' => $b['msgs'] + (int) $b['voice'] and the segments entry ['label' =>
'Voice', 'value' => (int) $b['voice']]) to preserve fractions — remove the (int)
cast and instead use the original numeric value or a controlled float like
round($b['voice'], 2) so the combined value and the 'Voice' segment reflect
fractional hours accurately.
- Around line 220-233: The chart currently hardcodes w1 = this.tl.slice(0,7) and
w2 = this.tl.slice(7,14) which breaks for ranges other than 14 days; change the
splitting logic to compute a dynamic half based on this.tl.length (e.g. const
half = Math.ceil(this.tl.length / 2)), set const w1 = this.tl.slice(0, half) and
const w2 = this.tl.slice(half, half + half) (or to end) and derive labels from
w1 (or from dates in this.tl) instead of the fixed days array, then map datasets
(Msgs and Voice) from w1.map(...) and w2.map(...) and, if needed, pad w2 with
nulls/zeros so both datasets align; update the Chart creation (sChart = new
Chart(...)) to use these computed labels and arrays so Sem 1 / Sem 2 reflect the
selected range correctly.

In `@app-modules/panel-admin/resources/views/pages/meeting-showcase.blade.php`:
- Around line 104-113: In the html2canvas call that creates the canvas (the
block using html2canvas with options scale, useCORS, allowTaint), remove or set
allowTaint to false so the canvas is not tainted before calling
canvas.toDataURL('image/png'); keep useCORS: true and ensure remote images have
proper CORS headers (or route them through a proxy) so images load without
tainting; update the options passed to html2canvas and verify canvas.toDataURL
and link.click still work after this change.

In
`@app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/DiscordDashboard.php`:
- Line 23: The public property rangeDays is client-hydrated and must be clamped
to an allowlist before queries to prevent tampered large windows; in mount() and
in updatedRangeDays() normalize/validate rangeDays against a predefined
allowlist (e.g., an array of allowed integers) and set it to a safe default if
invalid, then call loadData() only after the normalized value is assigned; also
apply the same normalization wherever rangeDays is used (references: property
rangeDays, methods mount(), updatedRangeDays(), and loadData()).
- Around line 144-154: Replace the hard-coded Portuguese weekday abbreviations
in the $dayLabels array with localized strings by loading translation keys and
using the app's translation helper (e.g. __() or trans()) inside the
DiscordDashboard code where $dayLabels is defined; update the $dayLabels
assignment to pull keys like 'weekdays.mon'...'weekdays.sun' (or your chosen
keys) so the existing foreach that builds $this->activityByDow (using $msgsByDow
and $voiceByDow) continues to use the same indexes and structure but outputs
localized labels.

In `@app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php`:
- Around line 61-68: The result of Message::query()->...->get() is inferred as
Collection<Message>, so the aggregate column total_messages isn't a declared
model property; update the code that builds $messageStats to fetch base-query
rows instead (e.g., replace the terminal ->get() with ->getQuery()->get() so
$messageStats becomes a collection of stdClass rows exposing total_messages) or,
if you must keep Eloquent models, access the aggregate via
$row->getAttribute('total_messages') when reading the value; adjust the
Message::query() block (and the code that reads $messageStats) accordingly.
- Around line 54-56: The early-return branch in MeetingShowcasePage that checks
if $this->channelId, $this->startDate, or $this->endDate are empty should clear
any previously loaded state before returning; update the block surrounding the
if ($this->channelId === '' || $this->startDate === '' || $this->endDate === '')
check to reset the page's loaded data (for example, set $this->participants to
an empty array and clear any result/loaded flags such as $this->loaded or
$this->hasResults) so stale participants or flags are not left visible when
filters are incomplete.

In `@app-modules/panel-admin/src/PanelAdminServiceProvider.php`:
- Line 83: The route check currently uses $requestPath->contains('marketing/')
so requests to '.../marketing' won't match; replace that condition to check the
marketing prefix (e.g. use $requestPath->startsWith('marketing') or a regex that
matches '^marketing(/|$)') so both the marketing root and nested marketing/*
routes invoke marketingNavigation($builder); update the condition where
$requestPath->contains('marketing/') is used to this new startsWith/regex-based
check.

In `@storage/private/dumps/meeting-showcase.html`:
- Around line 598-609: The current grid.innerHTML = users.map(...) inserts
untrusted fields (name, username, avatar_url) directly into HTML causing XSS;
replace this with DOM construction inside the users.map loop:
createElement('div') for the "user-card", create an img element and set img.src
= avatar || placeholder via setAttribute or assignment, attach an
img.addEventListener('error', ...) to set a safe fallback, and set
name/username/msgs via textContent (not innerHTML) on span elements; then append
each card to grid (or use a DocumentFragment) instead of assigning innerHTML.
Ensure you remove any inline event handlers/markup injection and only use safe
attribute setters and textContent for the variables name, uname and msgs.

In `@storage/private/dumps/voice-chat-2026-05-11.json`:
- Around line 1-408: The committed JSON "voice-chat-2026-05-11.json" contains
raw PII and must be removed: delete the file from the repo history and working
tree (use git rm --cached then commit), add the filename to your repo .gitignore
to prevent re-adding, replace the tracked file with a sanitized schema-only
example if an example is needed, and move the real export to secure
non-versioned storage (and if the data was shared, follow your org's
data-exposure remediation such as rotating any exposed credentials). If the file
must be purged from history, run a history-rewrite tool (git filter-repo or BFG)
to remove all historical commits containing voice-chat-2026-05-11.json.

---

Outside diff comments:
In `@app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php`:
- Around line 36-55: RawGatewayEvent currently only checks d->user_id and
d->author->id when extracting the user id; update the extraction in
RawGatewayEvent (where it builds $userId from the payload) to also fallback to
$payload->d->user->id (i.e. $payload->d->user_id ?? $payload->d->author->id ??
$payload->d->user->id ?? null) and add a PHPUnit test in the RawGatewayEventTest
that constructs a payload with d.user.id set and asserts the created
DiscordEventLog->user_id matches that value to prevent regression.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a891ea0c-5f08-4155-8b06-b2bc01ac45da

📥 Commits

Reviewing files that changed from the base of the PR and between 42918f4 and 2a4ccf5.

📒 Files selected for processing (27)
  • .gitignore
  • CONTEXT-MAP.md
  • app-modules/bot-discord/src/Events/RawGatewayEvent.php
  • app-modules/bot-discord/tests/Feature/Events/RawGatewayEventTest.php
  • app-modules/he4rt/resources/views/components/dashboard/bar-chart.blade.php
  • app-modules/integration-discord/database/migrations/2026_05_19_155732_create_discord_event_logs_table.php
  • app-modules/integration-discord/src/Models/DiscordEventLog.php
  • app-modules/integration-discord/tests/Feature/Models/DiscordEventLogTest.php
  • app-modules/panel-admin/CONTEXT.md
  • app-modules/panel-admin/docs/adr/0001-discord-dashboard-architecture.md
  • app-modules/panel-admin/lang/en/marketing.php
  • app-modules/panel-admin/lang/pt_BR/marketing.php
  • app-modules/panel-admin/resources/views/marketing/discord-dashboard.blade.php
  • app-modules/panel-admin/resources/views/pages/meeting-showcase.blade.php
  • app-modules/panel-admin/src/Marketing/MarketingCluster.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/DiscordDashboard.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/ActivityPerDay.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php
  • app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php
  • app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
  • app-modules/panel-admin/src/Marketing/Widgets/DiscordStatsWidget.php
  • app-modules/panel-admin/src/PanelAdminServiceProvider.php
  • storage/private/dumps/meeting-showcase.html
  • storage/private/dumps/voice-chat-2026-05-11.json

Comment thread app-modules/bot-discord/src/Events/RawGatewayEvent.php
Comment thread app-modules/panel-admin/CONTEXT.md
Comment thread app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
Comment thread app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
Comment thread app-modules/panel-admin/src/PanelAdminServiceProvider.php
Comment thread storage/private/dumps/meeting-showcase.html Outdated
Comment thread storage/private/dumps/voice-chat-2026-05-11.json Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php`:
- Around line 58-59: The start/end date parsing currently converts local
midnights to UTC, excluding most of the selected end day; update the Date::parse
calls in MeetingShowcasePage.php so you normalize to the local day's boundaries
(call startOfDay() on the start Date and endOfDay() on the end Date) before
calling utc(), i.e. modify the logic that sets $start and $end (the variables
produced from Date::parse($this->startDate, 'America/Sao_Paulo') and
Date::parse($this->endDate, 'America/Sao_Paulo')) to use startOfDay() and
endOfDay() respectively and then convert to UTC so whereBetween covers the full
selected end day.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 895597eb-9ab4-48b3-809f-c228619e40f9

📥 Commits

Reviewing files that changed from the base of the PR and between 2a4ccf5 and 209156d.

📒 Files selected for processing (1)
  • app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php

Comment thread app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php
…d_event_logs

The column was never migrated to the database. Remove from model fillable,
casts, migration definition, and test fixtures. The occurred_at references
in DTOs and Actions are for other tables (voice_messages, moderation_events)
and remain unchanged.
@MariiMartins
Copy link
Copy Markdown

10/10

@sirelves
Copy link
Copy Markdown

LGTM!

@danielhe4rt danielhe4rt merged commit 163da31 into 4.x May 19, 2026
6 checks passed
@danielhe4rt danielhe4rt deleted the feat/admin-base-marketing branch May 19, 2026 23:34
danielhe4rt added a commit that referenced this pull request May 20, 2026
## Summary

- Adds `app.display_timezone` config key (`DISPLAY_TIMEZONE` env,
default `America/Sao_Paulo`) to centralize the user-facing timezone
- Changes `app.timezone` default from `America/Sao_Paulo` to `UTC`
- Replaces all 15 hardcoded `'America/Sao_Paulo'` occurrences across 15
files with `config('app.display_timezone')`
- Uses parameterized SQL bindings (`?`) for `AT TIME ZONE` clauses
instead of string interpolation
- Adds `AT TIME ZONE` conversion to moderation heatmap SQL that was
missing it entirely
- Converts all user-facing timestamp displays (SLA deadlines, connection
dates, schedule cards) to use the display timezone

## Modules affected

- `config/app.php` — new config key
- `bot-discord` — GreetingsEvent
- `he4rt` — heatmap highlight + schedule-card
- `panel-admin` — marketing dashboard queries (6 files) + moderation
heatmap + appeal views
- `integration-discord` — BackfillVoiceLogsCommand CLI output
- `routes/console.php` — backup:monitor schedule
- `resources/views` — connection-hub

## Test plan

- [x] Rector dry-run — no suggestions
- [x] Pint — passed
- [x] PHPStan — 0 errors
- [x] Tests — 497 passed, 3 skipped
- [x] Verified `AT TIME ZONE ?` binding works via tinker
- [ ] Verify heatmaps render correctly in the admin panel
- [ ] Verify SLA deadline times display in Brazil timezone
- [ ] Verify schedule-card shows correct local times

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Description
Centralizes the user-facing timezone by adding app.display_timezone (env
DISPLAY_TIMEZONE, default America/Sao_Paulo) and switching the
application default timezone to UTC. Replaces hard-coded
America/Sao_Paulo occurrences with config('app.display_timezone'),
parameterizes SQL AT TIME ZONE bindings, fixes a missing AT TIME ZONE in
the moderation heatmap, and converts user-facing timestamp displays to
the new display timezone.

## References
- PR: #262
- PR: #260
- PostgreSQL AT TIME ZONE docs:
https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-TIMEZONES

## Dependencies & Requirements
- New config key: app.display_timezone (env: DISPLAY_TIMEZONE, default:
America/Sao_Paulo)
- app.timezone default changed to UTC (env: APP_TIMEZONE)
- .env.example updated with APP_TIMEZONE=UTC and
DISPLAY_TIMEZONE=America/Sao_Paulo
- No new composer/npm packages added
- Database must accept parameterized AT TIME ZONE bindings (validated
via tinker)
- Manual verifications pending: admin heatmaps rendering, SLA deadline
display, schedule-card local times

## Contributor Summary
| Contributor | Lines Added | Lines Removed | Files Changed |
|---|---:|---:|---:|
| gvieira18 | 63 | 45 | 15 |

## Changes Summary
| File Path | Change Description |
|---|---|
| config/app.php | Added display_timezone key; changed default
app.timezone to UTC |
| .env.example | Added APP_TIMEZONE and DISPLAY_TIMEZONE entries |
| app-modules/bot-discord/src/Events/GreetingsEvent.php | Use
display_timezone for greeting-hour calculation |
|
app-modules/he4rt/resources/views/components/dashboard/heatmap.blade.php
| Heatmap current-time highlight uses display_timezone |
| app-modules/he4rt/resources/views/components/schedule-card.blade.php |
Normalize schedule timestamps to display_timezone |
|
app-modules/integration-discord/src/ETL/Console/BackfillVoiceLogsCommand.php
| Format CLI log timestamps with display_timezone |
|
app-modules/panel-admin/resources/views/moderation/appeal-queue/appeal-detail.blade.php
| SLA deadline displays converted to display_timezone with null fallback
|
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/ActivityPerDay.php
| Use display_timezone and bind into SQL AT TIME ZONE |
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/MessageHeatmap.php
| Parameterized timezone bindings for day/hour extraction |
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoiceHeatmap.php
| Parameterized timezone bindings in voice heatmap query |
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/VoicePerDay.php
| Day-extraction query updated to use display_timezone |
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/PeriodStats.php
| Compute subdivisions using display_timezone |
|
app-modules/panel-admin/src/Marketing/Pages/Discord/Dashboard/Queries/TopChannels.php
| Date range calculations use display_timezone |
| app-modules/panel-admin/src/Marketing/Pages/MeetingShowcasePage.php |
Parse meeting bounds with display_timezone and convert to UTC |
|
app-modules/panel-admin/src/Moderation/Livewire/ModerationDashboardLivewire.php
| Added AT TIME ZONE conversion and parameterized bindings for
moderation heatmap |
| resources/views/livewire/connection-hub.blade.php | "Connected at"
timestamp uses display_timezone; props formatting |
| routes/console.php | Schedule backup:monitor to run at 09:00 in
display_timezone |
|
database/migrations/2026_05_20_160249_alter_messages_sent_at_to_timestamptz.php
| Migration: convert messages.sent_at to timestamptz interpreting
existing values as UTC |

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/he4rt/heartdevs.com/pull/264?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: danielhe4rt <danielhe4rt@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants