components([ Grid::make() ->schema([ /* |-------------------------------------------------------------------------- | Colonne principale |-------------------------------------------------------------------------- */ Grid::make(1) ->schema([ Tabs::make('MemberTabs') ->tabs([ /* |-------------------------------------------------------------------------- | TAB : Informations générales |-------------------------------------------------------------------------- */ Tabs\Tab::make(__('members.tabs.general_info')) ->icon(Heroicon::OutlinedInformationCircle) ->schema([ Section::make(__('members.sections.personal_info')) ->collapsible() ->schema([ TextInput::make('lastname') ->label(Member::getAttributeLabel('lastname')) ->required(), TextInput::make('firstname') ->label(Member::getAttributeLabel('firstname')) ->required(), DatePicker::make('date_of_birth') ->label(Member::getAttributeLabel('date_of_birth')), TextInput::make('company') ->label(Member::getAttributeLabel('company')), ]) ->columns(2), Section::make(__('members.sections.administrative_info')) ->collapsible() ->schema([ TextInput::make('keycloak_id') ->label(Member::getAttributeLabel('keycloak_id')), Select::make('nature') ->label(Member::getAttributeLabel('nature')) ->options([ 'physical' => Member::getAttributeLabel('physical'), 'legal' => Member::getAttributeLabel('legal'), ]) ->default('physical') ->required(), Select::make('group_id') ->label(Member::getAttributeLabel('group_id')) ->relationship('group', 'name') ->default(null), ]) ->columns(2), Section::make(__('members.sections.contact_info')) ->collapsible() ->schema([ TextInput::make('email') ->label(Member::getAttributeLabel('email')) ->email() ->required(), TextInput::make('phone1') ->label(Member::getAttributeLabel('phone1')) ->tel(), TextInput::make('phone2') ->label(Member::getAttributeLabel('phone2')) ->tel(), TextInput::make('address') ->label(Member::getAttributeLabel('address')), TextInput::make('zipcode') ->label(Member::getAttributeLabel('zipcode')), TextInput::make('city') ->label(Member::getAttributeLabel('city')), TextInput::make('country') ->label(Member::getAttributeLabel('country')), ]) ->columns(2), ]), /* |-------------------------------------------------------------------------- | TAB : Services/Modules |-------------------------------------------------------------------------- */ Tabs\Tab::make(__('members.tabs.modules')) ->icon(Heroicon::OutlinedPuzzlePiece) ->schema([ Section::make(__('members.sections.ispconfig_mail')) ->afterHeader([ ServiceToggleAction::forService('mail'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfig_mails') ->label(__('members.ispconfig.mail_data')) ->state(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::MAIL) ->get() ) ->schema([ TextEntry::make('email') ->label(__('members.ispconfig.email')), TextEntry::make('ispconfig_service_user_id') ->label(__('members.ispconfig.id')), TextEntry::make('data.mailuser.quota') ->label(__('members.ispconfig.quota')), TextEntry::make('data.mailuser.domain') ->label(__('members.ispconfig.domain')) ->default('retzien.fr'), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(2), ]), Section::make(__('members.sections.ispconfig_web')) ->afterHeader([ ServiceToggleAction::forService('webhosting'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfigs_web') ->label(__('members.ispconfig.web_data')) ->state(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::WEB) ->get() ->map(fn ($ispconfig) => $ispconfig->toArray()) ->all() ) ->schema([ TextEntry::make('data.domain_id') ->label(__('members.ispconfig.id')), TextEntry::make('data.domain') ->label(__('members.ispconfig.domain')), TextEntry::make('data.active') ->label(__('members.ispconfig.state')) ->formatStateUsing(fn ($state) => $state === 'y' ? __('members.ispconfig.enabled') : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(3), ]), Section::make(__('members.sections.nextcloud')) ->afterHeader([ ServiceToggleAction::forService('nextcloud'), ]) ->collapsible() ->schema([ RepeatableEntry::make('nextcloud_accounts') ->label(__('members.ispconfig.nextcloud_data')) ->state(fn (?Member $record) => $record?->nextcloudAccounts() ->get() ->map(fn ($nextcloudAccount) => $nextcloudAccount->toArray()) ->all() ) ->schema([ TextEntry::make('nextcloud_user_id') ->label(__('members.ispconfig.nextcloud_id')), TextEntry::make('data.displayname') ->label(__('members.ispconfig.display_name')), TextEntry::make('data.enabled') ->label(__('members.ispconfig.state')) ->formatStateUsing(fn ($state) => $state == 'true' ? __('members.ispconfig.enabled') : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(3), ]), Section::make(__('members.sections.listmonk')) ->afterHeader([ ServiceToggleAction::forService('listmonk'), ]) ->collapsible() ->schema([ RepeatableEntry::make('listmonk_accounts') ->label(__('members.ispconfig.listmonk_data')) ->state(fn (?Member $record) => $record?->listmonkMembers() ->get() ->map(fn (ListmonkMember $lm) => $lm->toArray()) ->all() ) ->schema([ TextEntry::make('listmonk_user_id') ->label(__('members.ispconfig.listmonk_id')), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(2), ]), ]), ]) ->contained(false), ]) ->columnSpan(3), /* |-------------------------------------------------------------------------- | Colonne latérale |-------------------------------------------------------------------------- */ Grid::make(1) ->schema([ Section::make(__('members.sections.status')) ->collapsible() ->schema([ Select::make('status') ->label(Member::getAttributeLabel('status')) ->options([ 'draft' => Member::getAttributeLabel('draft'), 'valid' => Member::getAttributeLabel('valid'), 'pending' => Member::getAttributeLabel('pending'), 'cancelled' => Member::getAttributeLabel('cancelled'), 'excluded' => Member::getAttributeLabel('excluded'), ]) ->default('draft') ->required(), Toggle::make('public_membership') ->label(Member::getAttributeLabel('public_membership')) ->required(), ]) ->extraAttributes(['class' => 'sticky top-4 h-fit']), Section::make(__('members.sections.actions')) ->collapsible() ->schema([ Action::make('create-membership') ->label(__('members.actions.create_membership')) ->icon('heroicon-o-plus-circle') ->color('primary') ->modalHeading(__('members.actions.create_membership')) ->modalSubmitActionLabel(__('members.actions.create_membership_submit')) ->form([ Select::make('package_id') ->label(Membership::getAttributeLabel('package_id')) ->options(fn () => Package::all()->pluck('name', 'id')) ->searchable() ->required(), Select::make('status') ->label(Membership::getAttributeLabel('status')) ->options([ 'pending' => Membership::getAttributeLabel('pending'), 'active' => Membership::getAttributeLabel('active'), ]) ->default('pending') ->required(), Select::make('payment_status') ->label(Membership::getAttributeLabel('payment_status')) ->options([ 'paid' => Membership::getAttributeLabel('paid'), 'unpaid' => Membership::getAttributeLabel('unpaid'), 'partial' => Membership::getAttributeLabel('partial'), ]) ->default('unpaid') ->required(), TextInput::make('amount') ->label(Membership::getAttributeLabel('amount')) ->numeric() ->default(0) ->required(), DatePicker::make('start_date') ->label(Membership::getAttributeLabel('start_date')) ->default(now()), DatePicker::make('end_date') ->label(Membership::getAttributeLabel('end_date')), ]) ->action(function (array $data, Member $record) { $membership = $record->memberships()->create([ 'admin_id' => auth()->id(), ...$data, ]); return redirect(MembershipResource::getUrl('edit', ['record' => $membership->id])); }), Action::make('send-payment-mail') ->label(__('members.actions.send_payment_mail')) ->icon('heroicon-o-envelope') ->color('primary') ->action(function () { // Mail de paiement pour nouvelle inscription (Job) }), Action::make('send-renewal-mail') ->label(__('members.actions.send_renewal_mail')) ->icon('heroicon-o-envelope') ->color('primary') ->action(function () { // Mail de relance à créer (Job) }), ]) ->extraAttributes(['class' => 'sticky top-4 h-fit']), ]) ->columnSpan(1), ]) ->columns(4) ->columnSpanFull(), ]); } }