diff --git a/app/Enums/EuPlatescStatus.php b/app/Enums/EuPlatescStatus.php index 38b01f4b..8602473f 100644 --- a/app/Enums/EuPlatescStatus.php +++ b/app/Enums/EuPlatescStatus.php @@ -24,6 +24,7 @@ enum EuPlatescStatus: string case PAYMENT_DECLINED = 'payment_declined'; case POSSIBLE_FRAUD = 'possible_fraud'; case CHARGED = 'charged'; + case CAPTURE = 'capture'; public function labelKeyPrefix(): string { diff --git a/app/Filament/Exports/ExcelExportWithNotificationInDB.php b/app/Filament/Exports/ExcelExportWithNotificationInDB.php index e8d24019..44766870 100644 --- a/app/Filament/Exports/ExcelExportWithNotificationInDB.php +++ b/app/Filament/Exports/ExcelExportWithNotificationInDB.php @@ -11,6 +11,16 @@ class ExcelExportWithNotificationInDB extends ExcelExport { + public function getColumns(): array + { + $columns = []; + foreach ($this->evaluate($this->columns) as $column) { + $columns[$column->getName()] = $column; + } + + return $columns; + } + public function export() { $this->resolveFilename(); @@ -34,5 +44,11 @@ function () use ($authUser, $filename) { new ExportExcelNotification($authUser, $filename) ); }]); + + \Filament\Notifications\Notification::make('export') + ->title(__('notification.export.success.title')) + ->body(__('notification.export.success.body')) + ->success() + ->send(); } } diff --git a/app/Filament/Pages/Dashboard.php b/app/Filament/Pages/Dashboard.php index b12a2d6c..d023bf62 100644 --- a/app/Filament/Pages/Dashboard.php +++ b/app/Filament/Pages/Dashboard.php @@ -27,6 +27,6 @@ public function getHeaderWidgets(): array protected function getHeaderWidgetsColumns(): int { - return 3; + return 4; } } diff --git a/app/Filament/Resources/DonationResource.php b/app/Filament/Resources/DonationResource.php index 1c64ed9e..29fb84b1 100644 --- a/app/Filament/Resources/DonationResource.php +++ b/app/Filament/Resources/DonationResource.php @@ -6,6 +6,7 @@ use App\Enums\EuPlatescStatus; use App\Filament\Filters\DateFilter; +use App\Filament\Resources\DonationResource\Actions\ExportAction; use App\Filament\Resources\DonationResource\Pages; use App\Forms\Components\Link; use App\Models\Donation; @@ -148,6 +149,9 @@ public static function table(Table $table): Table DateFilter::make('created_at'), ]) ->defaultSort('created_at', 'desc') + ->headerActions([ + ExportAction::make('download'), + ]) ->actions([ ViewAction::make()->iconButton(), ]); diff --git a/app/Filament/Resources/DonationResource/Actions/ExportAction.php b/app/Filament/Resources/DonationResource/Actions/ExportAction.php new file mode 100644 index 00000000..a9fddc56 --- /dev/null +++ b/app/Filament/Resources/DonationResource/Actions/ExportAction.php @@ -0,0 +1,78 @@ +color('secondary'); + + try { + $this->exports([ + ExcelExportWithNotificationInDB::make() + ->withFilename(fn () => sprintf( + '%s-%s', + now()->format('Y_m_d-H_i_s'), + Str::slug(DonationResource::getPluralModelLabel()), + )) + ->fromTable() + + ->withColumns([ + Column::make('id') + ->heading('ID'), + + Column::make('organization.name') + ->formatStateUsing(fn ($state) => Str::upper($state)) + ->heading(__('organization.label.singular')), + + Column::make('project.name') + ->formatStateUsing(fn ($state) => Str::upper($state)) + ->heading(__('project.label.singular')), + + Column::make('full_name') + ->formatStateUsing(fn ($state) => Str::upper($state)) + ->heading(__('donation.labels.full_name')), + + Column::make('amount') + ->heading(__('donation.labels.amount')), + + Column::make('created_at') + ->formatStateUsing(fn (Donation $record) => $record->created_at->toFormattedDateTime()) + ->heading(__('donation.labels.created_at')), + + Column::make('updated_at') + ->formatStateUsing(fn (Donation $record) => $record->updated_at?->toFormattedDateTime()) + ->heading(__('donation.labels.status_updated_at')), + + Column::make('status') + ->formatStateUsing(fn (Donation $record) => $record->status->label()) + ->heading(__('donation.labels.status')), + + ]) + ->queue(), + ]); + } catch (\Throwable $exception) { + logger()->error($exception->getMessage()); + Notification::make('export') + ->title(__('notification.export.error.title')) + ->body(__('notification.export.error.body')) + ->danger() + ->send(); + } + } +} diff --git a/app/Filament/Resources/DonationResource/Pages/ListDonations.php b/app/Filament/Resources/DonationResource/Pages/ListDonations.php index 3912e188..9bc4a3d7 100644 --- a/app/Filament/Resources/DonationResource/Pages/ListDonations.php +++ b/app/Filament/Resources/DonationResource/Pages/ListDonations.php @@ -16,7 +16,6 @@ class ListDonations extends ListRecords protected function getActions(): array { return [ - Actions\CreateAction::make(), ]; } @@ -24,4 +23,5 @@ protected function getTableHeading(): string { return __('donation.header', ['number' => Donation::count()]); } + } diff --git a/app/Filament/Resources/OrganizationResource/Actions/Tables/ExportAction.php b/app/Filament/Resources/OrganizationResource/Actions/Tables/ExportAction.php index bdf1f509..740a74ce 100644 --- a/app/Filament/Resources/OrganizationResource/Actions/Tables/ExportAction.php +++ b/app/Filament/Resources/OrganizationResource/Actions/Tables/ExportAction.php @@ -4,9 +4,11 @@ namespace App\Filament\Resources\OrganizationResource\Actions\Tables; +use App\Enums\UserRole; use App\Filament\Exports\ExcelExportWithNotificationInDB; use App\Filament\Resources\OrganizationResource; use App\Models\Organization; +use Filament\Notifications\Notification; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Str; @@ -15,7 +17,7 @@ class ExportAction extends BaseAction { - protected string | null $status = null; + protected string|null $status = null; protected function setUp(): void { @@ -23,163 +25,160 @@ protected function setUp(): void $this->color('secondary'); $status = $this->name ?? ''; - - $this->exports([ - ExcelExportWithNotificationInDB::make() - ->withFilename(fn () => sprintf( - '%s-%s-%s', - now()->format('Y_m_d-H_i_s'), - Str::slug(OrganizationResource::getPluralModelLabel()), - $this->status - )) - ->modifyQueryUsing(function (Builder $query) use ($status) { - return $query - ->status($status) - ->with([ - 'activityDomains', - 'counties', - 'projects' => fn ($q) => $q->select('id', 'organization_id', 'status') - ->withCount('donations'), - 'users' => fn ($q) => $q->select('organization_id', 'name', 'role') - ->onlyOrganizationAdmins(), - ]) - ->withCount([ - 'volunteers', - ]); - }) - ->withColumns([ - Column::make('id') - ->heading('ID'), - - Column::make('name') - ->heading(__('organization.labels.name')), - - Column::make('status') - ->heading(__('organization.labels.status')), - - Column::make('cif') - ->heading(__('organization.labels.cif')), - - Column::make('description') - ->heading(__('organization.labels.description')), - - Column::make('activity_domains') - ->heading(__('organization.labels.activity_domains')) - ->formatStateUsing( - fn (Organization $record) => $record->activityDomains - ->pluck('name') - ->join(', ') - ), - - Column::make('users') - ->heading(__('organization.labels.admin')) - ->formatStateUsing( - fn (Collection $state) => $state->pluck('name') - ->join(', ') - ), - - Column::make('contact_person') - ->heading(__('organization.labels.contact_person')), - - Column::make('contact_phone') - ->heading(__('organization.labels.contact_phone')), - - Column::make('contact_email') - ->heading(__('organization.labels.contact_email')), - - Column::make('website') - ->heading(__('organization.labels.website')), - - Column::make('address') - ->heading(__('organization.labels.address')), - - Column::make('counties') - ->heading(__('organization.labels.counties')) - ->formatStateUsing( - fn (Organization $record) => $record->counties - ->pluck('name') - ->join(', ') - ), - - Column::make('accepts_volunteers') - ->heading(__('organization.labels.accepts_volunteers')) - ->formatStateUsing( - fn (Organization $record) => $record->accepts_volunteers - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('has_volunteers') - ->heading(__('organization.labels.has_volunteers')) - ->formatStateUsing( - fn (Organization $record) => $record->volunteers_count - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('projects_count') - ->heading(__('organization.labels.projects_count')) - ->formatStateUsing( - fn (Organization $record) => $record->projects - ->count() - ), - - Column::make('active_projects_count') - ->heading(__('organization.labels.active_projects_count')) - ->formatStateUsing( - fn (Organization $record) => $record->projects() - ->whereIsOpen() - ->count() - ), - - Column::make('has_projects') - ->heading(__('organization.labels.has_projects')) - ->formatStateUsing( - fn (Organization $record) => $record->projects->count() - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('has_active_projects') - ->heading(__('organization.labels.has_active_projects')) - ->formatStateUsing( - fn (Organization $record) => $record->projects()->whereIsOpen()->count() - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('has_eu_platesc') - ->heading(__('organization.labels.has_eu_platesc')) - ->formatStateUsing( - fn (Organization $record) => $record->eu_platesc_merchant_id !== null && $record->eu_platesc_private_key !== null - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('has_donations') - ->heading(__('organization.labels.has_donations')) - ->formatStateUsing( - fn (Organization $record) => $record->projects->sum('donations_count') - ? __('forms::components.select.boolean.true') - : __('forms::components.select.boolean.false') - ), - - Column::make('donations_count') - ->heading(__('organization.labels.donations_count')) - ->formatStateUsing( - fn (Organization $record) => $record->donations() - ->count() - ), - - Column::make('donations_amount') - ->heading(__('organization.labels.donations_amount')) - ->formatStateUsing( - fn (Organization $record) => $record->donations() - ->sum('amount') - ), - - ]) - ->queue(), - ]); + $this->status = $status; + + try { + $this->exports([ + ExcelExportWithNotificationInDB::make() + ->fromTable() + ->withFilename(fn () => sprintf( + '%s-%s-%s', + now()->format('Y_m_d-H_i_s'), + Str::slug(OrganizationResource::getPluralModelLabel()), + $this->status + )) + ->modifyQueryUsing(function (Builder $query) use ($status) { + return $query + ->addSelect(['accepts_volunteers', 'eu_platesc_merchant_id', 'eu_platesc_private_key', 'cif', 'description', 'address', 'contact_person', 'contact_phone', 'contact_email', 'website', 'facebook']) + ->status($status) + ->with([ + 'activityDomains', + 'counties', + 'projects' => fn ($q) => $q->select('id', 'organization_id', 'status') + ->withCount('donations'), + 'users' => fn ($q) => $q->select('organization_id', 'name', 'role') + ->onlyOrganizationAdmins(), + ]) + ->withCount([ + 'volunteers', + ]); + }) + ->withColumns([ + Column::make('id') + ->heading('ID'), + + Column::make('name') + ->heading(__('organization.labels.name')), + + Column::make('status') + ->formatStateUsing(fn (Organization $record) => $record->status->label()) + ->heading(__('organization.labels.status')), + + Column::make('accepts_donations') + ->heading(__('organization.labels.accepts_donations')) + ->formatStateUsing( + fn (Organization $record) => $record->accept_donations + ? __('forms::components.select.boolean.true') + : __('forms::components.select.boolean.false') + ), + + Column::make('counties') + ->heading(__('organization.labels.counties')) + ->formatStateUsing( + fn (Organization $record) => $record->counties + ->pluck('name') + ->join(', ') + ), + Column::make('description') + ->heading(__('organization.labels.description')), + + Column::make('activity_domains') + ->heading(__('organization.labels.activity_domains')) + ->formatStateUsing( + fn (Organization $record) => $record->activityDomains + ->pluck('name') + ->join(', ') + ), + + Column::make('cif') + ->heading(__('organization.labels.cif')), + + Column::make('statute') + ->heading(__('organization.labels.statute')) + ->formatStateUsing( + fn (Organization $record) => $record->has_statute + ? $record->statute_file + : __('forms::components.select.boolean.false') + ), + + Column::make('website') + ->heading(__('organization.labels.website')), + + Column::make('address') + ->heading(__('organization.labels.address')), + + Column::make('contact_phone') + ->heading(__('organization.labels.contact_phone')), + + Column::make('contact_email') + ->heading(__('organization.labels.contact_email')), + + Column::make('contact_person') + ->heading(__('organization.labels.contact_person')), + + Column::make('users') + ->heading(__('organization.labels.admin_count')) + ->formatStateUsing( + fn (Collection $state) => $state->filter( + fn ($record) => $record->role !== UserRole::ADMIN->value + )->count() + ), + + Column::make('accepts_volunteers') + ->heading(__('organization.labels.accepts_volunteers')) + ->formatStateUsing( + fn (Organization $record) => $record->accepts_volunteers + ? __('forms::components.select.boolean.true') + : __('forms::components.select.boolean.false') + ), + + Column::make('projects_count') + ->heading(__('organization.labels.projects_count')) + ->formatStateUsing( + fn (Organization $record) => $record->projects() + ->count() + ), + + Column::make('active_projects_count') + ->heading(__('organization.labels.active_projects_count')) + ->formatStateUsing( + function (Organization $record) { + $count = $record->projects() + ->whereIsOpen() + ->count(); + + return $count > 0 ? + $count : + '0'; + } + ), + + Column::make('donations_count') + ->heading(__('organization.labels.donations_count')) + ->formatStateUsing( + fn (Organization $record) => $record->donations() + ->whereCharged() + ->count() + ), + + Column::make('donations_amount') + ->heading(__('organization.labels.donations_amount')) + ->formatStateUsing( + fn (Organization $record) => $record->donations() + ->whereCharged() + ->sum('charge_amount') + ), + + ]) + ->queue(), + ]); + } catch (\Throwable $exception) { + logger()->error($exception->getMessage()); + Notification::make('export') + ->title(__('notification.export.error.title')) + ->body(__('notification.export.error.body')) + ->danger() + ->send(); + } } } diff --git a/app/Filament/Resources/ProjectResource.php b/app/Filament/Resources/ProjectResource.php index 9c1e1050..79210f67 100644 --- a/app/Filament/Resources/ProjectResource.php +++ b/app/Filament/Resources/ProjectResource.php @@ -46,6 +46,16 @@ protected static function getNavigationBadge(): ?string return (string) static::$model::count(); } + public static function getModelLabel(): string + { + return __('project.label.singular'); + } + + public static function getPluralModelLabel(): string + { + return __('project.label.plural'); + } + public static function form(Form $form): Form { return $form @@ -247,7 +257,7 @@ public static function getWidgetColumns(): array TextColumn::make('target_budget') ->label(__('project.labels.target_budget')) ->formatStateUsing( - fn (Project $record) => number_format($record->target_budget, 2, ',', '.') + fn (Project $record) => number_format($record->target_budget ?? 0, 2, ',', '.') ), TextColumn::make('status_updated_at') diff --git a/app/Filament/Resources/ProjectResource/Actions/ExportAction.php b/app/Filament/Resources/ProjectResource/Actions/ExportAction.php index 98490de7..d5866e13 100644 --- a/app/Filament/Resources/ProjectResource/Actions/ExportAction.php +++ b/app/Filament/Resources/ProjectResource/Actions/ExportAction.php @@ -6,12 +6,10 @@ use App\Filament\Exports\ExcelExportWithNotificationInDB; use App\Filament\Resources\ProjectResource; -use App\Models\Activity; use App\Models\County; -use App\Models\Donation; -use App\Models\Organization; use App\Models\Project; use App\Models\ProjectCategory; +use Filament\Notifications\Notification; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Str; use pxlrbt\FilamentExcel\Actions\Tables\ExportAction as BaseAction; @@ -27,128 +25,111 @@ protected function setUp(): void $this->color('secondary'); - $fileName = sprintf( - '%s-%s', - now()->format('Y_m_d-H_i_s'), - Str::slug(ProjectResource::getPluralModelLabel()), - ); - $name = $this->name; - $this->exports([ - ExcelExportWithNotificationInDB::make() - ->withFilename($fileName) - ->modifyQueryUsing( - function (Builder $query) use ($name) { - if ($name == 'approved_project') { - $query->whereIsApproved(); - } - - if ($name == 'new_project') { - $query->whereIsPending(); - } - - if ($name == 'pending_changes') { - $query->withCount([ - 'activities' => fn (Builder $query) => $query->wherePending(), - ]) - ->whereHas('activities', function (Builder $query) { - $query->wherePending(); - }) - ->addSelect([ - 'latest_updated_at' => Activity::query() - ->withoutGlobalScopes() - ->wherePending() - ->select('created_at') - ->whereColumn('subject_id', 'projects.id') - ->whereMorphedTo('subject', Organization::class) - ->latest() - ->take(1), - ]); - } - - if ($name == 'rejected_project') { - $query->whereIsRejected(); - } - - return $query; - } - ) - ->withColumns([ - Column::make('name') - ->heading(__('project.labels.name')), - - Column::make('status') - ->heading(__('project.labels.status')), - - Column::make('organization') - ->heading(__('project.labels.organization')) - ->formatStateUsing( - fn (Project $record, $state) => $state->name - ), - - Column::make('start') - ->heading(__('project.labels.start')) - ->formatStateUsing( - fn (Project $record, $state) => $state?->toFormattedDateTime() - ), - - Column::make('end') - ->heading(__('project.labels.end')) - ->formatStateUsing( - fn (Project $record, $state) => $state?->toFormattedDateTime() - ), - - Column::make('counties') - ->heading(__('project.labels.counties')) - ->formatStateUsing( - fn (Project $record) => $record->counties->map(fn (County $county) => $county->name) - ->join(', ') - ), - - Column::make('category') - ->heading(__('project.labels.category')) - ->formatStateUsing( - fn (Project $record, $state) => $record->categories->map(fn (ProjectCategory $category) => $category->name) - ->join(', ') - ), - - Column::make('description') - ->heading(__('project.labels.description')), - - Column::make('scope') - ->heading(__('project.labels.scope')), - - Column::make('target_budget') - ->heading(__('project.labels.target_budget')), - - Column::make('beneficiaries') - ->heading(__('project.labels.beneficiaries')), - - Column::make('reason_to_donate') - ->heading(__('project.labels.reason_to_donate')), - - Column::make('accepting_volunteers') - ->heading(__('project.labels.accepting_volunteers')) - ->formatStateUsing(fn (Project $record, $state) => $state ? __('field.boolean.true') : __('field.boolean.false')), - - Column::make('accepting_comments') - ->heading(__('project.labels.accepting_comments')) - ->formatStateUsing(fn (Project $record, $state) => $state ? __('field.boolean.true') : __('field.boolean.false')), - - Column::make('donation_number') - ->heading(__('donation.labels.count')) - ->formatStateUsing( - fn (Project $record) => $record->donations->count() - ), - - Column::make('donation_amount') - ->heading(__('donation.labels.amount')) - ->formatStateUsing( - fn (Project $record) => $record->donations - ->map(fn (Donation $donation) => $donation->amount) - ->sum() - ), - ]) - ->queue(), - ]); + try { + $this->exports([ + ExcelExportWithNotificationInDB::make() + ->withFilename(fn () => sprintf( + '%s-%s-%s', + now()->format('Y_m_d-H_i_s'), + Str::slug(ProjectResource::getPluralModelLabel()), + $this->name ?? '' + )) + ->fromTable() + ->modifyQueryUsing(fn (Builder $query): Builder => $query->addSelect([ + 'start', + 'end', + 'scope', + 'target_budget', + 'beneficiaries', + 'reason_to_donate', + 'accepting_volunteers', + 'description', + 'accepting_comments', + ])) + ->withColumns([ + Column::make('name') + ->heading(__('project.labels.name')), + + Column::make('status') + ->formatStateUsing(fn (Project $record): string => $record->status->label()) + ->heading(__('project.labels.status')), + + Column::make('organization') + ->heading(__('project.labels.organization')) + ->formatStateUsing( + fn (Project $record, $state) => $state->name + ), + + Column::make('start') + ->heading(__('project.labels.start')) + ->formatStateUsing( + fn (Project $record, $state) => $state?->toFormattedDateTime() + ), + + Column::make('end') + ->heading(__('project.labels.end')) + ->formatStateUsing( + fn (Project $record, $state) => $state?->toFormattedDateTime() + ), + + Column::make('counties') + ->heading(__('project.labels.counties')) + ->formatStateUsing( + fn (Project $record) => $record->counties->map(fn (County $county) => $county->name) + ->join(', ') + ), + + Column::make('category') + ->heading(__('project.labels.category')) + ->formatStateUsing( + fn (Project $record, $state) => $record->categories->map(fn (ProjectCategory $category) => $category->name) + ->join(', ') + ), + + Column::make('description') + ->heading(__('project.labels.description')), + + Column::make('scope') + ->heading(__('project.labels.scope')), + + Column::make('target_budget') + ->heading(__('project.labels.target_budget')), + + Column::make('beneficiaries') + ->heading(__('project.labels.beneficiaries')), + + Column::make('reason_to_donate') + ->heading(__('project.labels.reason_to_donate')), + + Column::make('accepting_volunteers') + ->heading(__('project.labels.accepting_volunteers')) + ->formatStateUsing(fn (Project $record, $state) => $state ? __('field.boolean.true') : __('field.boolean.false')), + + Column::make('accepting_comments') + ->heading(__('project.labels.accepting_comments')) + ->formatStateUsing(fn (Project $record, $state) => $state ? __('field.boolean.true') : __('field.boolean.false')), + + Column::make('donation_number') + ->heading(__('donation.labels.count')) + ->formatStateUsing( + fn (Project $record) => $record->loadMissing('donations')->donations->count() + ), + + Column::make('donation_amount') + ->heading(__('donation.labels.amount')) + ->formatStateUsing( + fn (Project $record) => $record->loadMissing('donations')->donations()->sum('amount'), + ), + ]) + ->queue(), + ]); + } catch (\Throwable $exception) { + logger()->error($exception->getMessage()); + Notification::make('export') + ->title(__('notification.export.error.title')) + ->body(__('notification.export.error.body')) + ->danger() + ->send(); + } } } diff --git a/app/Filament/Resources/ProjectResource/Widgets/ApprovedProject.php b/app/Filament/Resources/ProjectResource/Widgets/ApprovedProject.php index 4a183d8d..60cf573d 100644 --- a/app/Filament/Resources/ProjectResource/Widgets/ApprovedProject.php +++ b/app/Filament/Resources/ProjectResource/Widgets/ApprovedProject.php @@ -118,7 +118,7 @@ function (Project $record) { TextColumn::make('target_budget') ->formatStateUsing( - fn (Project $record) => number_format($record->target_budget, 2, ',', '.') + fn (Project $record) => number_format($record->target_budget ?? 0, 2, ',', '.') ) ->label(__('project.labels.target_budget')), diff --git a/app/Filament/Resources/ProjectResource/Widgets/BaseProjectWidget.php b/app/Filament/Resources/ProjectResource/Widgets/BaseProjectWidget.php index cda3d2d3..762eae45 100644 --- a/app/Filament/Resources/ProjectResource/Widgets/BaseProjectWidget.php +++ b/app/Filament/Resources/ProjectResource/Widgets/BaseProjectWidget.php @@ -6,6 +6,7 @@ use App\Enums\ProjectStatus; use App\Filament\Resources\ProjectResource; +use App\Filament\Resources\ProjectResource\Actions\ExportAction; use App\Models\Project; use Filament\Tables\Actions\Action; use Filament\Widgets\TableWidget as BaseWidget; @@ -114,7 +115,7 @@ protected function paginateTableQuery(Builder $query): Paginator protected function getTableHeaderActions(): array { return [ - ProjectResource\Actions\ExportAction::make($this->getTableQueryStringIdentifier()), + ExportAction::make($this->getTableQueryStringIdentifier()), ]; } } diff --git a/app/Filament/Resources/UserResource/Actions/ExportAction.php b/app/Filament/Resources/UserResource/Actions/ExportAction.php index 7f63a6e0..1ad6f928 100644 --- a/app/Filament/Resources/UserResource/Actions/ExportAction.php +++ b/app/Filament/Resources/UserResource/Actions/ExportAction.php @@ -8,6 +8,7 @@ use App\Filament\Exports\ExcelExportWithNotificationInDB; use App\Filament\Resources\UserResource; use App\Models\User; +use Filament\Notifications\Notification; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Str; use pxlrbt\FilamentExcel\Actions\Tables\ExportAction as BaseAction; @@ -23,82 +24,110 @@ protected function setUp(): void $this->color('secondary'); - $this->exports([ - ExcelExportWithNotificationInDB::make() - ->withFilename(fn () => sprintf( - '%s-%s', - now()->format('Y_m_d-H_i_s'), - Str::slug(UserResource::getPluralModelLabel()), - )) - ->modifyQueryUsing(function (Builder $query) { - return $query - ->with([ - 'donations' => fn ($q) => $q->select(['user_id', 'amount', 'status', 'created_at']), - ]) - ->withCount(['donations']); - }) - ->withColumns([ - Column::make('id') - ->heading('ID'), - - Column::make('name') - ->heading(__('user.labels.name')), - - Column::make('email') - ->heading(__('user.labels.email')), - - Column::make('role') - ->heading(__('user.labels.role'))->formatStateUsing( - fn ($state) => $state->label() - ), - - Column::make('status') - ->heading(__('user.labels.status')) - ->formatStateUsing( - fn (User $record) => $record->email_verified_at ? - __('user.labels.status_display.verified') : - __('user.labels.status_display.unverified') - ), - - Column::make('created_at') - ->heading(__('user.labels.created_at')) - ->formatStateUsing( - fn (User $record) => $record->created_at->toFormattedDateTime() - ), - - Column::make('newsletter_subscription') - ->heading(__('user.labels.newsletter_subscription')) - ->formatStateUsing( - fn (User $record) => $record->newsletter ? + try { + $this->exports([ + ExcelExportWithNotificationInDB::make() + ->withFilename(fn () => sprintf( + '%s-%s', + now()->format('Y_m_d-H_i_s'), + Str::slug(UserResource::getPluralModelLabel()), + )) + ->fromTable() + ->modifyQueryUsing(function (Builder $query) { + return $query + ->addSelect([ + 'referrer', + 'id', + 'role', + 'name', + 'email', + 'email_verified_at', + 'created_at', + 'newsletter', + 'organization_id', + 'created_by', + ]) + ->with([ + 'donations' => fn ($q) => $q->select(['user_id', 'amount', 'status', 'created_at']), + ]) + ->withCount(['donations']); + }) + ->withColumns([ + Column::make('id') + ->heading('ID'), + + Column::make('name') + ->heading(__('user.labels.name')), + + Column::make('email') + ->heading(__('user.labels.email')), + + Column::make('role') + ->heading(__('user.labels.role'))->formatStateUsing( + fn (User $record) => $record->role->label() + ), + + Column::make('status') + ->heading(__('user.labels.status')) + ->formatStateUsing( + fn (User $record) => $record->email_verified_at ? + __('user.labels.status_display.verified') : + __('user.labels.status_display.unverified') + ), + + Column::make('created_at') + ->heading(__('user.labels.created_at')) + ->formatStateUsing( + fn (User $record) => $record->created_at->toFormattedDateTime() + ), + + Column::make('newsletter_subscription') + ->heading(__('user.labels.newsletter_subscription')) + ->formatStateUsing( + fn (User $record) => $record->newsletter ? $record->created_at->toFormattedDateTime() : '' - ), - - Column::make('referrer') - ->heading(__('user.labels.referrer')), - - Column::make('donations') - ->heading(__('user.labels.donations_sum')) - ->formatStateUsing( - fn (User $record) => $record->donations - ->reject(fn ($item) => $item->status === EuPlatescStatus::CHARGED) - ->sum('amount') - ), - - Column::make('donations_count') - ->heading(__('user.labels.donations_count')), - - Column::make('last_donation_date') - ->heading(__('user.labels.last_donation_date')) - ->formatStateUsing( - fn (User $record) => $record->donations_count ? + ), + + Column::make('referrer') + ->heading(__('user.labels.referrer')), + + Column::make('donations_count') + ->formatStateUsing(fn (User $record) => $record->donations()->whereCharged()->count() ?? 0) + ->heading(__('user.labels.donations_count')), + + Column::make('donations') + ->heading(__('user.labels.donations_sum')) + ->formatStateUsing( + fn (User $record) => $record->donations + ->reject(fn ($item) => $item->status === EuPlatescStatus::CHARGED) + ->sum('amount') + ), + + Column::make('comments_count') + //TODO:: implement comments module and add this column + ->formatStateUsing(fn (User $record) => 'numarul de comentarii va aparea dupa implementarea modulului') + ->heading(__('user.labels.comments_count')), + + Column::make('last_donation_date') + ->heading(__('user.labels.last_donation_date')) + ->formatStateUsing( + fn (User $record) => $record->donations_count ? $record->donations ->reject(fn ($item) => $item->status === EuPlatescStatus::CHARGED) ->last()?->created_at->toFormattedDateTime() : '' - ), - - ]) - ->queue(), - ]); + ), + + ]) + ->queue(), + ]); + } catch (\Throwable $exception) { + logger()->error($exception->getMessage()); + Notification::make('export') + ->title(__('notification.export.error.title')) + ->body(__('notification.export.error.body')) + ->danger() + ->send(); + } } } diff --git a/app/Filament/Widgets/DonationAmountOverview.php b/app/Filament/Widgets/DonationAmountOverview.php index fe2e048b..9b390254 100644 --- a/app/Filament/Widgets/DonationAmountOverview.php +++ b/app/Filament/Widgets/DonationAmountOverview.php @@ -6,6 +6,9 @@ class DonationAmountOverview extends BaseDonationWidget { + protected int | string | array $columnSpan = 2; + + protected function getData(): array { $selectedFields = 'sum(amount) as total'; diff --git a/app/Filament/Widgets/DonationCountOverview.php b/app/Filament/Widgets/DonationCountOverview.php index 59c83aa9..06a9a40d 100644 --- a/app/Filament/Widgets/DonationCountOverview.php +++ b/app/Filament/Widgets/DonationCountOverview.php @@ -6,6 +6,8 @@ class DonationCountOverview extends BaseDonationWidget { + protected int | string | array $columnSpan = 2; + protected function getData(): array { $selectedField = 'count(id) as count'; diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 5bc301a6..09be9ca9 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -9,6 +9,7 @@ use App\Http\Filters\CountiesFilter; use App\Http\Filters\ProjectCategoriesFilter; use App\Http\Filters\ProjectDatesFilter; +use App\Http\Filters\ProjectNationalFilter; use App\Http\Filters\ProjectStatusFilter; use App\Http\Filters\SearchFilter; use App\Http\Requests\Project\DonateRequest; @@ -51,6 +52,7 @@ protected function projectList(Request $request, string $view): Response AllowedFilter::custom('status', new ProjectStatusFilter), AllowedFilter::custom('volunteers', new AcceptsVolunteersFilter), AllowedFilter::custom('search', new SearchFilter), + AllowedFilter::custom('is_national', new ProjectNationalFilter), ]) ->allowedSorts([ AllowedSort::field('publish_date', 'start'), diff --git a/app/Http/Filters/ProjectNationalFilter.php b/app/Http/Filters/ProjectNationalFilter.php new file mode 100644 index 00000000..6d6798e7 --- /dev/null +++ b/app/Http/Filters/ProjectNationalFilter.php @@ -0,0 +1,24 @@ +where('is_national', true); + } + + if (! $value) { + return $query->where('is_national', false); + } + + return $query; + } +} diff --git a/app/Http/Livewire/Welcome.php b/app/Http/Livewire/Welcome.php index cb0f1fa4..4cbe99bd 100644 --- a/app/Http/Livewire/Welcome.php +++ b/app/Http/Livewire/Welcome.php @@ -41,7 +41,7 @@ public function mount($user, Request $request): void abort(Response::HTTP_FORBIDDEN, __('auth.welcome.invalid_signature')); } - $this->user = User::find($user)->first(); + $this->user = User::where('id', $user->id)->first(); if (\is_null($this->user)) { abort(Response::HTTP_FORBIDDEN, __('auth.welcome.no_user')); diff --git a/app/Jobs/CaptureAuthorizedDonationJob.php b/app/Jobs/CaptureAuthorizedDonationJob.php index 01871d01..ae59b2f0 100644 --- a/app/Jobs/CaptureAuthorizedDonationJob.php +++ b/app/Jobs/CaptureAuthorizedDonationJob.php @@ -52,21 +52,25 @@ public function __construct(Donation $donation, EuPlatescService $service) public function handle(): void { if ($this->service->recipeTransaction($this->donation)) { + \Log::info('Donation ' . $this->donation->id . ' was charged'); $this->donation->update([ 'status' => EuPlatescStatus::CHARGED, 'status_updated_at' => now(), ]); + \Log::info('Donation ' . $this->donation->id . 'was updated in DB'); $userInstanceOfDonner = new User([ 'email' => $this->donation->email, 'name' => $this->donation->first_name . ' ' . $this->donation->last_name, ]); \Notification::send($userInstanceOfDonner, new UserDonationReceived($this->donation)); + \Log::info('Notification was sent to ' . $this->donation->email); $organizationsUsers = $this->donation->load('organization') ->organization->load('users')->users->filter(function ($user) { return $user->hasVerifiedEmail(); }); \Notification::send($organizationsUsers, new DonationReceived()); + \Log::info('Notification was sent to ' . $organizationsUsers->pluck('email')->implode(', ')); } } } diff --git a/app/Jobs/ProcessAuthorizedTransactionsJob.php b/app/Jobs/ProcessAuthorizedTransactionsJob.php index 6a8ca065..c027fd69 100644 --- a/app/Jobs/ProcessAuthorizedTransactionsJob.php +++ b/app/Jobs/ProcessAuthorizedTransactionsJob.php @@ -26,6 +26,8 @@ class ProcessAuthorizedTransactionsJob implements ShouldQueue */ public function handle(): void { + \Log::info('Processing authorized transactions started at ' . now()->toString()); + $counter = 0; Organization::query() ->whereHasEuPlatesc() ->withWhereHas('donations', function (Builder $query) { @@ -34,11 +36,13 @@ public function handle(): void ->whereNotNull('ep_id'); }) ->get() - ->each(function (Organization $organization) { + ->each(function (Organization $organization) use (&$counter) { $service = new EuPlatescService($organization); $organization->donations ->each(fn (Donation $donation) => CaptureAuthorizedDonationJob::dispatch($donation, $service)); + $counter++; }); + \Log::info('Processing authorized transactions ended at ' . now()->toString() . ' with ' . $counter . ' organization processed.'); } } diff --git a/app/Models/Donation.php b/app/Models/Donation.php index 06cddbea..a4c39db2 100644 --- a/app/Models/Donation.php +++ b/app/Models/Donation.php @@ -67,7 +67,7 @@ public function scopeWhereAuthorized(Builder $query): Builder public function scopeWhereCharged(Builder $query): Builder { - return $query->where('status', EuPlatescStatus::CHARGED); + return $query->where('donations.status', EuPlatescStatus::CHARGED); } public function scopeWherePending(Builder $query): Builder diff --git a/app/Models/Organization.php b/app/Models/Organization.php index 686104d7..cf310613 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -244,6 +244,28 @@ public function getHasStatuteAttribute(): bool return $this->getMedia('statute')->isNotEmpty(); } + public function getStatuteFileAttribute(): ?string + { + if ($this->getMedia('statute')->isNotEmpty()) { + $file = $this->getFirstMedia('statute'); + + try { + return $file->getTemporaryUrl(now()->addDay()); + } catch (\RuntimeException $exception) { + return $file->getUrl(); + } catch (\Throwable $exception) { + return $exception->getMessage(); + } + } + + return null; + } + + public function getAcceptDonationsAttribute(): bool + { + return ! empty($this->eu_platesc_merchant_id) && ! empty($this->eu_platesc_private_key); + } + public function badges(): BelongsToMany { return $this->belongsToMany(Badge::class) diff --git a/app/Notifications/Admin/ExportExcelNotification.php b/app/Notifications/Admin/ExportExcelNotification.php index 0a3467c3..dec2bc21 100644 --- a/app/Notifications/Admin/ExportExcelNotification.php +++ b/app/Notifications/Admin/ExportExcelNotification.php @@ -10,6 +10,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\URL; class ExportExcelNotification extends Notification @@ -45,10 +46,10 @@ public function via(object $notifiable): array public function toMail(object $notifiable): MailMessage { return (new MailMessage) - ->line('The introduction to the notification.') - ->attach(storage_path('app/filament-excel/') . $this->filename) -// ->action('Notification Action', $this->generateURL()) - ->line('Thank you for using our application!'); + ->subject(__('notification.export_finished.title')) + ->line(__('notification.export_finished.body', ['filename' => $this->filename])) + ->attach(Storage::disk('filament-excel')->path($this->filename)); +// ->action(__('notification.export_finished.action'), Storage::disk('filament-excel')->url($this->filename)); } /** @@ -66,17 +67,17 @@ public function toArray(object $notifiable): array public function toDatabase(): array { return FilamentNotification::make() - ->title(__('filament-excel::notifications.queued.title')) - ->body(__('filament-excel::notifications.queued.body')) + ->title(__('notification.export_finished.title')) + ->body(__('notification.export_finished.notification')) ->success() ->seconds(5) ->icon('heroicon-o-inbox-in') ->actions([ - Action::make('export_excel') - ->label(__('filament-excel::notifications.download_ready.download')) - ->url($this->generateURL(), shouldOpenInNewTab: true) - ->button() - ->close(), + // Action::make('export_excel') + // ->label(__('filament-excel::notifications.download_ready.download')) + // ->url($this->generateURL(), shouldOpenInNewTab: true) + // ->button() + // ->close(), ]) ->getDatabaseMessage(); } diff --git a/app/Traits/HasProjectStatus.php b/app/Traits/HasProjectStatus.php index e58ed4fb..670be72e 100644 --- a/app/Traits/HasProjectStatus.php +++ b/app/Traits/HasProjectStatus.php @@ -41,7 +41,7 @@ public function isPublished(): bool public function isOpen(): bool { - return $this->isPublished() && $this->start->isPast() && $this->end->isFuture(); + return $this->isPublished() && $this->start?->isPast() && $this->end?->isFuture(); } public function isArchived(): bool @@ -52,13 +52,13 @@ public function isArchived(): bool public function isStartingSoon(): bool { return $this->isPublished() - && ! $this->end->isPast() - && ($this->start->isFuture() || ! $this->organization->EuPlatescIsActive()); + && ! $this->end?->isPast() + && ($this->start?->isFuture() || ! $this->organization->EuPlatescIsActive()); } public function isClose(): bool { - return $this->isPublished() && $this->end->isPast(); + return $this->isPublished() && $this->end?->isPast(); } public function scopeWhereIsPending(Builder $query): Builder diff --git a/config/filament.php b/config/filament.php index e75c6802..c42d858a 100644 --- a/config/filament.php +++ b/config/filament.php @@ -185,7 +185,7 @@ 'database_notifications' => [ 'enabled' => true, - 'polling_interval' => '30s', + 'polling_interval' => '10s', ], /* @@ -291,6 +291,8 @@ 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DRIVER', 'public'), + 'filament-excel-disk' => env('FILAMENT_EXCEL_DISK', 'filament-excel'), + /* |-------------------------------------------------------------------------- | Google Fonts diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index d41cafcf..102f5536 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -6,6 +6,7 @@ use App\Enums\UserRole; use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; /** @@ -24,7 +25,7 @@ public function definition(): array 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'password' => Hash::make('password'), // password 'remember_token' => Str::random(10), 'newsletter' => fake()->boolean(), ]; diff --git a/lang/ro/donation.php b/lang/ro/donation.php index 20187de8..641b1f1f 100644 --- a/lang/ro/donation.php +++ b/lang/ro/donation.php @@ -46,6 +46,7 @@ 'payment_declined' => 'Plata refuzată', 'possible_fraud' => 'Posibil fraudulos', 'charged' => 'Încasată', + 'capture' => 'Capturată', ], 'header' => 'Donatii (:number)', diff --git a/lang/ro/notification.php b/lang/ro/notification.php new file mode 100644 index 00000000..0a644e43 --- /dev/null +++ b/lang/ro/notification.php @@ -0,0 +1,24 @@ + [ + 'success' => [ + 'title' => 'Exportul a fost adaugat în coadă', + 'body' => ' Procesul de export a fost adaugat in coada de procesare. Te vom anunta cand va fi gata.', + ], + 'error' => [ + 'title' => 'Exportul nu a putut fi adaugat in coada.', + 'body' => ' Procesul de export nu a putut fi adaugat in coada de procesare. Te rugam sa incerci din nou mai tarziu.', + ], + ], + + 'export_finished' => [ + 'title' => 'Exportul a fost finalizat', + 'body' => ' Procesul de export pentru :filename a fost finalizat', + 'notification' => 'Fisierul a fost trimis la adresa de email asociata contului tau.', + 'action' => 'Descarcă', + ], +]; diff --git a/lang/ro/organization.php b/lang/ro/organization.php index f2b08dff..9dd3beee 100644 --- a/lang/ro/organization.php +++ b/lang/ro/organization.php @@ -48,6 +48,7 @@ 'general_data' => 'Date organizație', 'name' => 'Denumire organizație', 'cif' => 'CIF/CUI', + 'accepts_donations' => 'Organizația acceptă donații?', 'logo' => 'Logo-ul organizației', 'description' => 'Descriere organizație', 'activity_domains' => 'Domenii de activitate', @@ -84,6 +85,7 @@ 'pending' => 'Organizații în așteptare', 'status' => 'Status', 'admin' => 'Administrator', + 'admin_count' => 'Număr administratori', 'projects_count' => 'Număr de proiecte', 'active_projects_count' => 'Număr proiecte active', 'donations_count' => 'Număr total donații', diff --git a/resources/js/Components/Head.vue b/resources/js/Components/Head.vue index a364b238..aded7cd3 100644 --- a/resources/js/Components/Head.vue +++ b/resources/js/Components/Head.vue @@ -7,8 +7,8 @@ @@ -19,22 +19,23 @@ - diff --git a/resources/js/Components/cards/ArticleCard.vue b/resources/js/Components/cards/ArticleCard.vue index 3480380a..9390f19b 100644 --- a/resources/js/Components/cards/ArticleCard.vue +++ b/resources/js/Components/cards/ArticleCard.vue @@ -17,7 +17,7 @@

-
+