diff --git a/app/Models/Commons/Menu.php b/app/Models/Commons/Menu.php index ab1f559..4dc5afd 100644 --- a/app/Models/Commons/Menu.php +++ b/app/Models/Commons/Menu.php @@ -19,7 +19,7 @@ class Menu extends Model protected $fillable = [ 'tenant_id', 'parent_id', 'global_menu_id', 'name', 'url', 'is_active', 'sort_order', - 'hidden', 'is_customized', 'is_external', 'external_url', 'icon', + 'hidden', 'is_customized', 'is_external', 'external_url', 'icon', 'options', 'created_by', 'updated_by', 'deleted_by', ]; @@ -35,6 +35,7 @@ class Menu extends Model 'hidden' => 'boolean', 'is_customized' => 'boolean', 'is_external' => 'boolean', + 'options' => 'array', ]; /** @@ -135,6 +136,140 @@ public function scopeRoots($query) */ public static function getSyncFields(): array { - return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url']; + return ['name', 'url', 'icon', 'sort_order', 'is_active', 'hidden', 'is_external', 'external_url', 'options']; + } + + // ============================================================ + // Options JSON 헬퍼 메서드 + // ============================================================ + + /** + * options에서 특정 키 값 조회 + */ + public function getOption(string $key, mixed $default = null): mixed + { + return data_get($this->options, $key, $default); + } + + /** + * options에 특정 키 값 설정 + */ + public function setOption(string $key, mixed $value): static + { + $options = $this->options ?? []; + data_set($options, $key, $value); + $this->options = $options; + + return $this; + } + + /** + * 라우트명 조회 (mng, api, react 등에서 사용) + */ + public function getRouteName(): ?string + { + return $this->getOption('route_name'); + } + + /** + * 메뉴 섹션 조회 (main, tools, labs 등) + */ + public function getSection(): string + { + return $this->getOption('section', 'main'); + } + + /** + * 메뉴 타입 조회 (normal, tool, lab 등) + */ + public function getMenuType(): string + { + return $this->getOption('menu_type', 'normal'); + } + + /** + * 필요 역할 조회 + */ + public function getRequiresRole(): ?string + { + return $this->getOption('requires_role'); + } + + /** + * 특정 역할이 필요한지 확인 + */ + public function requiresRole(?string $role = null): bool + { + $requiredRole = $this->getRequiresRole(); + + if ($requiredRole === null) { + return false; + } + + if ($role === null) { + return true; // 역할이 필요함 + } + + return $requiredRole === $role; + } + + /** + * Blade 컴포넌트명 조회 + */ + public function getBladeComponent(): ?string + { + return $this->getOption('blade_component'); + } + + /** + * CSS 클래스 조회 + */ + public function getCssClass(): ?string + { + return $this->getOption('css_class'); + } + + /** + * meta 데이터 조회 (앱별 커스텀 데이터) + */ + public function getMeta(?string $key = null, mixed $default = null): mixed + { + if ($key === null) { + return $this->getOption('meta', []); + } + + return $this->getOption("meta.{$key}", $default); + } + + /** + * meta 데이터 설정 + */ + public function setMeta(string $key, mixed $value): static + { + return $this->setOption("meta.{$key}", $value); + } + + /** + * 특정 섹션 메뉴만 조회 + */ + public function scopeSection($query, string $section) + { + return $query->whereJsonContains('options->section', $section); + } + + /** + * 특정 메뉴 타입만 조회 + */ + public function scopeMenuType($query, string $type) + { + return $query->whereJsonContains('options->menu_type', $type); + } + + /** + * 특정 역할이 필요한 메뉴만 조회 + */ + public function scopeRequiringRole($query, string $role) + { + return $query->whereJsonContains('options->requires_role', $role); } } diff --git a/database/migrations/2025_12_16_000001_add_options_to_menus_table.php b/database/migrations/2025_12_16_000001_add_options_to_menus_table.php new file mode 100644 index 0000000..81b868f --- /dev/null +++ b/database/migrations/2025_12_16_000001_add_options_to_menus_table.php @@ -0,0 +1,29 @@ +json('options')->nullable() + ->after('external_url') + ->comment('확장 옵션 JSON: route_name, section, menu_type, requires_role, meta 등'); + }); + } + + public function down(): void + { + Schema::table('menus', function (Blueprint $table) { + $table->dropColumn('options'); + }); + } +};