260 lines
10 KiB
PHP
260 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Studio;
|
|
|
|
use App\Models\DashboardPreference;
|
|
use App\Models\User;
|
|
|
|
final class CreatorStudioPreferenceService
|
|
{
|
|
/**
|
|
* @return array{
|
|
* default_content_view: string,
|
|
* analytics_range_days: int,
|
|
* dashboard_shortcuts: array<int, string>,
|
|
* featured_modules: array<int, string>,
|
|
* featured_content: array<string, int>,
|
|
* draft_behavior: string,
|
|
* default_landing_page: string,
|
|
* widget_visibility: array<string, bool>,
|
|
* widget_order: array<int, string>,
|
|
* card_density: string,
|
|
* scheduling_timezone: string|null,
|
|
* activity_last_read_at: string|null
|
|
* }
|
|
*/
|
|
public function forUser(User $user): array
|
|
{
|
|
$record = DashboardPreference::query()->find($user->id);
|
|
$stored = is_array($record?->studio_preferences) ? $record->studio_preferences : [];
|
|
|
|
return [
|
|
'default_content_view' => $this->normalizeView((string) ($stored['default_content_view'] ?? 'grid')),
|
|
'analytics_range_days' => $this->normalizeRange((int) ($stored['analytics_range_days'] ?? 30)),
|
|
'dashboard_shortcuts' => DashboardPreference::pinnedSpacesForUser($user),
|
|
'featured_modules' => $this->normalizeModules($stored['featured_modules'] ?? []),
|
|
'featured_content' => $this->normalizeFeaturedContent($stored['featured_content'] ?? []),
|
|
'draft_behavior' => $this->normalizeDraftBehavior((string) ($stored['draft_behavior'] ?? 'resume-last')),
|
|
'default_landing_page' => $this->normalizeLandingPage((string) ($stored['default_landing_page'] ?? 'overview')),
|
|
'widget_visibility' => $this->normalizeWidgetVisibility($stored['widget_visibility'] ?? []),
|
|
'widget_order' => $this->normalizeWidgetOrder($stored['widget_order'] ?? []),
|
|
'card_density' => $this->normalizeDensity((string) ($stored['card_density'] ?? 'comfortable')),
|
|
'scheduling_timezone' => $this->normalizeTimezone($stored['scheduling_timezone'] ?? null),
|
|
'activity_last_read_at' => $this->normalizeActivityLastReadAt($stored['activity_last_read_at'] ?? null),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
* @return array{
|
|
* default_content_view: string,
|
|
* analytics_range_days: int,
|
|
* dashboard_shortcuts: array<int, string>,
|
|
* featured_modules: array<int, string>,
|
|
* featured_content: array<string, int>,
|
|
* draft_behavior: string,
|
|
* default_landing_page: string,
|
|
* widget_visibility: array<string, bool>,
|
|
* widget_order: array<int, string>,
|
|
* card_density: string,
|
|
* scheduling_timezone: string|null,
|
|
* activity_last_read_at: string|null
|
|
* }
|
|
*/
|
|
public function update(User $user, array $attributes): array
|
|
{
|
|
$current = $this->forUser($user);
|
|
$payload = [
|
|
'default_content_view' => array_key_exists('default_content_view', $attributes)
|
|
? $this->normalizeView((string) $attributes['default_content_view'])
|
|
: $current['default_content_view'],
|
|
'analytics_range_days' => array_key_exists('analytics_range_days', $attributes)
|
|
? $this->normalizeRange((int) $attributes['analytics_range_days'])
|
|
: $current['analytics_range_days'],
|
|
'featured_modules' => array_key_exists('featured_modules', $attributes)
|
|
? $this->normalizeModules($attributes['featured_modules'])
|
|
: $current['featured_modules'],
|
|
'featured_content' => array_key_exists('featured_content', $attributes)
|
|
? $this->normalizeFeaturedContent($attributes['featured_content'])
|
|
: $current['featured_content'],
|
|
'draft_behavior' => array_key_exists('draft_behavior', $attributes)
|
|
? $this->normalizeDraftBehavior((string) $attributes['draft_behavior'])
|
|
: $current['draft_behavior'],
|
|
'default_landing_page' => array_key_exists('default_landing_page', $attributes)
|
|
? $this->normalizeLandingPage((string) $attributes['default_landing_page'])
|
|
: $current['default_landing_page'],
|
|
'widget_visibility' => array_key_exists('widget_visibility', $attributes)
|
|
? $this->normalizeWidgetVisibility($attributes['widget_visibility'])
|
|
: $current['widget_visibility'],
|
|
'widget_order' => array_key_exists('widget_order', $attributes)
|
|
? $this->normalizeWidgetOrder($attributes['widget_order'])
|
|
: $current['widget_order'],
|
|
'card_density' => array_key_exists('card_density', $attributes)
|
|
? $this->normalizeDensity((string) $attributes['card_density'])
|
|
: $current['card_density'],
|
|
'scheduling_timezone' => array_key_exists('scheduling_timezone', $attributes)
|
|
? $this->normalizeTimezone($attributes['scheduling_timezone'])
|
|
: $current['scheduling_timezone'],
|
|
'activity_last_read_at' => array_key_exists('activity_last_read_at', $attributes)
|
|
? $this->normalizeActivityLastReadAt($attributes['activity_last_read_at'])
|
|
: $current['activity_last_read_at'],
|
|
];
|
|
|
|
$record = DashboardPreference::query()->firstOrNew(['user_id' => $user->id]);
|
|
$record->pinned_spaces = array_key_exists('dashboard_shortcuts', $attributes)
|
|
? DashboardPreference::sanitizePinnedSpaces(is_array($attributes['dashboard_shortcuts']) ? $attributes['dashboard_shortcuts'] : [])
|
|
: $current['dashboard_shortcuts'];
|
|
$record->studio_preferences = $payload;
|
|
$record->save();
|
|
|
|
return $this->forUser($user);
|
|
}
|
|
|
|
private function normalizeView(string $view): string
|
|
{
|
|
return in_array($view, ['grid', 'list'], true) ? $view : 'grid';
|
|
}
|
|
|
|
private function normalizeRange(int $days): int
|
|
{
|
|
return in_array($days, [7, 14, 30, 60, 90], true) ? $days : 30;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $modules
|
|
* @return array<int, string>
|
|
*/
|
|
private function normalizeModules(mixed $modules): array
|
|
{
|
|
$allowed = ['artworks', 'cards', 'collections', 'stories'];
|
|
|
|
return collect(is_array($modules) ? $modules : [])
|
|
->map(fn ($module): string => (string) $module)
|
|
->filter(fn (string $module): bool => in_array($module, $allowed, true))
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param mixed $featuredContent
|
|
* @return array<string, int>
|
|
*/
|
|
private function normalizeFeaturedContent(mixed $featuredContent): array
|
|
{
|
|
$allowed = ['artworks', 'cards', 'collections', 'stories'];
|
|
|
|
return collect(is_array($featuredContent) ? $featuredContent : [])
|
|
->mapWithKeys(function ($id, $module) use ($allowed): array {
|
|
$moduleKey = (string) $module;
|
|
$normalizedId = (int) $id;
|
|
|
|
if (! in_array($moduleKey, $allowed, true) || $normalizedId < 1) {
|
|
return [];
|
|
}
|
|
|
|
return [$moduleKey => $normalizedId];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
private function normalizeDraftBehavior(string $value): string
|
|
{
|
|
return in_array($value, ['resume-last', 'open-drafts', 'focus-published'], true)
|
|
? $value
|
|
: 'resume-last';
|
|
}
|
|
|
|
private function normalizeLandingPage(string $value): string
|
|
{
|
|
return in_array($value, ['overview', 'content', 'drafts', 'scheduled', 'analytics', 'activity', 'calendar', 'inbox', 'search', 'growth', 'challenges', 'preferences'], true)
|
|
? $value
|
|
: 'overview';
|
|
}
|
|
|
|
private function normalizeDensity(string $value): string
|
|
{
|
|
return in_array($value, ['compact', 'comfortable'], true)
|
|
? $value
|
|
: 'comfortable';
|
|
}
|
|
|
|
private function normalizeTimezone(mixed $value): ?string
|
|
{
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim($value);
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
private function normalizeActivityLastReadAt(mixed $value): ?string
|
|
{
|
|
if (! is_string($value)) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim($value);
|
|
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<string, bool>
|
|
*/
|
|
private function normalizeWidgetVisibility(mixed $value): array
|
|
{
|
|
$defaults = collect($this->allowedWidgets())->mapWithKeys(fn (string $widget): array => [$widget => true]);
|
|
|
|
return $defaults->merge(
|
|
collect(is_array($value) ? $value : [])
|
|
->filter(fn ($enabled, $widget): bool => in_array((string) $widget, $this->allowedWidgets(), true))
|
|
->map(fn ($enabled): bool => (bool) $enabled)
|
|
)->all();
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @return array<int, string>
|
|
*/
|
|
private function normalizeWidgetOrder(mixed $value): array
|
|
{
|
|
$requested = collect(is_array($value) ? $value : [])
|
|
->map(fn ($widget): string => (string) $widget)
|
|
->filter(fn (string $widget): bool => in_array($widget, $this->allowedWidgets(), true))
|
|
->unique()
|
|
->values();
|
|
|
|
return $requested
|
|
->concat(collect($this->allowedWidgets())->reject(fn (string $widget): bool => $requested->contains($widget)))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function allowedWidgets(): array
|
|
{
|
|
return [
|
|
'quick_stats',
|
|
'continue_working',
|
|
'scheduled_items',
|
|
'recent_activity',
|
|
'top_performers',
|
|
'draft_reminders',
|
|
'module_summaries',
|
|
'growth_hints',
|
|
'active_challenges',
|
|
'creator_health',
|
|
'featured_status',
|
|
'comments_snapshot',
|
|
'stale_drafts',
|
|
];
|
|
}
|
|
} |