zephyr-it / shared
Shared traits, report pages, and utilities for Zephyr-IT modular applications.
Fund package maintenance!
zephyr-it
Requires
- php: ^8.4
- andreiio/blade-remix-icon: ^3.6
- blade-ui-kit/blade-heroicons: ^2.6
- codeat3/blade-phosphor-icons: ^2.3
- illuminate/contracts: ^10.0||^11.0||^12.0
- mallardduck/blade-lucide-icons: ^1.23
- malzariey/filament-daterangepicker-filter: ^3.4
- secondnetwork/blade-tabler-icons: ^3.33
- spatie/laravel-activitylog: ^4.10
- spatie/laravel-package-tools: ^1.16
- ysfkaya/filament-phone-input: ^3.1
Requires (Dev)
- bezhansalleh/filament-shield: ^3.3
- filament/filament: ^3.3
- friendsofphp/php-cs-fixer: ^3.75
- laravel/pint: ^1.14
- maatwebsite/excel: ^3.1
- mcamara/laravel-localization: ^2.3
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0.0||^9.0.0||^8.22.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- stancl/tenancy: ^3.9
Suggests
- filament/filament: Enables Filament integration.
- stancl/tenancy: Enables tenant-aware support for central connections.
README
Zephyr Shared is a foundational package that delivers shared logic, UI components, and developer tools across all Zephyr-IT modular Laravel applications.
It provides:
- A reusable
BasePlugin
system for Filament v3 (auto-register pages, resources, widgets) - Pre-configured Filament resources: Country, State, and City
- Export architecture for multi-sheet Excel reports using Laravel Excel
- A CLI script to check for missing language keys
- Centralized helpers, traits, and support classes for common functionality
π¦ Installation
Install via Composer:
composer require zephyr-it/shared
π Usage
βοΈ Run the Shared Installer
After installing the package, publish all shared configuration files, assets, migrations, and utilities by running:
php artisan shared:install
This command will:
- Publish shared configs, views, migrations, and scripts
- Publish third-party assets like
spatie/laravel-activitylog
migrations - Prompt you to run
php artisan migrate
To overwrite existing files:
php artisan shared:install --force
βΉοΈ This is the preferred way to initialize the shared stack within any Laravel application or module using Zephyr-ITβs architecture.
π€ Excel Report Exports
This package offers a powerful and flexible foundation for building multi-sheet Excel reports using Laravel Excel (maatwebsite/excel).
With built-in support for:
- π¨ Styled sheets and column formatting
- π Auto-generated legends and notes
- π― Custom filters, headings, and merged cells
- π Modular architecture for reusable reporting logic
β Laravel Excel is already required. No additional setup is necessary.
π§± Base Export Classes
π§Ύ BaseSheet
Defines a single Excel sheet. Implements:
FromCollection
,WithHeadings
,WithStyles
WithEvents
,WithTitle
,WithColumnFormatting
ShouldQueue
,ShouldAutoSize
Ideal for building modular, stylized reports.
ποΈ AbstractReportExport
Used to group and manage multiple BaseSheet
instances.
Also provides integrated date range parsing via the HasDateRangeParser
trait.
new CompanyUsersExport( $company, '2024-01-01 - 2024-01-31', $extraContext );
π BaseSheet Constructor Example
new UsersSheet( title: 'Users', data: $collection, headings: ['ID', 'Name'], headerColor: '1E88E5', legendRows: [['Column', 'Details']], legendStyles: [], columnWidths: ['A' => 15, 'B' => 30], mergeCells: ['A1:C1'], rowStyles: [], applyBorders: true, freezeHeader: true, useAutoFilter: true, enableLogging: true, notesRow: 'Data generated from system.' )
π§© Feature Breakdown
Feature | Description |
---|---|
notesRow |
Adds an intro row above headings for metadata or descriptions |
legendRows |
Displays a table legend for context (e.g., column definitions) |
legendStyles |
Style individual cells in the legend |
columnWidths |
Specify manual widths for columns |
mergeCells |
Merge specific cell ranges |
rowStyles |
Apply font, color, or alignment styles to specific rows |
headerColor |
Adds a background color and white text to the heading row |
useAutoFilter |
Enables Excel dropdown filters on the heading row |
freezeHeader |
Locks the top row while scrolling |
enableLogging |
Logs export metadata to storage/logs/laravel.log |
π¨ Extending Styles and Formats
You can override columnFormats()
and styles()
in your custom sheet:
public function columnFormats(): array { return [ 'C' => '#,##0.00', 'D' => 'dd-mm-yyyy', ]; } public function styles(Worksheet $sheet): array { return [ 2 => ['font' => ['bold' => true]], ]; }
π§΅ Advanced Customization
Override registerEvents()
in your BaseSheet
class to:
- Customize borders
- Dynamically merge cells
- Alter freeze pane logic
- Inject
notesRow
orlegendRows
conditionally
β Full Export Flow
Step 1: Define the Sheet
class UsersSheet extends BaseSheet { public function columnFormats(): array { return ['C' => '#,##0.00']; } }
Step 2: Create the Export
class CompanyUsersExport extends AbstractReportExport { public function sheets(): array { return [ new UsersSheet( title: 'Users', data: $this->entity->users->map(fn ($u) => [$u->id, $u->name, $u->balance]), headings: ['ID', 'Name', 'Balance'], notesRow: 'This report shows active users only.', legendRows: [['Balance', 'Userβs current account balance']], ) ]; } }
Step 3: Trigger the Export
Immediate download:
return Excel::download( new CompanyUsersExport($company, 'this_month'), 'company-users.xlsx' );
Queued export:
Excel::queue( new CompanyUsersExport($company, 'last_30_days'), 'exports/company.xlsx' );
π¦ Real-World Use Cases
- Export buttons on Filament table listings
- Scheduled exports via Laravel scheduler
- Admin and compliance reporting
- API-based report generators
- Multi-sheet financial summaries
- Dynamic data audits filtered by date range
π FilamentBase
π Creating a Modular Filament Plugin
Zephyr Shared provides a powerful BasePlugin
class to streamline plugin development for Filament v3. It enables clean, modular registration of your Pages, Resources, and Widgets β all based on convention.
π What BasePlugin
Does
- β Auto-registers Filament components via reflection
- β Detects correct namespaces and directories based on file location
- β Keeps plugin definitions DRY and declarative
- β Supports toggling discovery for Pages, Widgets, and Resources
π§° How to Create a Plugin
To build a plugin for your module (e.g., Accounts
), extend BasePlugin
:
namespace ZephyrIt\Accounts\Filament; use ZephyrIt\Shared\FilamentBase\Plugins\BasePlugin; use Filament\Navigation\NavigationGroup; class AccountsPlugin extends BasePlugin { public function getId(): string { return 'accounts'; } protected function navigationGroups(): array { return [ NavigationGroup::make() ->label(__('accounts::navigations.groups.transactions')) ->icon('tabler-report-money') ->collapsed(), NavigationGroup::make() ->label(__('accounts::navigations.groups.configuration')) ->icon('tabler-settings') ->collapsed(), ]; } }
π Required Folder Structure
To enable auto-discovery, structure your module as follows:
src/
βββ Filament/
βββ Pages/
βββ Resources/
βββ Widgets/
βββ AccountsPlugin.php
Any components inside Pages/
, Resources/
, and Widgets/
will be automatically registered when the plugin is loaded.
π§ͺ Register in Filament Panel
To enable your plugin in the Filament panel:
use ZephyrIt\Accounts\Filament\AccountsPlugin; public function panel(Panel $panel): Panel { return $panel ->plugin(AccountsPlugin::make()); }
βοΈ Optional Discovery Controls
Want to skip registering specific components?
AccountsPlugin::make() ->registerPages(false) ->registerWidgets(false);
By using BasePlugin
, all your Zephyr module plugins remain:
- β Clean
- β Auto-wired
- β Convention-driven
- β Consistently modular
π Creating a Report Page with Filters
Zephyr Shared includes a ReportPage
base class to help you rapidly scaffold Filament-powered dashboard pages β complete with filters, permissions, and layout.
π Example: Custom Ledger Report Page
namespace ZephyrIt\Accounts\Filament\Pages; use ZephyrIt\Shared\FilamentBase\Pages\ReportPage; use Filament\Forms\Components\Select; class LedgerReport extends ReportPage { protected static ?string $navigationIcon = 'tabler-report'; protected static string $titleKey = 'accounts::navigations.labels.ledger_report'; public static function getNavigationGroup(): string { return __('accounts::navigations.groups.reports'); } protected function getFilterFormSchema(): array { return [ Select::make('account_id') ->label(__('accounts::labels.account')) ->relationship('account', 'name') ->searchable(), ]; } }
β Built-In Features
When extending ReportPage
, you automatically get:
- π A date range picker with todayβs date as the default
- π§ Navigation label and title via
titleKey
- π Grouping via
getNavigationGroup()
- π§± Layout managed by Filamentβs dashboard base
- π Optional custom filters via
getFilterFormSchema()
- π Role-based access control with
HasPageShield
π§ Global Helper Functions
All helpers are available globally from src/Helpers/bootstrap_helpers.php
.
π’ FormatHelpers
numberToIndianFormat(1234567); // "12,34,567" numberToWord(123); // "one hundred twenty-three" formatNumberShort(1500000); // "1.5M" formatAddButtonLabel('User'); // "Add User" formatCopyMessage('Email'); // "Copied Email"
π§΅ StringHelpers
sanitizeAndFormat('John DOE'); // "John Doe" sanitizeSpecialCharacters('Hey#$', ' ') // "Hey" normalizeString('Γrgerlich'); // "Argerlich"
𧬠EnumHelpers
getEnumValues(StatusEnum::class); // ['active', 'inactive'] getEnumLabels(StatusEnum::class); // ['active' => 'Active', ...] getFilteredEnumStatuses(StatusEnum::class, ['archived']);
π¦ ModuleHelpers
getActiveModules(); // ['cms', 'hrm', 'insurance'] getModuleModels('cms'); // ['Page', 'Post'] getAllModelPaths(); getModuleNamespace('cms'); // App\Modules\Cms clearModuleCache();
π§© ModelHelpers
getAllModelClasses(); // Fully-qualified class names resolveFieldCast(User::class, 'email'); // 'string' getLastRecord(User::class, 'created_at'); // Latest created user
π§° GeneratorHelpers
generateUniqueNumber(User::class, 'USR', 'code'); // "USR/2025/001" generateYears(2000, 2025); // [2000, ..., 2025] getClassesFromDirectory(app_path('Models'), 'App\\Models');
πΈ ApplicationHelpers
getCurrencySymbol(); // βΉ getDenominationsArray('currency'); transformSupportedLocales(); // ['en' => 'English', ...]
βοΈ InstallHelper
Functions
Used in commands and setup logic
install_publish($this, [['tag' => 'toolkit-config']]); install_publish_files($this, base_path('publish')); install_publish_migrations($this, base_path('migrations'));
π BaseModel
A foundational Eloquent model that serves as the base for all Zephyr-IT models.
β Key Features
- Auto-tracks
fillable
changes usingSpatie\Activitylog
- Lifecycle control via
HasLifecycleHooks
- Easily extended with shared scopes (
HasCommonScopes
)
use ZephyrIt\Shared\Models\BaseModel; use Spatie\Activitylog\LogOptions; class Invoice extends BaseModel { public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logFillable() ->logOnlyDirty(); } }
π€ BaseAuthModel
Extends Laravel's Authenticatable
with the same lifecycle behaviors.
π Use It For:
User
,Admin
,Staff
, or other models with Laravel Auth
use ZephyrIt\Shared\Models\BaseAuthModel; class Staff extends BaseAuthModel { // Includes auth + lifecycle logic }
π§© Model Concerns
𧬠HasLifecycleHooks
Encapsulates transactional lifecycle control for Eloquent model events.
π§ Key Features
- Wraps create/update/save/delete/restore in DB transactions
- Uses simple overridable methods for each lifecycle phase
- Supports automatic cascade delete/restore for relationships
π Soft Delete Cascade
Enable cascade delete/restore on related models:
protected bool $shouldSyncRelatedSoftDeletes = true; protected function relatedModelsForSoftDelete(): array { return ['tasks', 'comments']; }
β¨ Lifecycle Hook Methods
You can override any of the following:
protected function performCreating(): void {} protected function performAfterCreate(): void {} protected function performUpdating(): void {} protected function performAfterUpdate(): void {} protected function performSaving(): void {} protected function performAfterSave(): void {} protected function performDeleting(): void {} protected function performAfterDelete(): void {} protected function performRestoring(): void {} protected function performAfterRestore(): void {}
All methods are optional β define only what you need.
β Example Usage
use ZephyrIt\Shared\Models\BaseModel; class Invoice extends BaseModel { protected bool $shouldSyncRelatedSoftDeletes = true; protected function relatedModelsForSoftDelete(): array { return ['lineItems']; } public function lineItems() { return $this->hasMany(LineItem::class); } protected function performAfterUpdate(): void { AuditLogger::log("Invoice {$this->id} was updated."); } }
π HasCommonScopes
A collection of reusable and schema-safe Eloquent query scopes.
π§ Purpose
- Add common filtering, searching, and ordering methods
- Skip queries safely if the expected column doesnβt exist
- Ideal for multi-tenant or optional schema scenarios
π Column Guard
Every scope uses hasColumn()
internally:
protected function hasColumn(Builder $query, string $column): bool
Ensures the query only runs if the column is present.
π§° Available Scopes
Scope | Description |
---|---|
active() |
is_active = true (safe fallback) |
inactive() |
is_active = false |
whereBoolean() |
Generic boolean filter with column check |
whereStatus() |
Filters by a status value |
ordered() |
Order by any column + direction |
recent() |
Order by created_at DESC |
latestFirst() |
Most recent records by any column |
oldestFirst() |
Oldest records by any column |
search() |
LIKE %term% search on a single column |
β Example Usage
User::active() ->whereStatus('verified') ->search('email', 'example.com') ->latestFirst() ->get();
Each scope checks for the existence of the target column β meaning no more "column not found" errors in evolving or modular schemas.
π UniqueEncryptedRule
This custom validation rule ensures encrypted fields remain unique across your models β even when stored as ciphertext.
It is designed specifically for cases where traditional unique:
validation can't compare encrypted values in the database.
β When to Use It
- You store fields like
email
,phone
, orSSN
encrypted in the DB - You need to prevent duplicate entries without decrypting every row
- You want secure validation while editing (excluding the current model)
π§ͺ Example Usage
Standalone instantiation:
use ZephyrIt\Shared\Rules\UniqueEncryptedRule; new UniqueEncryptedRule(User::class, 'email', $this->user, 'name');
User::class
β target model'email'
β encrypted column$this->user
β optional current model (for edit forms)'name'
β optional display column for error messages
In a Form Request:
public function rules(): array { return [ 'email' => [ 'required', new UniqueEncryptedRule(User::class, 'email', $this->user), ], ]; }
π Localization Message
Add this to your resources/lang/en/messages.php
file to customize the validation error:
'unique_encrypted' => 'The :attribute already exists (used by :existing).',
:existing
will be replaced with the value from the optional "display column" (likename
or
π± Smart Seeding System with BaseSeeder
The BaseSeeder
class provides a safe, idempotent, and environment-aware seeding entrypoint for Zephyr-IT applications. It is used at the top level (DatabaseSeeder
) to orchestrate and manage all module and development seeders.
β Module-specific seeders (e.g.
AccountsSeeder
,UserSeeder
) should extend the defaultIlluminate\Database\Seeder
, notBaseSeeder
.
β Key Features
- π Logs seeder runs in the
seed_log
table to prevent duplicate execution - β»οΈ Automatically suppresses
spatie/laravel-activitylog
during seeding - βοΈ Smart
run()
logic that separates production vs. development seeders - π Nested seeding supported via Laravelβs native
$this->call()
system
π§ Setup
Ensure the seed_log
tracking table exists by running:
php artisan vendor:publish --tag=shared-migrations php artisan migrate
π§ͺ Actual Usage Pattern
β
DatabaseSeeder
using BaseSeeder
namespace Database\Seeders; use ZephyrIt\Shared\Support\BaseSeeder; use ZephyrIt\Shared\Database\Seeders\WorldDatabaseSeeder; class DatabaseSeeder extends BaseSeeder { public function run(): void { $this->command->info('π Starting database seeding...'); $baseSeeders = [ WorldDatabaseSeeder::class, ShieldSeeder::class, UserSeeder::class, PlanSeeder::class, FeatureSeeder::class, ProductSeeder::class, ]; $developmentSeeders = [ TenantSeeder::class, // Add other dev/test seeders here ]; $seeders = app()->isProduction() ? $baseSeeders : array_merge($baseSeeders, $developmentSeeders); $this->call($seeders); $this->command->info('β Database seeding completed.'); } }
β AccountsSeeder
β does NOT extend BaseSeeder
namespace ZephyrIt\Accounts\Database\Seeders; use Illuminate\Database\Seeder; class AccountsSeeder extends Seeder { public function run(): void { $this->call([ AccountTypeSeeder::class, DefaultAccountsSeeder::class, ]); } }
This pattern keeps your module seeders lightweight and clean, using Laravel's core Seeder
class.
β Running Seeders
To run shared or environment-specific seeders via DatabaseSeeder
:
php artisan db:seed
To run a specific one:
php artisan db:seed --class="ZephyrIt\Shared\Database\Seeders\WorldDatabaseSeeder"
π Benefits of BaseSeeder
- π‘ Prevents duplicate seed execution using
seed_log
- π§ Auto-suppresses activity logs during seeding
- π Restores log state even if an exception occurs
- π§΅ Supports full project orchestration without touching module logic
π¨ Useful Shared Traits
These traits help you keep your services, charts, and multi-tenant logic modular, DRY, and production-ready across Zephyr-IT modules.
1οΈβ£ ColorPaletteTrait
Dynamically generate a consistent color palette for charts (e.g. Chart.js, ApexCharts) with fallback to random RGBA values.
β Features
- Returns an array of RGBA strings with optional opacity
- Ensures visual consistency using preconfigured base colors
- Auto-fills additional entries with randomized color variants
β Example Usage
use ZephyrIt\Shared\Traits\ColorPaletteTrait; class RevenueChartService { use ColorPaletteTrait; public function getChartColors(int $count): array { return $this->getColors($count); } }
πΌοΈ Sample Output
[ 'backgroundColor' => ['rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)', ...], 'borderColor' => ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', ...], ]
This array can be injected directly into chart configuration objects.
2οΈβ£ DeterminesTenant
Resolves the current tenant based on the domain, using the stancl/tenancy
package.
β Features
- Reads the current domain via
request()->getHost()
- Queries the
domains
table to find the matchingTenant
- Returns the resolved tenant model β or
null
if not found
β Example Usage
use ZephyrIt\Shared\Traits\DeterminesTenant; class TenantAwareReportService { use DeterminesTenant; public function getTenantName(): ?string { return optional($this->determineTenant())->name; } }
π‘ Safe for CLI Use
Returns null
if php artisan
is running (e.g. in queue workers, commands, or test runners).
3οΈβ£ HasDateRangeParser
A smart, flexible parser for handling UI-driven or backend-passed date ranges in a clean, uniform format.
β Example Usage
[$start, $end] = $this->parseDateRange('2024-05-01 - 2024-05-31'); [$from, $to] = $this->parseDateRangeAsStrings(['01/05/2024', '31/05/2024']);
ποΈ Supported Input Formats
'2024-05-01 - 2024-05-31'
(string range)['2024-05-01', '2024-05-31']
(array of ISO dates)['01/05/2024', '31/05/2024']
(array of D/M/Y format)
This trait is extremely useful for:
- Report filtering
- Dashboard widgets
- Scheduled exports
- API endpoints expecting flexible date inputs
π Dynamic Metrics Engine
The DynamicMetricsTrait
provides a flexible, powerful way to aggregate, filter, and chart dynamic data metrics across any set of models. It is ideal for use in Filament dashboards, admin reports, and time-based insights.
β Key Features
- Aggregate metrics (sum, count, list, avg) over time
- Group by single or nested keys (e.g., date β user β status)
- Chart-ready structure with interval-aware logic (daily/weekly/monthly)
- Optional
Closure
-based filters and dynamic groupings - Works across multiple models in a single call
π How to Use
1οΈβ£ fetchDynamicMetricsData()
Fetches grouped, nested metric results across a time range.
$results = $this->fetchDynamicMetricsData( models: [Invoice::class, Payment::class], metrics: [ 'total_amount' => ['amount', 'sum'], 'avg_settlement_days' => ['registered_date', 'avg', fn ($group) => $group->whereNotNull('settlement_date')], ], startDate: now()->subMonth(), endDate: now(), groupByColumn: fn ($item) => [$item->created_at->format('Y-m'), $item->status] );
π§ Output will be grouped by month + status, and metrics are aggregated accordingly.
2οΈβ£ getMetricChartData()
Returns an array of values suitable for charting over time (daily, weekly, monthly).
$chartData = $this->getMetricChartData( models: [Invoice::class], metricType: 'sum', column: 'amount', startDate: now()->subMonths(3), endDate: now() );
Returns something like:
[1200, 1500, 1800, 2100] // One value per interval
3οΈβ£ getMetricData()
Calculates a final, formatted metric (e.g. grand total or count) over time and multiple models.
$totalRevenue = $this->getMetricData( models: [Invoice::class], metricType: 'sum', column: 'amount', startDate: now()->subQuarter(), endDate: now() );
β
Automatically returns formatted string using numberToIndianFormat()
helper.
π§ Advanced Techniques
π Auto Interval Selection
The trait automatically picks an interval based on date range:
- β€ 7 days β
daily
- β€ 90 days β
weekly
- β€ 365 days β
monthly
-
365 days β
quarterly
Override logic manually by customizing the determineDateInterval()
method.
π§ͺ Conditional Metrics
Use closures or arrays to apply conditional logic to a group:
'only_pending' => ['amount', 'sum', fn ($group) => $group->where('status', 'pending')]
Or:
[ 'field' => 'status', 'operator' => '=', 'value' => 'pending', ]
𧬠Nested Grouping
Support multi-dimensional grouping via groupByColumn
closure:
fn ($item) => [$item->created_at->format('Y-m'), $item->user_id]
π§° Helper Methods (Internal Use)
Method | Description |
---|---|
prepareDateRange() |
Defaults to current month if no date provided |
generateDateRange() |
Builds list of intervals for aggregation |
calculateIntervalMetric() |
Aggregates values within each interval |
deepMerge() |
Recursively merges numeric/array values |
recursiveGroupBy() |
Handles multi-dimensional grouping |
processMetricGroup() |
Applies aggregate functions on grouped data |
π¦ Real-World Use Cases
- Filament dashboard KPIs with date filters
- Line/bar/pie charts with
Chart.js
orApexCharts
- Admin exports of grouped metrics (per user, per status, etc.)
- Aggregating metrics across multi-model data lakes (e.g.
Invoice
,Payment
,Transaction
)
π§ͺ Testing
Run the full test suite using:
composer test
This ensures that all traits, resources, commands, and integrations function as expected across environments.
π Changelog
Refer to the CHANGELOG for a full history of updates, bug fixes, and feature releases.
π€ Contributing
We welcome contributions! Please review our CONTRIBUTING guide before submitting issues or pull requests.
π Security
If you discover any security vulnerabilities, please refer to our security policy for responsible disclosure guidelines.
π§ Credits
- @abbasmashaddy72 β Project Lead
- Zephyr-IT Team β Core Maintainers
- All Contributors β Special thanks to everyone who has contributed!
π License
This project is open-source software licensed under the MIT License.