diff --git a/app/Http/Controllers/Api/MenuFavoriteController.php b/app/Http/Controllers/Api/MenuFavoriteController.php new file mode 100644 index 00000000..c9d1074c --- /dev/null +++ b/app/Http/Controllers/Api/MenuFavoriteController.php @@ -0,0 +1,44 @@ +validate([ + 'menu_id' => 'required|integer|exists:menus,id', + ]); + + $result = $this->sidebarMenuService->toggleFavorite( + auth()->id(), + $request->integer('menu_id') + ); + + return response()->json($result); + } + + public function reorder(Request $request): JsonResponse + { + $request->validate([ + 'menu_ids' => 'required|array', + 'menu_ids.*' => 'integer', + ]); + + $this->sidebarMenuService->reorderFavorites( + auth()->id(), + $request->input('menu_ids') + ); + + return response()->json(['success' => true]); + } +} diff --git a/app/Models/Commons/MenuFavorite.php b/app/Models/Commons/MenuFavorite.php new file mode 100644 index 00000000..1c5f148a --- /dev/null +++ b/app/Models/Commons/MenuFavorite.php @@ -0,0 +1,36 @@ + 'integer', + ]; + + public function menu(): BelongsTo + { + return $this->belongsTo(Menu::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function scopeForUser($query, int $userId) + { + return $query->where('user_id', $userId)->orderBy('sort_order'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b9d84580..2d148c76 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -60,6 +60,8 @@ public function boot(): void 'mainMenus' => $menusBySection['main'], 'toolsMenus' => $menusBySection['tools'], 'labsMenus' => $menusBySection['labs'], + 'favoriteMenus' => $menuService->getFavoriteMenus(), + 'favoriteMenuIds' => $menuService->getFavoriteMenuIds(), ]); }); } diff --git a/app/Services/SidebarMenuService.php b/app/Services/SidebarMenuService.php index 8c7362f0..0b51a208 100644 --- a/app/Services/SidebarMenuService.php +++ b/app/Services/SidebarMenuService.php @@ -4,6 +4,7 @@ use App\Models\Boards\Board; use App\Models\Commons\Menu; +use App\Models\Commons\MenuFavorite; use App\Models\Tenants\Department; use App\Models\User; use Illuminate\Support\Collection; @@ -297,6 +298,100 @@ private static function hasMoreSpecificPrefixMenu(string $currentPath, string $m return $result; } + // ─── 즐겨찾기 기능 ─── + + private const MAX_FAVORITES = 10; + + /** + * 사용자의 즐겨찾기 메뉴 목록 조회 + */ + public function getFavoriteMenus(?int $userId = null): Collection + { + $userId = $userId ?? auth()->id(); + if (! $userId) { + return collect(); + } + + $tenantId = auth()->user()?->tenant_id ?? 1; + + return MenuFavorite::where('tenant_id', $tenantId) + ->forUser($userId) + ->with(['menu' => fn ($q) => $q->withoutGlobalScopes()]) + ->get() + ->filter(fn ($fav) => $fav->menu && $fav->menu->is_active) + ->values(); + } + + /** + * 즐겨찾기 메뉴 ID 배열 (별 아이콘 활성 판단용) + */ + public function getFavoriteMenuIds(?int $userId = null): array + { + $userId = $userId ?? auth()->id(); + if (! $userId) { + return []; + } + + $tenantId = auth()->user()?->tenant_id ?? 1; + + return MenuFavorite::where('tenant_id', $tenantId) + ->where('user_id', $userId) + ->pluck('menu_id') + ->toArray(); + } + + /** + * 즐겨찾기 토글 (추가/제거) + */ + public function toggleFavorite(int $userId, int $menuId): array + { + $tenantId = auth()->user()?->tenant_id ?? 1; + + $existing = MenuFavorite::where('tenant_id', $tenantId) + ->where('user_id', $userId) + ->where('menu_id', $menuId) + ->first(); + + if ($existing) { + $existing->delete(); + + return ['action' => 'removed']; + } + + // 최대 개수 체크 + $count = MenuFavorite::where('tenant_id', $tenantId) + ->where('user_id', $userId) + ->count(); + + if ($count >= self::MAX_FAVORITES) { + return ['action' => 'max_reached', 'max' => self::MAX_FAVORITES]; + } + + MenuFavorite::create([ + 'tenant_id' => $tenantId, + 'user_id' => $userId, + 'menu_id' => $menuId, + 'sort_order' => $count, + ]); + + return ['action' => 'added']; + } + + /** + * 즐겨찾기 순서 변경 + */ + public function reorderFavorites(int $userId, array $menuIds): void + { + $tenantId = auth()->user()?->tenant_id ?? 1; + + foreach ($menuIds as $order => $menuId) { + MenuFavorite::where('tenant_id', $tenantId) + ->where('user_id', $userId) + ->where('menu_id', $menuId) + ->update(['sort_order' => $order]); + } + } + /** * 메뉴 또는 자식 메뉴가 활성 상태인지 확인 */ diff --git a/resources/views/components/sidebar/favorites-section.blade.php b/resources/views/components/sidebar/favorites-section.blade.php new file mode 100644 index 00000000..881f53eb --- /dev/null +++ b/resources/views/components/sidebar/favorites-section.blade.php @@ -0,0 +1,65 @@ +@props(['favorites' => collect()]) + +@if($favorites->isNotEmpty()) +