feat: [sidebar] 사이드바 메뉴 즐겨찾기 기능 추가
- MenuFavorite 모델 생성 (menu_favorites 테이블) - SidebarMenuService에 즐겨찾기 CRUD 메서드 추가 - MenuFavoriteController 생성 (toggle/reorder API) - 사이드바 상단에 즐겨찾기 섹션 표시 - 메뉴 아이템에 별 아이콘 추가 (hover 시 표시, 토글) - 최대 10개 제한, 리프 메뉴만 대상
This commit is contained in:
44
app/Http/Controllers/Api/MenuFavoriteController.php
Normal file
44
app/Http/Controllers/Api/MenuFavoriteController.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\SidebarMenuService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MenuFavoriteController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private SidebarMenuService $sidebarMenuService
|
||||
) {}
|
||||
|
||||
public function toggle(Request $request): JsonResponse
|
||||
{
|
||||
$request->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]);
|
||||
}
|
||||
}
|
||||
36
app/Models/Commons/MenuFavorite.php
Normal file
36
app/Models/Commons/MenuFavorite.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Commons;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class MenuFavorite extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'user_id',
|
||||
'menu_id',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort_order' => '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');
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,8 @@ public function boot(): void
|
||||
'mainMenus' => $menusBySection['main'],
|
||||
'toolsMenus' => $menusBySection['tools'],
|
||||
'labsMenus' => $menusBySection['labs'],
|
||||
'favoriteMenus' => $menuService->getFavoriteMenus(),
|
||||
'favoriteMenuIds' => $menuService->getFavoriteMenuIds(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 또는 자식 메뉴가 활성 상태인지 확인
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user