변환된 메뉴 데이터 배열 */ public function toArray(Request $request): array { return [ 'id' => $this->getValue('id'), 'name' => $this->getValue('name'), // 다국어 객체 그대로 반환 (프론트엔드에서 $localized() 사용) 'slug' => $this->getValue('slug'), 'url' => $this->getValue('url'), 'icon' => $this->getValue('icon'), 'order' => $this->getValue('order'), 'is_active' => $this->getValue('is_active', true), // 관계형 데이터 'parent_id' => $this->getValue('parent_id'), // parent_id 추가 (프론트엔드에서 필요) 'extension_type' => $this->getValue('extension_type'), 'extension_identifier' => $this->getValue('extension_identifier'), 'parent' => $this->whenLoaded('parent', function () { return $this->parent ? [ 'id' => $this->parent->id, 'name' => $this->parent->name, // 다국어 객체 그대로 반환 'url' => $this->parent->url, 'icon' => $this->parent->icon, ] : null; }), 'children' => $this->whenLoaded('children', function () { return $this->children->map(function ($child) { $data = [ 'id' => $child->id, 'name' => $child->name, // 다국어 객체 그대로 반환 'slug' => $child->slug, 'url' => $child->url, 'icon' => $child->icon, 'order' => $child->order, 'is_active' => $child->is_active, 'parent_id' => $child->parent_id, 'extension_type' => $child->extension_type?->value, 'extension_identifier' => $child->extension_identifier, ]; // 역할은 로드된 경우에만 싣는다. 미로드 시 `[]` 를 돌려주면 "역할 제한이 // 없다" 는 사실이 아닌 단언이 되어, 화면이 그 값을 그대로 저장에 실어 // 보내면 해당 메뉴의 역할 제한이 통째로 지워진다. 키 자체를 생략해 // 소비 측이 "모른다" 를 구분할 수 있게 한다 (단건 조회로 보강). if ($child->relationLoaded('roles')) { $data['roles'] = $child->roles->map(function ($role) { return [ 'id' => $role->id, 'name' => $role->name, 'permission_type' => $role->pivot->permission_type ?? null, ]; })->values(); } return $data; })->sortBy('order')->values(); }), 'creator' => $this->whenLoaded('creator', function () { return $this->creator ? [ 'uuid' => $this->creator->uuid, 'name' => $this->creator->name, 'email' => $this->creator->email, ] : null; }), 'roles' => $this->whenLoaded('roles', function () { return $this->roles->map(function ($role) { return [ 'id' => $role->id, 'name' => $role->name, 'permission_type' => $role->pivot->permission_type ?? null, ]; }); }), // 통계 정보 'children_count' => $this->when( isset($this->children_count), $this->getValue('children_count') ), 'depth_level' => $this->when( isset($this->depth_level), $this->getValue('depth_level') ), ...$this->formatTimestamps(), ...$this->resourceMeta($request), ]; } /** * 소유자 필드명을 반환합니다. * * @return string|null 소유자 필드명 */ protected function ownerField(): ?string { return 'created_by'; } /** * 리소스별 권한 매핑을 반환합니다. * * @return array */ protected function abilityMap(): array { return [ 'can_create' => 'core.menus.create', 'can_update' => 'core.menus.update', 'can_delete' => 'core.menus.delete', ]; } /** * 네비게이션용 단순한 형태의 배열을 반환합니다 (계층 구조 포함). * * @return array 네비게이션용 메뉴 정보 */ public function toNavigationArray(): array { return [ 'id' => $this->id, 'name' => $this->name, // 다국어 객체 그대로 반환 'slug' => $this->slug, 'url' => $this->url, 'icon' => $this->icon, 'order' => $this->order, 'is_active' => $this->is_active, 'children' => $this->whenLoaded('activeChildren', function () { return $this->activeChildren ->sortBy('order') ->map(function ($child) { return [ 'id' => $child->id, 'name' => $child->name, // 다국어 객체 그대로 반환 'slug' => $child->slug, 'url' => $child->url, 'icon' => $child->icon, 'order' => $child->order, 'is_active' => $child->is_active, 'parent_id' => $child->parent_id, 'extension_type' => $child->extension_type?->value, 'extension_identifier' => $child->extension_identifier, ]; })->values(); }), ]; } /** * 관리자용 상세 정보를 포함한 배열을 반환합니다. * * @return array 관리자용 상세 메뉴 정보 */ public function withAdminInfo(): array { return array_merge($this->toArray(request()), [ 'admin_notes' => $this->admin_notes, 'created_by' => $this->creator?->uuid, 'last_accessed_at' => $this->when($this->last_accessed_at, fn () => $this->formatDateTimeForUser($this->last_accessed_at)), 'access_count' => $this->access_count ?? 0, ]); } }