mapWithKeys(fn (CouponType $type) => [$type->value => $type->label()]) ->all(); $statusOptions = collect(CouponStatus::cases()) ->mapWithKeys(fn (CouponStatus $status) => [$status->value => $status->label()]) ->all(); return $schema ->columns(1) ->components([ Section::make(__('Coupon details')) ->columns(3) ->schema([ TextInput::make('name') ->label(__('Name')) ->maxLength(191) ->required(), TextInput::make('code') ->label(__('Code')) ->maxLength(32) ->required() ->helperText(__('Codes are stored uppercase and must be unique.')) ->unique(ignoreRecord: true, modifyRuleUsing: fn (Unique $rule) => $rule->withoutTrashed()) ->rule('alpha_dash:ascii'), Select::make('status') ->label(__('Status')) ->options($statusOptions) ->default(CouponStatus::DRAFT) ->required(), Select::make('type') ->label(__('Discount Type')) ->options($typeOptions) ->default(CouponType::PERCENTAGE) ->required(), TextInput::make('amount') ->label(__('Amount')) ->numeric() ->minValue(0) ->step(0.01) ->required(), TextInput::make('currency') ->label(__('Currency')) ->maxLength(3) ->default('EUR') ->helperText(__('Required for flat discounts. Hidden for percentage codes.')) ->disabled(fn (Get $get) => $get('type') === CouponType::PERCENTAGE->value) ->nullable(), Textarea::make('description') ->label(__('Description')) ->rows(3) ->columnSpanFull(), Toggle::make('enabled_for_checkout') ->label(__('Can be redeemed at checkout')) ->default(true), Toggle::make('auto_apply') ->label(__('Auto apply when query parameter matches')) ->helperText(__('Automatically applies when marketing links include ?coupon=CODE.')), Toggle::make('is_stackable') ->label(__('Allow stacking with other coupons')) ->default(false), ]), Section::make(__('Limits & Scheduling')) ->columns(2) ->schema([ DateTimePicker::make('starts_at') ->label(__('Starts at')) ->seconds(false) ->nullable(), DateTimePicker::make('ends_at') ->label(__('Ends at')) ->seconds(false) ->nullable(), TextInput::make('usage_limit') ->label(__('Global usage limit')) ->numeric() ->minValue(1) ->nullable(), TextInput::make('per_customer_limit') ->label(__('Per tenant limit')) ->numeric() ->minValue(1) ->nullable(), ]), Section::make(__('Package restrictions')) ->schema([ Select::make('packages') ->label(__('Applicable packages')) ->relationship('packages', 'name') ->multiple() ->preload() ->searchable() ->helperText(__('Leave empty to allow all packages.')), ]), Section::make(__('Metadata')) ->schema([ KeyValue::make('metadata') ->label(__('Internal metadata')) ->keyLabel(__('Key')) ->valueLabel(__('Value')) ->nullable() ->columnSpanFull(), ]), Section::make(__('Paddle sync')) ->columns(2) ->schema([ Select::make('paddle_mode') ->label(__('Paddle mode')) ->options([ 'standard' => __('Standard'), 'custom' => __('Custom (one-off)'), ]) ->default('standard'), Placeholder::make('paddle_discount_id') ->label(__('Paddle Discount ID')) ->content(fn ($record) => $record?->paddle_discount_id ?? '—'), Placeholder::make('paddle_last_synced_at') ->label(__('Last synced')) ->content(fn ($record) => $record?->paddle_last_synced_at?->diffForHumans() ?? '—'), Placeholder::make('redemptions_count') ->label(__('Total redemptions')) ->content(fn ($record) => number_format($record?->redemptions_count ?? 0)), ]), ]); } }