-
Notifications
You must be signed in to change notification settings - Fork 28
feat(panel-admin): Marketing cluster with Discord Dashboard #260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
cdf2c67
wip
danielhe4rt 3e77693
feat(integration-discord): capture all Discord gateway events into da…
danielhe4rt edda785
feat(panel-admin): add Marketing cluster with Discord Dashboard
danielhe4rt 2a4ccf5
feat(panel-admin): wire Discord Dashboard to real queries and fix PHP…
danielhe4rt 209156d
chore: remove dump files and fix PHPStan error in MeetingShowcasePage
danielhe4rt 9231c32
fix: remove non-existent occurred_at from RawGatewayEvent and suppres…
danielhe4rt 9d1ee92
fix(integration-discord): remove non-existent occurred_at from discor…
danielhe4rt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Panel Admin — Context | ||
|
|
||
| The admin panel (`/admin`) is the operational hub for the He4rt Developers community. It is a Filament v5 panel that provides dashboards, resource management, and moderation tools for community administrators. | ||
|
|
||
| ## Glossary | ||
|
|
||
| | Term | Definition | Not to be confused with | | ||
| | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | ||
| | **Cluster** | A Filament navigation group that acts as a sub-panel with its own sidebar. Implemented as a `Cluster` class with `$slug` and `$shouldRegisterSubNavigation = false`. | Filament NavigationGroup (simpler, no sub-navigation) | | ||
| | **Marketing** | Cluster focused on community growth analytics — Discord activity dashboards, meeting showcase. Slug: `marketing`. | The marketing team (people); here it's a panel section | | ||
| | **Discord Dashboard** | A Filament Page under Marketing that surfaces Discord community metrics (messages, voice, users) with configurable time ranges and rolling comparisons. | The Discord bot itself (`bot-discord` module) | | ||
| | **Meeting Showcase** | A Filament Page under Marketing that generates visual cards of meeting participants for social media sharing. | The `meeting` domain module (data layer) | | ||
| | **Rolling comparison** | The pattern of subdividing a selected time range into equal sub-periods to show evolution. 14d→2×7d, 30d→4×7d, 90d→3×30d, etc. | Quarter-based comparison (fixed calendar periods) | | ||
| | **Period breakdown** | A widget showing sub-period metrics side by side with multiple visualization modes (summary, table, cards, bars, donut). | The timeline chart (shows daily granularity) | | ||
| | **Activity by DOW** | Aggregated activity per day-of-week (Mon→Sun) with toggle between All/Messages/Voice. Uses stacked bar chart in "All" mode. | Heatmap (shows hour×day matrix) | | ||
|
|
||
| ## Module boundaries | ||
|
|
||
| Panel Admin is a **view layer** module. It: | ||
|
|
||
| - **Reads from** `activity` (messages, voice), `identity` (external identities), `moderation` (cases, appeals, actions) | ||
| - **Never writes** domain data — only reads for display | ||
| - **Owns** its Filament Pages, Widgets, Resources, and Blade views | ||
| - **Does not** contain domain logic, models, or migrations | ||
|
|
||
| ## Structure | ||
|
|
||
| ``` | ||
| panel-admin/ | ||
| ├── src/ | ||
| │ ├── Marketing/ | ||
| │ │ ├── MarketingCluster.php | ||
| │ │ ├── Pages/ | ||
| │ │ │ ├── DiscordDashboard.php | ||
| │ │ │ └── MeetingShowcasePage.php | ||
| │ │ └── Widgets/ | ||
| │ │ └── DiscordStatsWidget.php | ||
| │ ├── Moderation/ | ||
| │ │ ├── ModerationCluster.php | ||
| │ │ ├── Pages/ | ||
| │ │ ├── Resources/ | ||
| │ │ ├── Widgets/ | ||
| │ │ └── Livewire/ | ||
| │ ├── Filament/Resources/ | ||
| │ ├── Pages/ | ||
| │ │ └── Dashboard.php | ||
| │ └── PanelAdminServiceProvider.php | ||
| ├── resources/views/ | ||
| │ ├── marketing/ | ||
| │ └── moderation/ | ||
| ├── lang/{en,pt_BR}/ | ||
| ├── config/panel-admin.php | ||
| └── docs/adr/ | ||
| ``` | ||
|
danielhe4rt marked this conversation as resolved.
|
||
|
|
||
| ## Navigation pattern | ||
|
|
||
| Each cluster has a dedicated navigation builder method in `PanelAdminServiceProvider`. When the URL path contains the cluster slug (e.g. `marketing/`), the sidebar switches to show a "Back to Admin" link + the cluster's sub-navigation. Default navigation shows all clusters as top-level items. | ||
|
|
||
| ## Architectural decisions | ||
|
|
||
| Recorded in [`docs/adr/`](docs/adr/): | ||
|
|
||
| - [ADR-0001](docs/adr/0001-discord-dashboard-architecture.md) — Discord Dashboard: widget structure, rolling comparison pattern, query layer, timezone handling, component extensions | ||
110 changes: 110 additions & 0 deletions
110
app-modules/panel-admin/docs/adr/0001-discord-dashboard-architecture.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # ADR-0001: Discord Dashboard Architecture | ||
|
|
||
| **Status:** Accepted | ||
| **Date:** 2026-05-19 | ||
| **Deciders:** danielhe4rt | ||
|
|
||
| ## Context | ||
|
|
||
| The He4rt Developers community needs visibility into Discord activity patterns for two purposes: | ||
|
|
||
| 1. **Scheduling events** — knowing when the community is most active (day-of-week × hour) to pick optimal times for lives, workshops, and meetings | ||
| 2. **Measuring community health** — tracking whether engagement (messages, voice, unique users) is growing or declining over time | ||
|
|
||
| The data already exists in the `messages` and `voice_messages` tables (from the `activity` module), but there is no way to visualize trends, compare periods, or identify peak hours without running raw SQL. | ||
|
|
||
| ## Decision | ||
|
|
||
| ### Dashboard structure | ||
|
|
||
| A single Filament Page (`DiscordDashboard`) under a new `MarketingCluster`, with the following widgets top-to-bottom: | ||
|
|
||
| | Widget | Purpose | Implementation | | ||
| | ----------------- | -------------------------------------- | ------------------------------------------------------------------------- | | ||
| | Stats overview | Quick KPIs with sparklines | Filament `StatsOverviewWidget` with `->chart()` | | ||
| | Period breakdown | Rolling comparison between sub-periods | Custom Blade with 5 switchable views (summary, table, cards, bars, donut) | | ||
| | Activity timeline | Daily trend over the full range | Chart.js line chart (msgs + voice + users) | | ||
| | Heatmap | Peak hours identification | `x-he4rt::dashboard.heatmap` component (SVG, week variant) | | ||
| | Activity by DOW | Best day-of-week for events | `x-he4rt::dashboard.bar-chart` with stacked mode toggle (All/Msgs/Voice) | | ||
| | Top channels | Most active channels | `x-he4rt::dashboard.bar-chart` ranked | | ||
|
|
||
| ### Rolling comparison pattern | ||
|
|
||
| Instead of fixed calendar quarters, the selected time range is subdivided into equal sub-periods: | ||
|
|
||
| | Range | Subdivision | | ||
| | ----- | ----------- | | ||
| | 90d | 3 × 30d | | ||
| | 60d | 2 × 30d | | ||
| | 30d | 4 × ~7d | | ||
| | 14d | 2 × 7d | | ||
| | 7d | 7 × 1d | | ||
| | 1d | 24 × 1h | | ||
|
|
||
| **Why:** Rolling comparison adapts to any range and avoids the problem of incomplete quarters. Stats cards show last sub-period vs previous; period breakdown shows all sub-periods. | ||
|
|
||
| ### Query layer | ||
|
|
||
| Each widget has a dedicated `final readonly` query class with a `builder()` method, located in `Marketing/Discord/Queries/`. The Page instantiates queries and passes results to the view. Heatmap data for messages and voice comes from separate query classes — the Page combines them. | ||
|
danielhe4rt marked this conversation as resolved.
|
||
|
|
||
| `PeriodStats` is a single class with private methods per metric to keep cohesion while avoiding class explosion. | ||
|
|
||
| ### Summary view design | ||
|
|
||
| The period breakdown's "summary" mode uses a 50/50 layout: | ||
|
|
||
| - **Left:** Line chart comparing sub-periods overlaid on the same axis (e.g. Mon→Sun with 4 lines: Msgs W1, Msgs W2, Voice W1, Voice W2). Solid = previous, dashed = current. | ||
| - **Right:** Narrative text blocks with trend icons, absolute values, and a contextual recommendation box that adapts based on the data pattern. | ||
|
|
||
| ### Channel names deferred | ||
|
|
||
| Channel IDs are stored as Discord snowflakes with no name resolution. Dedicated Discord entity tables (channels, roles, members) are a separate future initiative. The dashboard shows channel IDs for now. | ||
|
|
||
| ### Timezone | ||
|
|
||
| Fixed to `America/Sao_Paulo`. The community is Brazilian and there is no use case for other timezones. Consistent with the existing Meeting Showcase page. | ||
|
|
||
| ### Component extensions | ||
|
|
||
| The `x-he4rt::dashboard.bar-chart` Blade component was extended with backward-compatible props: | ||
|
|
||
| - `stacked` (bool) — enables segmented bars | ||
| - `segments` (array per item) — defines bar segments with label, value, color | ||
| - `legend` (array) — renders a legend below the chart | ||
|
|
||
| All existing usages remain unaffected. | ||
|
|
||
| ## Alternatives Considered | ||
|
|
||
| ### A — Filament Widgets for everything | ||
|
|
||
| Use `ChartWidget` and `StatsOverviewWidget` for all visualizations. Rejected because: heatmap has no native widget, period breakdown needs 5 switchable views that don't map to any widget, and mixing native widgets with custom Blade creates inconsistent layout control. | ||
|
|
||
| ### B — Fixed quarter comparison | ||
|
|
||
| Compare Q1 vs Q2 vs Q3 with calendar-aligned periods. Rejected because: the current quarter is always incomplete, making the comparison misleading. Rolling comparison is more flexible and avoids this problem. | ||
|
|
||
| ### C — He4rt design system components for stats | ||
|
|
||
| Use `x-he4rt::dashboard.stat-card` instead of Filament's `StatsOverviewWidget`. Rejected because: Filament's widget has built-in sparklines via `->chart()`, lazy loading, and polling — reimplementing these in custom Blade would be wasted effort. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| - Community managers can identify peak hours at a glance (heatmap + DOW chart) | ||
| - Rolling comparison makes weekly/monthly trends visible without manual SQL | ||
| - The summary view with narrative text makes data accessible to non-technical team members | ||
| - Stacked bar-chart extension is reusable across other dashboards | ||
| - No new domain modules or models required — reads from existing tables | ||
|
|
||
| ### Negative | ||
|
|
||
| - Chart.js is loaded via CDN (`@assets`) — adds an external dependency to the admin panel | ||
| - Charts inside `x-show` containers require `x-intersect` workarounds for Chart.js canvas sizing | ||
| - Channel IDs instead of names degrades UX until Discord entity tables are built | ||
|
|
||
| ### Risks | ||
|
|
||
| - Voice data may be sparse or missing for some periods (observed: zero voice events in recent 14 days). The dashboard must handle empty datasets gracefully. | ||
| - Large time ranges (90d) on high-volume servers may produce slow heatmap queries. Mitigated by: indexed columns (`tenant_id, sent_at`), and query classes can add caching later without view changes. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| return [ | ||
| 'navigation' => [ | ||
| 'cluster' => 'Marketing', | ||
| 'cluster_breadcrumb' => 'Marketing', | ||
| 'back_to_admin' => 'Back to Admin', | ||
| 'meeting_showcase' => 'Meeting Showcase', | ||
| 'discord_dashboard' => 'Discord Dashboard', | ||
| ], | ||
| ]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| return [ | ||
| 'navigation' => [ | ||
| 'cluster' => 'Marketing', | ||
| 'cluster_breadcrumb' => 'Marketing', | ||
| 'back_to_admin' => 'Voltar pro Admin', | ||
| 'meeting_showcase' => 'Meeting Showcase', | ||
| 'discord_dashboard' => 'Discord Dashboard', | ||
| ], | ||
| ]; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.