This commit is contained in:
f4ilji
2025-03-11 15:52:42 +05:00
parent 3385dc5807
commit 2fa59350cf
14 changed files with 279 additions and 81 deletions
+4 -7
View File
@@ -9,9 +9,9 @@ class MainSliderDTO
public function __construct( public function __construct(
public ?string $title, public ?string $title,
public ?string $content, public ?string $content,
public ?string $image, public ?array $image,
public ?string $link, public ?string $link,
public ?string $link_text, public ?array $settings,
public ?string $color_theme, public ?string $color_theme,
public ?bool $is_active, public ?bool $is_active,
public ?Carbon $start_time, public ?Carbon $start_time,
@@ -27,7 +27,7 @@ class MainSliderDTO
content: $data['content'] ?? null, content: $data['content'] ?? null,
image: $data['image'] ?? null, image: $data['image'] ?? null,
link: $data['link'] ?? null, link: $data['link'] ?? null,
link_text: $data['link_text'] ?? null, settings: $data['settings'] ?? null,
color_theme: $data['color_theme'] ?? 'white', color_theme: $data['color_theme'] ?? 'white',
is_active: $data['is_active'] ?? true, is_active: $data['is_active'] ?? true,
start_time: isset($data['start_time']) ? Carbon::parse($data['start_time']) : null, start_time: isset($data['start_time']) ? Carbon::parse($data['start_time']) : null,
@@ -40,19 +40,16 @@ class MainSliderDTO
public function toArray(): array public function toArray(): array
{ {
return [ return [
'id' => $this->id,
'title' => $this->title, 'title' => $this->title,
'content' => $this->content, 'content' => $this->content,
'image' => $this->image, 'image' => $this->image,
'link' => $this->link, 'link' => $this->link,
'link_text' => $this->link_text, 'settings' => $this->settings,
'color_theme' => $this->color_theme, 'color_theme' => $this->color_theme,
'is_active' => $this->is_active, 'is_active' => $this->is_active,
'start_time' => $this->start_time?->toDateTimeString(), 'start_time' => $this->start_time?->toDateTimeString(),
'end_time' => $this->end_time?->toDateTimeString(), 'end_time' => $this->end_time?->toDateTimeString(),
'sort' => $this->sort, 'sort' => $this->sort,
'created_at' => $this->created_at?->toDateTimeString(),
'updated_at' => $this->updated_at?->toDateTimeString(),
]; ];
} }
} }
+65 -34
View File
@@ -25,6 +25,7 @@ use Filament\Forms\Components\SpatieTagsInput;
use Filament\Forms\Components\Tabs; use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Resources\Components\Tab; use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
@@ -124,46 +125,76 @@ class PostForm
Toggle::make('is_slider_enabled') Toggle::make('is_slider_enabled')
->label('Добавить новый слайд') ->label('Добавить новый слайд')
->live() ->live()
->hidden(fn (string $context): bool => $context === 'edit')
->dehydrated(false) ->dehydrated(false)
->default(false), ->default(false),
Section::make() Section::make()
->schema([ ->schema([
Forms\Components\TextInput::make('slide.title') Forms\Components\Section::make('Информация слайда')->schema([
->label('Заголовок слайда'), Forms\Components\TextInput::make('slide.title')
Forms\Components\Textarea::make('slide.content') ->label('Заголовок слайда'),
->label('Текст слайда'), Forms\Components\Textarea::make('slide.content')
FileUpload::make('slide.image') ->label('Текст слайда'),
->label('Изображение') Forms\Components\Grid::make()->schema([
->image() ColorPicker::make('slide.color_theme')
->optimize('webp') ->label('Цвет текста')
->resize(50) ->default('#ffffff')
->disk('public') ->required(),
->directory('images') Forms\Components\ToggleButtons::make('slide.settings.text_position')
->imageEditor() ->options([
->required(), 'left' => 'Текст слева',
Grid::make(2)->schema([ 'center' => 'Текст по середине',
Toggle::make('disable_link_text') 'right' => 'Текст справа'
->label('Отключить текст кнопки (ссылка будет открываться при нажатии на слайд)') ])
->live() ->inline()->default('left')->grouped()
->inline(false) ->label('Позиция текста на слайде'),
->dehydrated(false) ]),
->default(false), Forms\Components\Grid::make()->schema([
Forms\Components\TextInput::make('slide.link_text') Toggle::make('active_button')
->default('Читать') ->label('Использовать кнопку для ссылки (Ссылка будет открываться при нажатии на слайд)')
->label('Текст кнопки') ->inline(false)
->disabled(fn (Forms\Get $get) => $get('disable_link_text')), ->live()
->afterStateHydrated(function (Toggle $component, $state, $get) {
$component->state(true);
})
->dehydrated(false),
Forms\Components\TextInput::make('slide.settings.link_text')
->default('Читать')
->label('Текст кнопки')
->disabled(fn (Forms\Get $get) => !$get('active_button'))
]),
]),
Forms\Components\Section::make('Изображение')->schema([
FileUpload::make('slide.image.url')
->label('Изображение')
->image()
->optimize('webp')
->resize(50)
->disk('public')
->directory('images')
->imageEditor()
->required(),
ToggleButtons::make('slide.image.shading')->inline()->grouped()->label('Уровень затемнения изображения')->options([
'1' => 'Без затемнения',
'0.7' => 'Слабое затемнение',
'0.5' => 'Среднее затемнение',
'0.3' => 'Сильное затемнение',
]),
]),
Forms\Components\Section::make('Общая часть')->schema([
Forms\Components\Grid::make()->schema([
DateTimePicker::make('slide.end_time')
->label('Слайд действует до')
->native()
->displayFormat('d/m/Y')
->minDate(Carbon::now())
->maxDate(Carbon::now()->addMonth()),
]),
]), ]),
DateTimePicker::make('slide.end_time')
->label('Слайд действует до')
->native()
->displayFormat('d/m/Y')
->minDate(Carbon::now())
->maxDate(Carbon::now()->addWeek()),
]) ])
->disabled(fn (Forms\Get $get) => !$get('is_slider_enabled')) // Отключаем секцию, если Toggle выключен
->hidden(fn (Forms\Get $get) => !$get('is_slider_enabled')), // Скрываем секцию, если Toggle выключен
])->hidden(fn (string $context): bool => $context === 'edit'), ]),
]), ]),
]) ])
]); ]);
@@ -124,8 +124,13 @@ class MainSliderResource extends Resource
Toggle::make('active_button') Toggle::make('active_button')
->label('Использовать кнопку для ссылки (Ссылка будет открываться при нажатии на слайд)') ->label('Использовать кнопку для ссылки (Ссылка будет открываться при нажатии на слайд)')
->inline(false) ->inline(false)
->default(true) ->default(true) // Проверяем, есть ли текст в link_text
->live() ->live()
->afterStateHydrated(function (Toggle $component, $state, $get) {
if ($state === null && !empty($get('settings.link_text'))) {
$component->state(true); // Устанавливаем значение по умолчанию
}
})
->dehydrated(false), ->dehydrated(false),
Forms\Components\TextInput::make('settings.link_text') Forms\Components\TextInput::make('settings.link_text')
->default('Читать') ->default('Читать')
@@ -156,12 +161,13 @@ class MainSliderResource extends Resource
->label('Слайд начинается с') ->label('Слайд начинается с')
->native() ->native()
->displayFormat('d/m/Y') ->displayFormat('d/m/Y')
->minDate(Carbon::now()->subDay()) ->default(Carbon::now())
->maxDate(Carbon::now()->addWeek()), ->maxDate(Carbon::now()->addWeeks(2)),
DateTimePicker::make('end_time') DateTimePicker::make('end_time')
->label('Слайд действует до') ->label('Слайд действует до')
->native() ->native()
->displayFormat('d/m/Y') ->displayFormat('d/m/Y')
->default(Carbon::now()->addWeeks(2))
->minDate(Carbon::now()) ->minDate(Carbon::now())
->maxDate(Carbon::now()->addMonth()), ->maxDate(Carbon::now()->addMonth()),
]), ]),
@@ -60,7 +60,7 @@ class CreatePost extends CreateRecord
$this->slideData['start_time'] = $this->record->publish_at; $this->slideData['start_time'] = $this->record->publish_at;
$sliderDTO = MainSliderDTO::fromArray($this->slideData); $sliderDTO = MainSliderDTO::fromArray($this->slideData);
(new PostSliderService($sliderDTO, $this->record->slug))->create(); (new PostSliderService($sliderDTO, $this->record))->create();
} }
protected function generateSeo(): void protected function generateSeo(): void
@@ -5,6 +5,7 @@ namespace App\Filament\Resources\PostResource\Pages;
use App\Dto\MainSliderDTO; use App\Dto\MainSliderDTO;
use App\Enums\PostStatus; use App\Enums\PostStatus;
use App\Filament\Resources\PostResource; use App\Filament\Resources\PostResource;
use App\Models\Post;
use App\Services\Filament\Domain\Posts\PostDataProcessor; use App\Services\Filament\Domain\Posts\PostDataProcessor;
use App\Services\Filament\Domain\Posts\PostNotificationService; use App\Services\Filament\Domain\Posts\PostNotificationService;
use App\Services\Filament\Domain\Posts\PostSeoGenerator; use App\Services\Filament\Domain\Posts\PostSeoGenerator;
@@ -23,6 +24,12 @@ class EditPost extends EditRecord
protected array $slideData; protected array $slideData;
protected function mutateFormDataBeforeFill(array $data): array
{
$post = Post::query()->with(['seo', 'mainSlider'])->find($data['id']);
$data['slide'] = $post->mainSlider->toArray() ?? null;
return $data;
}
protected function mutateFormDataBeforeSave(array $data): array protected function mutateFormDataBeforeSave(array $data): array
{ {
@@ -61,7 +68,7 @@ class EditPost extends EditRecord
$this->slideData['start_time'] = $this->record->publish_at; $this->slideData['start_time'] = $this->record->publish_at;
$sliderDTO = MainSliderDTO::fromArray($this->slideData); $sliderDTO = MainSliderDTO::fromArray($this->slideData);
(new PostSliderService($sliderDTO, $this->record->slug))->update(); (new PostSliderService($sliderDTO, $this->record))->update();
} }
protected function generateSeo(): void protected function generateSeo(): void
+9
View File
@@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class MainSlider extends Model class MainSlider extends Model
{ {
@@ -16,4 +18,11 @@ class MainSlider extends Model
'settings' => 'array', 'settings' => 'array',
'image' => 'array', 'image' => 'array',
]; ];
public function slidable()
{
return $this->morphTo();
}
} }
+7 -1
View File
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Spatie\Tags\HasTags; use Spatie\Tags\HasTags;
@@ -39,12 +41,16 @@ class Post extends Model
return $this->belongsTo(User::class, 'user_id'); return $this->belongsTo(User::class, 'user_id');
} }
public function seo() public function seo()
{ {
return $this->morphOne(Seo::class, 'seoable'); return $this->morphOne(Seo::class, 'seoable');
} }
public function mainSlider()
{
return $this->morphOne(MainSlider::class, 'slidable');
}
protected $casts = [ protected $casts = [
'content' => 'array', 'content' => 'array',
'authors' => 'array', 'authors' => 'array',
+10 -7
View File
@@ -19,12 +19,14 @@ class MainSliderObserver
*/ */
public function created(MainSlider $mainSlider): void public function created(MainSlider $mainSlider): void
{ {
// Устанавливаем сортировку для новой записи // Устанавливаем сортировку для новой записи
$mainSlider->sort = 1; $mainSlider->sort = 1;
$mainSlider->save(); $mainSlider->save();
// Обновляем сортировку для всех остальных записей // Обновляем сортировку для всех остальных записей
$this->updateSortOrder(); $this->updateSortOrder($mainSlider->id);
$this->cacheService->clearAllCacheByModel(); $this->cacheService->clearAllCacheByModel();
} }
@@ -61,15 +63,16 @@ class MainSliderObserver
// //
} }
protected function updateSortOrder(): void protected function updateSortOrder($id): void
{ {
// Получаем все записи, отсортированные по текущему значению sort // Получаем все записи, отсортированные по текущему значению sort
$slides = MainSlider::orderBy('sort', 'asc')->get(); $slides = MainSlider::orderBy('sort', 'asc')->where('id', '!=', $id)->get();
// Обновляем сортировку для каждой записи if ($slides->count() > 0) {
foreach ($slides as $index => $slide) { foreach ($slides as $index => $slide) {
$slide->sort = $index + 1; // Начинаем с 1 $slide->sort = $index + 2; // Начинаем с 1
$slide->save(); $slide->save();
}
} }
} }
} }
@@ -35,6 +35,7 @@ class PostDataProcessor
// Устанавливаем ID текущего пользователя // Устанавливаем ID текущего пользователя
$data['user_id'] = auth()->id(); $data['user_id'] = auth()->id();
return $data; return $data;
} }
@@ -62,12 +63,16 @@ class PostDataProcessor
* @param string $status * @param string $status
* @return Carbon|null * @return Carbon|null
*/ */
private function setPublishDateTime(array $publishSetting, string|PostStatus $status): ?Carbon private function setPublishDateTime(array $publishSetting, $status): ?Carbon
{ {
if ($publishSetting['publish_after'] === true) { if ($publishSetting['publish_after'] === true) {
return Carbon::parse($publishSetting['publish_at']); return Carbon::parse($publishSetting['publish_at']);
} }
if ($status === PostStatus::PUBLISHED->value) {
return Carbon::now();
}
if ($status === PostStatus::PUBLISHED) { if ($status === PostStatus::PUBLISHED) {
return Carbon::now(); return Carbon::now();
} }
@@ -4,56 +4,96 @@ namespace App\Services\Filament\Domain\Posts;
use App\Dto\MainSliderDTO; use App\Dto\MainSliderDTO;
use App\Models\MainSlider; use App\Models\MainSlider;
use App\Models\Post;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class PostSliderService class PostSliderService
{ {
public function __construct( public function __construct(
readonly private MainSliderDTO $dto, private readonly MainSliderDTO $dto,
readonly private string $postSlug, private readonly Model $post,
){} ) {}
/**
* Create a new MainSlider record associated with the post.
*
* @throws \Exception
*/
public function create(): void public function create(): void
{ {
try { try {
MainSlider::create([ $this->post->mainSlider()->create([
'title' => $this->dto->title, 'title' => $this->dto->title,
'content' => $this->dto->content, 'content' => $this->dto->content,
'image' => $this->dto->image, 'image' => $this->dto->image,
'link' => parse_url(route('client.post.show', $this->postSlug), PHP_URL_PATH), 'link' => $this->generatePostLink(),
'link_text' => $this->dto->link_text, 'settings' => $this->dto->settings,
'is_active' => $this->dto->is_active,
'color_theme' => $this->dto->color_theme, 'color_theme' => $this->dto->color_theme,
'is_active' => $this->dto->is_active,
'start_time' => $this->dto->start_time, 'start_time' => $this->dto->start_time,
'end_time' => $this->dto->end_time, 'end_time' => $this->dto->end_time,
]); ]);
Log::info('MainSlider created successfully', ['postSlug' => $this->postSlug]); Log::info('MainSlider created successfully', ['postTitle' => $this->post->title]);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Failed to create MainSlider', ['error' => $e->getMessage()]); Log::error('Failed to create MainSlider', [
'postTitle' => $this->post->title,
'error' => $e->getMessage(),
]);
throw $e; throw $e;
} }
} }
/**
* Update an existing MainSlider record associated with the post.
*
* @throws \Exception
*/
public function update(): void public function update(): void
{ {
try { try {
MainSlider::update([ // Retrieve the MainSlider instance
'title' => $this->dto->title, $mainSlider = $this->post->mainSlider;
'content' => $this->dto->content,
'image' => $this->dto->image,
'link' => parse_url(route('client.post.show', $this->postSlug), PHP_URL_PATH),
'link_text' => $this->dto->link_text,
'is_active' => $this->dto->is_active,
'color_theme' => $this->dto->color_theme,
'start_time' => $this->dto->start_time,
'end_time' => $this->dto->end_time,
]);
Log::info('MainSlider updated successfully', ['postSlug' => $this->postSlug]);
if ($mainSlider) {
// Update properties and save to trigger model events
$mainSlider->fill([
'title' => $this->dto->title,
'content' => $this->dto->content,
'image' => $this->dto->image,
'link' => $this->generatePostLink(),
'settings' => $this->dto->settings,
'color_theme' => $this->dto->color_theme,
'is_active' => $this->dto->is_active,
'start_time' => $this->dto->start_time,
'end_time' => $this->dto->end_time,
]);
$mainSlider->save(); // This will trigger updating and updated observer events
Log::info('MainSlider updated successfully', ['postTitle' => $this->post->title]);
} else {
Log::warning('MainSlider not found for update', ['postTitle' => $this->post->title]);
}
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Failed to update MainSlider', ['error' => $e->getMessage()]); Log::error('Failed to update MainSlider', [
'postTitle' => $this->post->title,
'error' => $e->getMessage(),
]);
throw $e; throw $e;
} }
} }
}
/**
* Generate the link for the post.
*
* @return string
*/
private function generatePostLink(): string
{
return parse_url(route('client.post.show', $this->post->slug), PHP_URL_PATH);
}
}
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('main_sliders', function (Blueprint $table) {
$table->unsignedBigInteger('slidable_id')->nullable(); // Поле для идентификатора
$table->string('slidable_type')->nullable(); // Поле для типа модели
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('main_sliders', function (Blueprint $table) {
$table->dropColumn(['slidable_id', 'slidable_type']);
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('pages', function (Blueprint $table) {
$table->longText('settings')->nullable()->after('sub_section_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pages', function (Blueprint $table) {
$table->dropColumn('settings');
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('pages', function (Blueprint $table) {
$table->dropColumn('template');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('pages', function (Blueprint $table) {
$table->string('template')->nullable();
});
}
};
+12 -3
View File
@@ -27,9 +27,18 @@
</div> </div>
</div> </div>
</div> </div>
<a v-if="item.settings.link_text" :href="item.link" class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-semibold rounded-lg border border-white text-white hover:border-white/70 hover:text-white/70 disabled:opacity-50 disabled:pointer-events-none"> <div :class="`text-${item.settings.text_position}`">
{{ item.settings.link_text }} <a
</a> v-if="item.settings.link_text"
:href="item.link"
class="py-3 px-4 inline-flex items-center duration-300 gap-x-2 text-sm font-semibold rounded-lg border border-white text-white bg-transparent hover:bg-white hover:text-black hover:mix-blend-screen disabled:opacity-50 disabled:pointer-events-none"
>
{{ item.settings.link_text }}
</a>
</div>
</div> </div>
<div v-if="slidersCarousel.data.length >= 2" class="mx-auto max-w-screen-md px-5"> <div v-if="slidersCarousel.data.length >= 2" class="mx-auto max-w-screen-md px-5">
<div class="mt-8 text-gray-500 absolute bottom-[100px]"> <div class="mt-8 text-gray-500 absolute bottom-[100px]">