@j-solution/components 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +413 -415
  2. package/assets/jwms-portal-frontend-Ct2Tc7yj.css +1 -0
  3. package/assets/styles/global-utilities.css +34 -0
  4. package/assets/styles/j-components.css +1 -1
  5. package/assets/styles/themes.css +443 -443
  6. package/components/atoms/JButton.vue.cjs +6 -1
  7. package/components/atoms/JButton.vue.cjs.map +1 -1
  8. package/components/atoms/JButton.vue.js +10 -85
  9. package/components/atoms/JButton.vue.js.map +1 -1
  10. package/components/atoms/JButton.vue2.cjs +1 -1
  11. package/components/atoms/JButton.vue2.cjs.map +1 -1
  12. package/components/atoms/JButton.vue2.js +85 -2
  13. package/components/atoms/JButton.vue2.js.map +1 -1
  14. package/components/atoms/JGrid.vue.cjs +1 -1
  15. package/components/atoms/JGrid.vue.js +1 -1
  16. package/components/atoms/JGrid.vue2.cjs +1 -1
  17. package/components/atoms/JGrid.vue2.cjs.map +1 -1
  18. package/components/atoms/JGrid.vue2.js +39 -35
  19. package/components/atoms/JGrid.vue2.js.map +1 -1
  20. package/components/atoms/JLabel.vue.cjs +1 -1
  21. package/components/atoms/JLabel.vue.cjs.map +1 -1
  22. package/components/atoms/JLabel.vue.js +26 -22
  23. package/components/atoms/JLabel.vue.js.map +1 -1
  24. package/components/atoms/JSectionTitle.vue.cjs +7 -0
  25. package/components/atoms/JSectionTitle.vue.cjs.map +1 -0
  26. package/components/atoms/JSectionTitle.vue.js +13 -0
  27. package/components/atoms/JSectionTitle.vue.js.map +1 -0
  28. package/components/atoms/JSectionTitle.vue2.cjs +2 -0
  29. package/components/atoms/JSectionTitle.vue2.cjs.map +1 -0
  30. package/components/atoms/JSectionTitle.vue2.js +67 -0
  31. package/components/atoms/JSectionTitle.vue2.js.map +1 -0
  32. package/components/atoms/JSplitter.vue.cjs +6 -1
  33. package/components/atoms/JSplitter.vue.cjs.map +1 -1
  34. package/components/atoms/JSplitter.vue.js +10 -59
  35. package/components/atoms/JSplitter.vue.js.map +1 -1
  36. package/components/atoms/JSplitter.vue2.cjs +1 -1
  37. package/components/atoms/JSplitter.vue2.cjs.map +1 -1
  38. package/components/atoms/JSplitter.vue2.js +59 -2
  39. package/components/atoms/JSplitter.vue2.js.map +1 -1
  40. package/components/examples/ExampleCrudPage.vue.cjs +1 -1
  41. package/components/examples/ExampleCrudPage.vue.cjs.map +1 -1
  42. package/components/examples/ExampleCrudPage.vue.js +228 -208
  43. package/components/examples/ExampleCrudPage.vue.js.map +1 -1
  44. package/components/examples/ExampleTabMappingPage.vue.cjs +1 -1
  45. package/components/examples/ExampleTabMappingPage.vue.cjs.map +1 -1
  46. package/components/examples/ExampleTabMappingPage.vue.js +341 -368
  47. package/components/examples/ExampleTabMappingPage.vue.js.map +1 -1
  48. package/components/molecules/JAlert.vue.cjs +1 -1
  49. package/components/molecules/JAlert.vue.cjs.map +1 -1
  50. package/components/molecules/JAlert.vue.js +18 -16
  51. package/components/molecules/JAlert.vue.js.map +1 -1
  52. package/components/molecules/JCard.vue.cjs +1 -1
  53. package/components/molecules/JCard.vue.cjs.map +1 -1
  54. package/components/molecules/JCard.vue.js +55 -39
  55. package/components/molecules/JCard.vue.js.map +1 -1
  56. package/components/molecules/JEmptyState.vue.cjs +7 -0
  57. package/components/molecules/JEmptyState.vue.cjs.map +1 -0
  58. package/components/molecules/JEmptyState.vue.js +13 -0
  59. package/components/molecules/JEmptyState.vue.js.map +1 -0
  60. package/components/molecules/JEmptyState.vue2.cjs +2 -0
  61. package/components/molecules/JEmptyState.vue2.cjs.map +1 -0
  62. package/components/molecules/JEmptyState.vue2.js +127 -0
  63. package/components/molecules/JEmptyState.vue2.js.map +1 -0
  64. package/components/molecules/JFormField.vue.cjs +6 -1
  65. package/components/molecules/JFormField.vue.cjs.map +1 -1
  66. package/components/molecules/JFormField.vue.js +10 -264
  67. package/components/molecules/JFormField.vue.js.map +1 -1
  68. package/components/molecules/JFormField.vue2.cjs +2 -0
  69. package/components/molecules/JFormField.vue2.cjs.map +1 -0
  70. package/components/molecules/JFormField.vue2.js +271 -0
  71. package/components/molecules/JFormField.vue2.js.map +1 -0
  72. package/components/molecules/JTabs.vue.cjs +1 -1
  73. package/components/molecules/JTabs.vue.js +1 -1
  74. package/components/molecules/JTabs.vue2.cjs +1 -1
  75. package/components/molecules/JTabs.vue2.cjs.map +1 -1
  76. package/components/molecules/JTabs.vue2.js +44 -50
  77. package/components/molecules/JTabs.vue2.js.map +1 -1
  78. package/components/molecules/JTitlebar.vue.cjs +1 -1
  79. package/components/molecules/JTitlebar.vue.cjs.map +1 -1
  80. package/components/molecules/JTitlebar.vue.js +23 -20
  81. package/components/molecules/JTitlebar.vue.js.map +1 -1
  82. package/components/organisms/JDynamicForm.vue2.cjs +1 -1
  83. package/components/organisms/JDynamicForm.vue2.cjs.map +1 -1
  84. package/components/organisms/JDynamicForm.vue2.js +35 -32
  85. package/components/organisms/JDynamicForm.vue2.js.map +1 -1
  86. package/components/organisms/JDynamicTabs.vue.cjs +1 -1
  87. package/components/organisms/JDynamicTabs.vue.cjs.map +1 -1
  88. package/components/organisms/JDynamicTabs.vue.js +47 -52
  89. package/components/organisms/JDynamicTabs.vue.js.map +1 -1
  90. package/components/organisms/JFilterBar.vue.cjs +6 -1
  91. package/components/organisms/JFilterBar.vue.cjs.map +1 -1
  92. package/components/organisms/JFilterBar.vue.js +10 -137
  93. package/components/organisms/JFilterBar.vue.js.map +1 -1
  94. package/components/organisms/JFilterBar.vue2.cjs +1 -1
  95. package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
  96. package/components/organisms/JFilterBar.vue2.js +143 -2
  97. package/components/organisms/JFilterBar.vue2.js.map +1 -1
  98. package/components/organisms/JFormModal.vue.cjs +1 -1
  99. package/components/organisms/JFormModal.vue.cjs.map +1 -1
  100. package/components/organisms/JFormModal.vue.js +54 -49
  101. package/components/organisms/JFormModal.vue.js.map +1 -1
  102. package/components/organisms/JHeader.vue.cjs +1 -1
  103. package/components/organisms/JHeader.vue.cjs.map +1 -1
  104. package/components/organisms/JHeader.vue.js +191 -190
  105. package/components/organisms/JHeader.vue.js.map +1 -1
  106. package/components/organisms/JModal.vue.cjs +1 -1
  107. package/components/organisms/JModal.vue.cjs.map +1 -1
  108. package/components/organisms/JModal.vue.js +47 -45
  109. package/components/organisms/JModal.vue.js.map +1 -1
  110. package/components/organisms/JPageContainer.vue.cjs +1 -1
  111. package/components/organisms/JPageContainer.vue.cjs.map +1 -1
  112. package/components/organisms/JPageContainer.vue.js +22 -22
  113. package/components/organisms/JPageContainer.vue.js.map +1 -1
  114. package/components/organisms/JSearchPanel.vue2.cjs +1 -1
  115. package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
  116. package/components/organisms/JSearchPanel.vue2.js +34 -32
  117. package/components/organisms/JSearchPanel.vue2.js.map +1 -1
  118. package/components/organisms/JShuttle.vue.cjs +7 -0
  119. package/components/organisms/JShuttle.vue.cjs.map +1 -0
  120. package/components/organisms/JShuttle.vue.js +13 -0
  121. package/components/organisms/JShuttle.vue.js.map +1 -0
  122. package/components/organisms/JShuttle.vue2.cjs +2 -0
  123. package/components/organisms/JShuttle.vue2.cjs.map +1 -0
  124. package/components/organisms/JShuttle.vue2.js +216 -0
  125. package/components/organisms/JShuttle.vue2.js.map +1 -0
  126. package/components/organisms/JSidebar/JSidebar.vue.cjs +2 -0
  127. package/components/organisms/JSidebar/JSidebar.vue.cjs.map +1 -0
  128. package/components/organisms/JSidebar/JSidebar.vue.js +189 -0
  129. package/components/organisms/JSidebar/JSidebar.vue.js.map +1 -0
  130. package/components/{molecules/JFormField.vue3.cjs → organisms/JSidebar/JSidebar.vue2.cjs} +2 -2
  131. package/components/organisms/JSidebar/JSidebar.vue2.cjs.map +1 -0
  132. package/components/organisms/JSidebar/JSidebar.vue2.js +5 -0
  133. package/components/organisms/JSidebar/JSidebar.vue2.js.map +1 -0
  134. package/components/organisms/JSidebar/JSidebarGroup.vue.cjs +2 -0
  135. package/components/organisms/JSidebar/JSidebarGroup.vue.cjs.map +1 -0
  136. package/components/organisms/JSidebar/JSidebarGroup.vue.js +89 -0
  137. package/components/organisms/JSidebar/JSidebarGroup.vue.js.map +1 -0
  138. package/components/organisms/JSidebar/JSidebarGroup.vue2.cjs +2 -0
  139. package/components/organisms/JSidebar/JSidebarGroup.vue2.cjs.map +1 -0
  140. package/components/organisms/JSidebar/JSidebarGroup.vue2.js +5 -0
  141. package/components/organisms/JSidebar/JSidebarGroup.vue2.js.map +1 -0
  142. package/components/organisms/JSidebar/JSidebarItem.vue.cjs +2 -0
  143. package/components/organisms/JSidebar/JSidebarItem.vue.cjs.map +1 -0
  144. package/components/organisms/JSidebar/JSidebarItem.vue.js +79 -0
  145. package/components/organisms/JSidebar/JSidebarItem.vue.js.map +1 -0
  146. package/components/organisms/JSidebar/JSidebarItem.vue2.cjs +2 -0
  147. package/components/organisms/JSidebar/JSidebarItem.vue2.cjs.map +1 -0
  148. package/components/organisms/JSidebar/JSidebarItem.vue2.js +5 -0
  149. package/components/organisms/JSidebar/JSidebarItem.vue2.js.map +1 -0
  150. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs +1 -1
  151. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.cjs.map +1 -1
  152. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js +52 -52
  153. package/components/organisms/JSidebarSimple/JDynamicMenuItem.vue.js.map +1 -1
  154. package/components/shadcn/Card.vue.cjs +1 -1
  155. package/components/shadcn/Card.vue.cjs.map +1 -1
  156. package/components/shadcn/Card.vue.js +1 -1
  157. package/components/shadcn/Card.vue.js.map +1 -1
  158. package/components/shadcn/CardContent.vue.cjs +1 -1
  159. package/components/shadcn/CardContent.vue.cjs.map +1 -1
  160. package/components/shadcn/CardContent.vue.js +4 -4
  161. package/components/shadcn/CardContent.vue.js.map +1 -1
  162. package/components/shadcn/CardHeader.vue.cjs +1 -1
  163. package/components/shadcn/CardHeader.vue.cjs.map +1 -1
  164. package/components/shadcn/CardHeader.vue.js +5 -5
  165. package/components/shadcn/CardHeader.vue.js.map +1 -1
  166. package/components/shadcn/Input.vue.cjs +1 -1
  167. package/components/shadcn/Input.vue.cjs.map +1 -1
  168. package/components/shadcn/Input.vue.js +3 -3
  169. package/components/shadcn/Input.vue.js.map +1 -1
  170. package/components/shadcn/SelectTrigger.vue.cjs +1 -1
  171. package/components/shadcn/SelectTrigger.vue.cjs.map +1 -1
  172. package/components/shadcn/SelectTrigger.vue.js +1 -1
  173. package/components/shadcn/SelectTrigger.vue.js.map +1 -1
  174. package/components/shadcn/TabsContent.vue.cjs +1 -1
  175. package/components/shadcn/TabsContent.vue.cjs.map +1 -1
  176. package/components/shadcn/TabsContent.vue.js +1 -1
  177. package/components/shadcn/TabsContent.vue.js.map +1 -1
  178. package/components/shadcn/TabsList.vue.cjs +1 -1
  179. package/components/shadcn/TabsList.vue.cjs.map +1 -1
  180. package/components/shadcn/TabsList.vue.js +10 -10
  181. package/components/shadcn/TabsList.vue.js.map +1 -1
  182. package/components/shadcn/Textarea.vue.cjs +1 -1
  183. package/components/shadcn/Textarea.vue.cjs.map +1 -1
  184. package/components/shadcn/Textarea.vue.js +1 -1
  185. package/components/shadcn/Textarea.vue.js.map +1 -1
  186. package/components/shadcn/index.cjs +1 -1
  187. package/components/shadcn/index.cjs.map +1 -1
  188. package/components/shadcn/index.js +4 -4
  189. package/components/shadcn/index.js.map +1 -1
  190. package/components/templates/JLayout.vue.cjs.map +1 -1
  191. package/components/templates/JLayout.vue.js.map +1 -1
  192. package/components/templates/JLayoutSimple.vue.cjs +1 -1
  193. package/components/templates/JLayoutSimple.vue.cjs.map +1 -1
  194. package/components/templates/JLayoutSimple.vue.js +36 -30
  195. package/components/templates/JLayoutSimple.vue.js.map +1 -1
  196. package/index.cjs +1 -1
  197. package/index.js +75 -67
  198. package/package.json +1 -1
  199. package/types/index.d.ts +662 -461
  200. package/types/sidebar.types.cjs +2 -0
  201. package/types/sidebar.types.cjs.map +1 -0
  202. package/types/sidebar.types.js +5 -0
  203. package/types/sidebar.types.js.map +1 -0
  204. package/assets/jwms-portal-frontend-CwxPfHfa.css +0 -1
  205. package/components/molecules/JFormField.vue3.cjs.map +0 -1
  206. package/components/molecules/JFormField.vue3.js +0 -6
  207. package/components/molecules/JFormField.vue3.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"JDynamicTabs.vue.js","sources":["../../../../src/components/organisms/JDynamicTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from 'vue'\r\nimport JTabs from '@/components/molecules/JTabs.vue'\r\nimport type { \r\n JDynamicTabsProps, \r\n JDynamicTabsEmits, \r\n JDynamicTabsMethods,\r\n DynamicTab \r\n} from '@/types/dynamic-tabs.types'\r\n\r\n/**\r\n * JDynamicTabs - 동적 탭 컴포넌트 (organisms)\r\n * Dynamic Tabs Component\r\n * \r\n * @description\r\n * 사이드 메뉴 클릭으로 탭을 동적으로 추가/제거할 수 있는 탭 컴포넌트입니다.\r\n * \r\n * Features:\r\n * - 탭 동적 추가/제거\r\n * - 중복 탭 방지 (이미 있으면 활성화)\r\n * - 닫기 버튼으로 탭 제거\r\n * - 활성 탭 자동 재설정\r\n * - 최대 탭 개수 제한\r\n * \r\n * @example\r\n * const tabsRef = ref()\r\n * \r\n * const handleMenuClick = (menuItem) => {\r\n * tabsRef.value?.addTab({\r\n * id: menuItem.id,\r\n * label: menuItem.label,\r\n * component: menuItem.component,\r\n * closable: true\r\n * })\r\n * }\r\n */\r\n\r\nconst props = withDefaults(defineProps<JDynamicTabsProps>(), {\r\n initialTabs: () => [],\r\n maxTabs: 0, // 0 = unlimited\r\n emptyMessage: '탭을 추가해주세요.',\r\n styletype: 'default',\r\n})\r\n\r\nconst emit = defineEmits<JDynamicTabsEmits>()\r\n\r\n/**\r\n * 탭 목록 (내부 상태)\r\n * Tabs list (internal state)\r\n */\r\nconst tabs = ref<DynamicTab[]>(Array.isArray(props.initialTabs) ? [...props.initialTabs] : [])\r\n\r\n/**\r\n * 현재 활성화된 탭 ID (내부 상태)\r\n * Current active tab ID (internal state)\r\n */\r\nconst activeTabId = ref<string>(\r\n props.defaultActiveId || (Array.isArray(props.initialTabs) && props.initialTabs.length > 0 ? props.initialTabs[0]?.id : '') || ''\r\n)\r\n\r\n/**\r\n * initialTabs가 변경되면 내부 상태 업데이트\r\n * Update internal state when initialTabs changes\r\n */\r\nwatch(() => props.initialTabs, (newTabs) => {\r\n if (!newTabs) {\r\n tabs.value = []\r\n return\r\n }\r\n \r\n if (Array.isArray(newTabs) && newTabs.length > 0) {\r\n tabs.value = [...newTabs]\r\n if (!activeTabId.value && newTabs[0]) {\r\n activeTabId.value = newTabs[0].id\r\n }\r\n } else if (Array.isArray(newTabs)) {\r\n // 빈 배열인 경우\r\n tabs.value = []\r\n } else {\r\n // 배열이 아닌 경우\r\n tabs.value = []\r\n }\r\n}, { immediate: true })\r\n\r\n/**\r\n * 탭 추가\r\n * Add tab (if exists, activate it; otherwise, add to array and activate)\r\n * \r\n * @param tab - 추가할 탭 정보\r\n */\r\nconst addTab = (tab: DynamicTab) => {\r\n // tabs.value가 배열이 아니면 초기화\r\n if (!Array.isArray(tabs.value)) {\r\n tabs.value = []\r\n }\r\n \r\n // 이미 존재하는 탭인지 확인\r\n const existingTab = tabs.value.find(t => t.id === tab.id)\r\n \r\n if (existingTab) {\r\n // 이미 있으면 해당 탭 활성화\r\n activateTab(tab.id)\r\n return\r\n }\r\n \r\n // 최대 탭 개수 체크\r\n if (props.maxTabs > 0 && tabs.value.length >= props.maxTabs) {\r\n console.warn(`최대 ${props.maxTabs}개의 탭만 열 수 있습니다.`)\r\n return\r\n }\r\n \r\n // 새 탭 추가\r\n tabs.value.push({\r\n ...tab,\r\n closable: tab.closable !== false, // 기본값 true\r\n })\r\n \r\n // 새로 추가된 탭 활성화\r\n activateTab(tab.id)\r\n \r\n // 이벤트 발생\r\n emit('tabAdd', tab)\r\n}\r\n\r\n/**\r\n * 탭 닫기\r\n * Close tab (remove from array and reset active tab)\r\n * \r\n * @param id - 닫을 탭 ID\r\n */\r\nconst closeTab = (id: string) => {\r\n if (!Array.isArray(tabs.value)) {\r\n return\r\n }\r\n \r\n const tabIndex = tabs.value.findIndex(t => t.id === id)\r\n \r\n if (tabIndex === -1) return\r\n \r\n // 탭 제거\r\n tabs.value.splice(tabIndex, 1)\r\n \r\n // 활성 탭 재설정\r\n if (activeTabId.value === id) {\r\n if (tabs.value.length > 0) {\r\n // 이전 탭이 있으면 이전 탭 활성화, 없으면 다음 탭 활성화\r\n const newIndex = Math.min(tabIndex, tabs.value.length - 1)\r\n activeTabId.value = tabs.value[newIndex]?.id || ''\r\n } else {\r\n activeTabId.value = ''\r\n }\r\n }\r\n \r\n // 이벤트 발생\r\n emit('tabClose', id)\r\n}\r\n\r\n/**\r\n * 탭 활성화\r\n * Activate tab\r\n * \r\n * @param id - 활성화할 탭 ID\r\n */\r\nconst activateTab = (id: string) => {\r\n if (!Array.isArray(tabs.value)) {\r\n return\r\n }\r\n const tab = tabs.value.find(t => t.id === id)\r\n if (tab) {\r\n activeTabId.value = id\r\n }\r\n}\r\n\r\n/**\r\n * 특정 탭 찾기\r\n * Find specific tab\r\n * \r\n * @param id - 찾을 탭 ID\r\n */\r\nconst findTab = (id: string): DynamicTab | undefined => {\r\n if (!Array.isArray(tabs.value)) {\r\n return undefined\r\n }\r\n return tabs.value.find(t => t.id === id)\r\n}\r\n\r\n/**\r\n * 모든 탭 닫기 (closable이 true인 탭만)\r\n * Close all tabs (only closable tabs)\r\n */\r\nconst closeAllTabs = () => {\r\n if (!Array.isArray(tabs.value)) {\r\n return\r\n }\r\n const closableTabs = tabs.value.filter(t => t.closable)\r\n closableTabs.forEach(tab => closeTab(tab.id))\r\n}\r\n\r\n/**\r\n * 탭 변경 핸들러\r\n * Tab change handler\r\n */\r\nconst handleTabChange = (id: string) => {\r\n activeTabId.value = id\r\n emit('tabChange', id)\r\n}\r\n\r\n/**\r\n * 탭 닫기 핸들러\r\n * Tab close handler\r\n */\r\nconst handleTabClose = (id: string) => {\r\n closeTab(id)\r\n}\r\n\r\n/**\r\n * 외부에서 호출 가능한 메서드 노출\r\n * Expose methods for external use\r\n */\r\ndefineExpose<JDynamicTabsMethods>({\r\n addTab,\r\n closeTab,\r\n activateTab,\r\n findTab,\r\n closeAllTabs,\r\n})\r\n\r\n/**\r\n * 안전한 tabs 배열\r\n * Safe tabs array\r\n */\r\nconst safeTabs = computed(() => {\r\n return Array.isArray(tabs.value) ? tabs.value : []\r\n})\r\n\r\n/**\r\n * 탭이 있는지 여부\r\n * Whether there are tabs\r\n */\r\nconst hasTabs = computed(() => safeTabs.value.length > 0)\r\n\r\n/**\r\n * 루트 클래스\r\n * Root classes\r\n */\r\nconst rootClasses = computed(() => {\r\n const classes = ['w-full', 'h-full']\r\n \r\n if (props.className) {\r\n classes.push(props.className)\r\n }\r\n \r\n return classes.join(' ')\r\n})\r\n\r\n/**\r\n * 콘텐츠 클래스\r\n * Content classes\r\n */\r\nconst contentClasses = computed(() => {\r\n const classes = []\r\n \r\n if (props.contentClassName) {\r\n classes.push(props.contentClassName)\r\n }\r\n \r\n return classes.join(' ')\r\n})\r\n</script>\r\n\r\n<template>\r\n <div :class=\"rootClasses\">\r\n <!-- 탭이 있을 경우 / When there are tabs -->\r\n <JTabs\r\n v-if=\"hasTabs\"\r\n :tabs=\"safeTabs\"\r\n :active-tab-id=\"activeTabId\"\r\n :styletype=\"props.styletype\"\r\n :class=\"contentClasses\"\r\n @tab-change=\"handleTabChange\"\r\n @tab-close=\"handleTabClose\"\r\n >\r\n <!-- 각 탭 콘텐츠 슬롯 전달 / Forward slots for each tab content -->\r\n <template\r\n v-for=\"tab in safeTabs\"\r\n :key=\"tab.id\"\r\n #[`content-${tab.id}`]=\"slotProps\"\r\n >\r\n <slot :name=\"`content-${tab.id}`\" v-bind=\"slotProps\" />\r\n </template>\r\n </JTabs>\r\n \r\n <!-- 탭이 없을 경우 / When there are no tabs -->\r\n <div\r\n v-else\r\n class=\"flex items-center justify-center h-full w-full\"\r\n >\r\n <div class=\"text-center\">\r\n <p class=\"text-muted-foreground text-lg\">{{ emptyMessage }}</p>\r\n <p class=\"text-muted-foreground/60 text-sm mt-2\">\r\n 메뉴를 클릭하여 탭을 추가하세요.\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","tabs","ref","activeTabId","watch","newTabs","addTab","tab","t","activateTab","closeTab","id","tabIndex","newIndex","findTab","closeAllTabs","handleTabChange","handleTabClose","__expose","safeTabs","computed","hasTabs","rootClasses","classes","contentClasses","_createElementBlock","_createBlock","JTabs","_renderList","_withCtx","slotProps","_renderSlot","_ctx","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","_cache"],"mappings":"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"}
1
+ {"version":3,"file":"JDynamicTabs.vue.js","sources":["../../../../src/components/organisms/JDynamicTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport JTabs from '@/components/molecules/JTabs.vue'\nimport { cn } from '@/lib/utils'\nimport type {\n JDynamicTabsProps, \n JDynamicTabsEmits, \n JDynamicTabsMethods,\n DynamicTab \n} from '@/types/dynamic-tabs.types'\n\n/**\n * JDynamicTabs - 동적 탭 컴포넌트 (organisms)\n * Dynamic Tabs Component\n * \n * @description\n * 사이드 메뉴 클릭으로 탭을 동적으로 추가/제거할 수 있는 탭 컴포넌트입니다.\n * \n * Features:\n * - 탭 동적 추가/제거\n * - 중복 탭 방지 (이미 있으면 활성화)\n * - 닫기 버튼으로 탭 제거\n * - 활성 탭 자동 재설정\n * - 최대 탭 개수 제한\n * \n * @example\n * const tabsRef = ref()\n * \n * const handleMenuClick = (menuItem) => {\n * tabsRef.value?.addTab({\n * id: menuItem.id,\n * label: menuItem.label,\n * component: menuItem.component,\n * closable: true\n * })\n * }\n */\n\nconst props = withDefaults(defineProps<JDynamicTabsProps>(), {\n initialTabs: () => [],\n maxTabs: 0, // 0 = unlimited\n emptyMessage: '탭을 추가해주세요.',\n styletype: 'default',\n})\n\nconst emit = defineEmits<JDynamicTabsEmits>()\n\n/**\n * 탭 목록 (내부 상태)\n * Tabs list (internal state)\n */\nconst tabs = ref<DynamicTab[]>(Array.isArray(props.initialTabs) ? [...props.initialTabs] : [])\n\n/**\n * 현재 활성화된 탭 ID (내부 상태)\n * Current active tab ID (internal state)\n */\nconst activeTabId = ref<string>(\n props.defaultActiveId || (Array.isArray(props.initialTabs) && props.initialTabs.length > 0 ? props.initialTabs[0]?.id : '') || ''\n)\n\n/**\n * initialTabs가 변경되면 내부 상태 업데이트\n * Update internal state when initialTabs changes\n */\nwatch(() => props.initialTabs, (newTabs) => {\n if (!newTabs) {\n tabs.value = []\n return\n }\n \n if (Array.isArray(newTabs) && newTabs.length > 0) {\n tabs.value = [...newTabs]\n if (!activeTabId.value && newTabs[0]) {\n activeTabId.value = newTabs[0].id\n }\n } else if (Array.isArray(newTabs)) {\n // 빈 배열인 경우\n tabs.value = []\n } else {\n // 배열이 아닌 경우\n tabs.value = []\n }\n}, { immediate: true })\n\n/**\n * 탭 추가\n * Add tab (if exists, activate it; otherwise, add to array and activate)\n * \n * @param tab - 추가할 탭 정보\n */\nconst addTab = (tab: DynamicTab) => {\n // tabs.value가 배열이 아니면 초기화\n if (!Array.isArray(tabs.value)) {\n tabs.value = []\n }\n \n // 이미 존재하는 탭인지 확인\n const existingTab = tabs.value.find(t => t.id === tab.id)\n \n if (existingTab) {\n // 이미 있으면 해당 탭 활성화\n activateTab(tab.id)\n return\n }\n \n // 최대 탭 개수 체크\n if (props.maxTabs > 0 && tabs.value.length >= props.maxTabs) {\n console.warn(`최대 ${props.maxTabs}개의 탭만 열 수 있습니다.`)\n return\n }\n \n // 새 탭 추가\n tabs.value.push({\n ...tab,\n closable: tab.closable !== false, // 기본값 true\n })\n \n // 새로 추가된 탭 활성화\n activateTab(tab.id)\n \n // 이벤트 발생\n emit('tabAdd', tab)\n}\n\n/**\n * 탭 닫기\n * Close tab (remove from array and reset active tab)\n * \n * @param id - 닫을 탭 ID\n */\nconst closeTab = (id: string) => {\n if (!Array.isArray(tabs.value)) {\n return\n }\n \n const tabIndex = tabs.value.findIndex(t => t.id === id)\n \n if (tabIndex === -1) return\n \n // 탭 제거\n tabs.value.splice(tabIndex, 1)\n \n // 활성 탭 재설정\n if (activeTabId.value === id) {\n if (tabs.value.length > 0) {\n // 이전 탭이 있으면 이전 탭 활성화, 없으면 다음 탭 활성화\n const newIndex = Math.min(tabIndex, tabs.value.length - 1)\n activeTabId.value = tabs.value[newIndex]?.id || ''\n } else {\n activeTabId.value = ''\n }\n }\n \n // 이벤트 발생\n emit('tabClose', id)\n}\n\n/**\n * 탭 활성화\n * Activate tab\n * \n * @param id - 활성화할 탭 ID\n */\nconst activateTab = (id: string) => {\n if (!Array.isArray(tabs.value)) {\n return\n }\n const tab = tabs.value.find(t => t.id === id)\n if (tab) {\n activeTabId.value = id\n }\n}\n\n/**\n * 특정 탭 찾기\n * Find specific tab\n * \n * @param id - 찾을 탭 ID\n */\nconst findTab = (id: string): DynamicTab | undefined => {\n if (!Array.isArray(tabs.value)) {\n return undefined\n }\n return tabs.value.find(t => t.id === id)\n}\n\n/**\n * 모든 탭 닫기 (closable이 true인 탭만)\n * Close all tabs (only closable tabs)\n */\nconst closeAllTabs = () => {\n if (!Array.isArray(tabs.value)) {\n return\n }\n const closableTabs = tabs.value.filter(t => t.closable)\n closableTabs.forEach(tab => closeTab(tab.id))\n}\n\n/**\n * 탭 변경 핸들러\n * Tab change handler\n */\nconst handleTabChange = (id: string) => {\n activeTabId.value = id\n emit('tabChange', id)\n}\n\n/**\n * 탭 닫기 핸들러\n * Tab close handler\n */\nconst handleTabClose = (id: string) => {\n closeTab(id)\n}\n\n/**\n * 외부에서 호출 가능한 메서드 노출\n * Expose methods for external use\n */\ndefineExpose<JDynamicTabsMethods>({\n addTab,\n closeTab,\n activateTab,\n findTab,\n closeAllTabs,\n})\n\n/**\n * 안전한 tabs 배열\n * Safe tabs array\n */\nconst safeTabs = computed(() => {\n return Array.isArray(tabs.value) ? tabs.value : []\n})\n\n/**\n * 탭이 있는지 여부\n * Whether there are tabs\n */\nconst hasTabs = computed(() => safeTabs.value.length > 0)\n\n/**\n * 루트 클래스\n * Root classes\n */\nconst rootClasses = computed(() => {\n return cn('w-full h-full', props.class)\n})\n\n/**\n * 콘텐츠 클래스\n * Content classes\n */\nconst contentClasses = computed(() => {\n return props.contentClass || ''\n})\n</script>\n\n<template>\n <div :class=\"rootClasses\">\n <!-- 탭이 있을 경우 / When there are tabs -->\n <JTabs\n v-if=\"hasTabs\"\n :tabs=\"safeTabs\"\n :active-tab-id=\"activeTabId\"\n :styletype=\"props.styletype\"\n :class=\"contentClasses\"\n @tab-change=\"handleTabChange\"\n @tab-close=\"handleTabClose\"\n >\n <!-- 각 탭 콘텐츠 슬롯 전달 / Forward slots for each tab content -->\n <template\n v-for=\"tab in safeTabs\"\n :key=\"tab.id\"\n #[`content-${tab.id}`]=\"slotProps\"\n >\n <slot :name=\"`content-${tab.id}`\" v-bind=\"slotProps\" />\n </template>\n </JTabs>\n \n <!-- 탭이 없을 경우 / When there are no tabs -->\n <div\n v-else\n class=\"flex items-center justify-center h-full w-full\"\n >\n <div class=\"text-center\">\n <p class=\"text-muted-foreground text-lg\">{{ emptyMessage }}</p>\n <p class=\"text-muted-foreground/60 text-sm mt-2\">\n 메뉴를 클릭하여 탭을 추가하세요.\n </p>\n </div>\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","tabs","ref","activeTabId","watch","newTabs","addTab","tab","t","activateTab","closeTab","id","tabIndex","newIndex","findTab","closeAllTabs","handleTabChange","handleTabClose","__expose","safeTabs","computed","hasTabs","rootClasses","cn","contentClasses","_createElementBlock","_createBlock","JTabs","_renderList","_withCtx","slotProps","_renderSlot","_ctx","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_toDisplayString","_cache"],"mappings":"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"}
@@ -1,2 +1,7 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),d=require("lucide-vue-next"),_=require("../atoms/JBadge.vue.cjs"),f=require("../atoms/JButton.vue.cjs"),B=require("../atoms/JLabel.vue.cjs"),k={class:"w-full rounded-lg border bg-card text-card-foreground"},v={class:"flex items-center justify-between px-3 py-1.5"},x={class:"flex items-center gap-2"},b={key:2,class:"flex items-center gap-1 flex-wrap"},w={class:"text-muted-foreground"},C=["onClick"],N={class:"flex items-center gap-2"},E={class:"px-3 pb-3"},S=e.defineComponent({__name:"JFilterBar",props:{title:{},collapsed:{type:Boolean,default:!0},collapsible:{type:Boolean,default:!0},filterValues:{default:()=>({})},filterDisplay:{default:()=>({})},showResetButton:{type:Boolean,default:!1},showSearchButton:{type:Boolean,default:!1},resetButtonText:{default:"초기화"},searchButtonText:{default:"조회"}},emits:["update:collapsed","update:filterValues","search","reset"],setup(r,{emit:p}){const s=r,n=p,c=e.computed(()=>s.collapsible?!s.collapsed:!0);function m(t){return!!(t==null||typeof t=="string"&&t.trim()===""||Array.isArray(t)&&t.length===0)}const u=e.computed(()=>{const t=[];for(const[l,o]of Object.entries(s.filterDisplay)){const a=s.filterValues[l];if(m(a))continue;const i=o.displayValue?o.displayValue(a):String(a);i.trim()!==""&&t.push({key:l,label:o.label,value:i})}return t});function y(){n("update:collapsed",!s.collapsed)}function h(){const t={};for(const l of Object.keys(s.filterValues)){const o=s.filterValues[l];typeof o=="string"?t[l]="":Array.isArray(o)?t[l]=[]:t[l]=null}n("update:filterValues",t),n("reset")}function g(){n("search")}function V(t){const l={...s.filterValues},o=l[t];typeof o=="string"?l[t]="":Array.isArray(o)?l[t]=[]:l[t]=null,n("update:filterValues",l)}return(t,l)=>(e.openBlock(),e.createElementBlock("div",k,[e.createElementVNode("div",v,[e.createElementVNode("div",x,[r.collapsible?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",onClick:y},[e.createVNode(e.unref(d.ChevronDown),{class:e.normalizeClass(["h-3.5 w-3.5 transition-transform",c.value?"rotate-0":"-rotate-90"])},null,8,["class"])])):e.createCommentVNode("",!0),r.title?(e.openBlock(),e.createBlock(B.default,{key:1,text:r.title,class:"text-sm font-semibold text-foreground"},null,8,["text"])):e.createCommentVNode("",!0),u.value.length>0?(e.openBlock(),e.createElementBlock("div",b,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(u.value,o=>(e.openBlock(),e.createBlock(_.default,{key:o.key,variant:"secondary",size:"sm",class:"flex items-center gap-1 cursor-default"},{default:e.withCtx(()=>[e.createElementVNode("span",w,e.toDisplayString(o.label)+":",1),e.createElementVNode("span",null,e.toDisplayString(o.value),1),e.createElementVNode("button",{type:"button",class:"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors",onClick:e.withModifiers(a=>V(o.key),["stop"])},[e.createVNode(e.unref(d.X),{class:"h-3 w-3"})],8,C)]),_:2},1024))),128))])):e.createCommentVNode("",!0)]),e.createElementVNode("div",N,[e.renderSlot(t.$slots,"actions"),r.showResetButton?(e.openBlock(),e.createBlock(f.default,{key:0,variant:"secondary",size:"sm",onClick:h},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.resetButtonText),1)]),_:1})):e.createCommentVNode("",!0),r.showSearchButton?(e.openBlock(),e.createBlock(f.default,{key:1,styletype:"primary",size:"sm",onClick:g},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.searchButtonText),1)]),_:1})):e.createCommentVNode("",!0)])]),e.withDirectives(e.createElementVNode("div",E,[e.renderSlot(t.$slots,"filters")],512),[[e.vShow,c.value]])]))}});exports.default=S;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JFilterBar.vue2.cjs");;/* empty css */const t = (t_comp, t_opts) => {
2
+ const t_merged = t_comp.__vccOpts || t_comp;
3
+ for (const [t_key, t_val] of t_opts)
4
+ t_merged[t_key] = t_val;
5
+ return t_merged;
6
+ };,r=t(e.default,[["__scopeId","data-v-b054109f"]]);exports.default=r;
2
7
  //# sourceMappingURL=JFilterBar.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue.cjs","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\r\n <div class=\"w-full rounded-lg border bg-card text-card-foreground\">\r\n <!-- Row 1: toolbar -->\r\n <div class=\"flex items-center justify-between px-3 py-1.5\">\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n v-if=\"collapsible\"\r\n type=\"button\"\r\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <ChevronDown\r\n :class=\"[\r\n 'h-3.5 w-3.5 transition-transform',\r\n isExpanded ? 'rotate-0' : '-rotate-90',\r\n ]\"\r\n />\r\n </button>\r\n <!-- 타이틀 -->\r\n <JLabel\r\n v-if=\"title\"\r\n :text=\"title\"\r\n class=\"text-sm font-semibold text-foreground\"\r\n />\r\n <!-- 선택된 필터 뱃지 표시 -->\r\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\r\n <JBadge\r\n v-for=\"filter in activeFilters\"\r\n :key=\"filter.key\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n class=\"flex items-center gap-1 cursor-default\"\r\n >\r\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\r\n <span>{{ filter.value }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\r\n @click.stop=\"removeFilter(filter.key)\"\r\n >\r\n <X class=\"h-3 w-3\" />\r\n </button>\r\n </JBadge>\r\n </div>\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <slot name=\"actions\" />\r\n <JButton\r\n v-if=\"showResetButton\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n @click=\"handleReset\"\r\n >\r\n {{ resetButtonText }}\r\n </JButton>\r\n <JButton\r\n v-if=\"showSearchButton\"\r\n styletype=\"primary\"\r\n size=\"sm\"\r\n @click=\"handleSearch\"\r\n >\r\n {{ searchButtonText }}\r\n </JButton>\r\n </div>\r\n </div>\r\n\r\n <!-- Row 2: filters -->\r\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\r\n <slot name=\"filters\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ChevronDown, X } from 'lucide-vue-next'\r\nimport JBadge from '@/components/atoms/JBadge.vue'\r\nimport JButton from '@/components/atoms/JButton.vue'\r\nimport JLabel from '@/components/atoms/JLabel.vue'\r\n\r\n/** 활성 필터 아이템 타입 */\r\nexport interface ActiveFilterItem {\r\n /** 필터 식별 키 */\r\n key: string\r\n /** 표시할 라벨 (필터명) */\r\n label: string\r\n /** 표시할 값 */\r\n value: string\r\n}\r\n\r\n/** 필터 설정 타입 */\r\nexport interface FilterDisplayItem {\r\n /** 표시할 라벨 */\r\n label: string\r\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\r\n displayValue?: (value: unknown) => string\r\n}\r\n\r\nexport interface JFilterBarProps {\r\n /** 필터바 타이틀 */\r\n title?: string\r\n /** 필터 접힘 상태 (v-model 지원) */\r\n collapsed?: boolean\r\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\r\n collapsible?: boolean\r\n /** 필터 값 객체 (v-model:filterValues 지원) */\r\n filterValues?: Record<string, unknown>\r\n /** 필터 표시 설정 (label, displayValue 등) */\r\n filterDisplay?: Record<string, FilterDisplayItem>\r\n /** 초기화 버튼 표시 여부 */\r\n showResetButton?: boolean\r\n /** 조회 버튼 표시 여부 */\r\n showSearchButton?: boolean\r\n /** 초기화 버튼 텍스트 */\r\n resetButtonText?: string\r\n /** 조회 버튼 텍스트 */\r\n searchButtonText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\r\n collapsed: true,\r\n collapsible: true,\r\n filterValues: () => ({}),\r\n filterDisplay: () => ({}),\r\n showResetButton: false,\r\n showSearchButton: false,\r\n resetButtonText: '초기화',\r\n searchButtonText: '조회',\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'update:filterValues': [value: Record<string, unknown>]\r\n /** 조회 버튼 클릭 */\r\n search: []\r\n /** 초기화 버튼 클릭 */\r\n reset: []\r\n}>()\r\n\r\nconst isExpanded = computed(() => {\r\n if (!props.collapsible) return true\r\n return !props.collapsed\r\n})\r\n\r\n/** 값이 비어있는지 확인 */\r\nfunction isEmpty(value: unknown): boolean {\r\n if (value === null || value === undefined) return true\r\n if (typeof value === 'string' && value.trim() === '') return true\r\n if (Array.isArray(value) && value.length === 0) return true\r\n return false\r\n}\r\n\r\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\r\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\r\n const filters: ActiveFilterItem[] = []\r\n\r\n for (const [key, config] of Object.entries(props.filterDisplay)) {\r\n const value = props.filterValues[key]\r\n if (isEmpty(value)) continue\r\n\r\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\r\n if (displayValue.trim() === '') continue\r\n\r\n filters.push({\r\n key,\r\n label: config.label,\r\n value: displayValue,\r\n })\r\n }\r\n\r\n return filters\r\n})\r\n\r\nfunction toggleCollapsed() {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n\r\nfunction handleReset() {\r\n // filterValues의 모든 값을 초기화\r\n const resetValues: Record<string, unknown> = {}\r\n for (const key of Object.keys(props.filterValues)) {\r\n const currentValue = props.filterValues[key]\r\n if (typeof currentValue === 'string') {\r\n resetValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n resetValues[key] = []\r\n } else {\r\n resetValues[key] = null\r\n }\r\n }\r\n emit('update:filterValues', resetValues)\r\n emit('reset')\r\n}\r\n\r\nfunction handleSearch() {\r\n emit('search')\r\n}\r\n\r\nfunction removeFilter(key: string) {\r\n // filterValues 업데이트 (해당 키 값을 초기화)\r\n const newValues = { ...props.filterValues }\r\n const currentValue = newValues[key]\r\n\r\n // 타입에 따라 적절한 초기값으로 설정\r\n if (typeof currentValue === 'string') {\r\n newValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n newValues[key] = []\r\n } else {\r\n newValues[key] = null\r\n }\r\n\r\n emit('update:filterValues', newValues)\r\n}\r\n</script>\r\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_unref","ChevronDown","_normalizeClass","_createBlock","JLabel","_hoisted_4","_Fragment","_renderList","filter","JBadge","_hoisted_5","_toDisplayString","_withModifiers","$event","X","_hoisted_7","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_8"],"mappings":"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"}
1
+ {"version":3,"file":"JFilterBar.vue.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,140 +1,13 @@
1
- import { defineComponent as $, computed as g, createElementBlock as u, openBlock as o, createElementVNode as n, withDirectives as D, createCommentVNode as i, createBlock as f, createVNode as x, unref as v, normalizeClass as T, Fragment as j, renderList as z, withCtx as p, toDisplayString as d, withModifiers as E, renderSlot as b, createTextVNode as V, vShow as F } from "vue";
2
- import { ChevronDown as N, X as R } from "lucide-vue-next";
3
- import O from "../atoms/JBadge.vue.js";
4
- import k from "../atoms/JButton.vue.js";
5
- import J from "../atoms/JLabel.vue.js";
6
- const L = { class: "w-full rounded-lg border bg-card text-card-foreground" }, M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { class: "flex items-center gap-2" }, q = {
7
- key: 2,
8
- class: "flex items-center gap-1 flex-wrap"
9
- }, G = { class: "text-muted-foreground" }, H = ["onClick"], I = { class: "flex items-center gap-2" }, K = { class: "px-3 pb-3" }, Z = /* @__PURE__ */ $({
10
- __name: "JFilterBar",
11
- props: {
12
- title: {},
13
- collapsed: { type: Boolean, default: !0 },
14
- collapsible: { type: Boolean, default: !0 },
15
- filterValues: { default: () => ({}) },
16
- filterDisplay: { default: () => ({}) },
17
- showResetButton: { type: Boolean, default: !1 },
18
- showSearchButton: { type: Boolean, default: !1 },
19
- resetButtonText: { default: "초기화" },
20
- searchButtonText: { default: "조회" }
21
- },
22
- emits: ["update:collapsed", "update:filterValues", "search", "reset"],
23
- setup(l, { emit: w }) {
24
- const r = l, a = w, m = g(() => r.collapsible ? !r.collapsed : !0);
25
- function B(e) {
26
- return !!(e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0);
27
- }
28
- const y = g(() => {
29
- const e = [];
30
- for (const [t, s] of Object.entries(r.filterDisplay)) {
31
- const c = r.filterValues[t];
32
- if (B(c)) continue;
33
- const h = s.displayValue ? s.displayValue(c) : String(c);
34
- h.trim() !== "" && e.push({
35
- key: t,
36
- label: s.label,
37
- value: h
38
- });
39
- }
40
- return e;
41
- });
42
- function _() {
43
- a("update:collapsed", !r.collapsed);
44
- }
45
- function C() {
46
- const e = {};
47
- for (const t of Object.keys(r.filterValues)) {
48
- const s = r.filterValues[t];
49
- typeof s == "string" ? e[t] = "" : Array.isArray(s) ? e[t] = [] : e[t] = null;
50
- }
51
- a("update:filterValues", e), a("reset");
52
- }
53
- function S() {
54
- a("search");
55
- }
56
- function A(e) {
57
- const t = { ...r.filterValues }, s = t[e];
58
- typeof s == "string" ? t[e] = "" : Array.isArray(s) ? t[e] = [] : t[e] = null, a("update:filterValues", t);
59
- }
60
- return (e, t) => (o(), u("div", L, [
61
- n("div", M, [
62
- n("div", X, [
63
- l.collapsible ? (o(), u("button", {
64
- key: 0,
65
- type: "button",
66
- class: "flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",
67
- onClick: _
68
- }, [
69
- x(v(N), {
70
- class: T([
71
- "h-3.5 w-3.5 transition-transform",
72
- m.value ? "rotate-0" : "-rotate-90"
73
- ])
74
- }, null, 8, ["class"])
75
- ])) : i("", !0),
76
- l.title ? (o(), f(J, {
77
- key: 1,
78
- text: l.title,
79
- class: "text-sm font-semibold text-foreground"
80
- }, null, 8, ["text"])) : i("", !0),
81
- y.value.length > 0 ? (o(), u("div", q, [
82
- (o(!0), u(j, null, z(y.value, (s) => (o(), f(O, {
83
- key: s.key,
84
- variant: "secondary",
85
- size: "sm",
86
- class: "flex items-center gap-1 cursor-default"
87
- }, {
88
- default: p(() => [
89
- n("span", G, d(s.label) + ":", 1),
90
- n("span", null, d(s.value), 1),
91
- n("button", {
92
- type: "button",
93
- class: "ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors",
94
- onClick: E((c) => A(s.key), ["stop"])
95
- }, [
96
- x(v(R), { class: "h-3 w-3" })
97
- ], 8, H)
98
- ]),
99
- _: 2
100
- }, 1024))), 128))
101
- ])) : i("", !0)
102
- ]),
103
- n("div", I, [
104
- b(e.$slots, "actions"),
105
- l.showResetButton ? (o(), f(k, {
106
- key: 0,
107
- variant: "secondary",
108
- size: "sm",
109
- onClick: C
110
- }, {
111
- default: p(() => [
112
- V(d(l.resetButtonText), 1)
113
- ]),
114
- _: 1
115
- })) : i("", !0),
116
- l.showSearchButton ? (o(), f(k, {
117
- key: 1,
118
- styletype: "primary",
119
- size: "sm",
120
- onClick: S
121
- }, {
122
- default: p(() => [
123
- V(d(l.searchButtonText), 1)
124
- ]),
125
- _: 1
126
- })) : i("", !0)
127
- ])
128
- ]),
129
- D(n("div", K, [
130
- b(e.$slots, "filters")
131
- ], 512), [
132
- [F, m.value]
133
- ])
134
- ]));
135
- }
136
- });
1
+ import o from "./JFilterBar.vue2.js";
2
+ /* empty css */
3
+ const r = (r_comp, r_opts) => {
4
+ const r_merged = r_comp.__vccOpts || r_comp;
5
+ for (const [r_key, r_val] of r_opts)
6
+ r_merged[r_key] = r_val;
7
+ return r_merged;
8
+ };
9
+ const m = /* @__PURE__ */ r(o, [["__scopeId", "data-v-b054109f"]]);
137
10
  export {
138
- Z as default
11
+ m as default
139
12
  };
140
13
  //# sourceMappingURL=JFilterBar.vue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\r\n <div class=\"w-full rounded-lg border bg-card text-card-foreground\">\r\n <!-- Row 1: toolbar -->\r\n <div class=\"flex items-center justify-between px-3 py-1.5\">\r\n <div class=\"flex items-center gap-2\">\r\n <button\r\n v-if=\"collapsible\"\r\n type=\"button\"\r\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <ChevronDown\r\n :class=\"[\r\n 'h-3.5 w-3.5 transition-transform',\r\n isExpanded ? 'rotate-0' : '-rotate-90',\r\n ]\"\r\n />\r\n </button>\r\n <!-- 타이틀 -->\r\n <JLabel\r\n v-if=\"title\"\r\n :text=\"title\"\r\n class=\"text-sm font-semibold text-foreground\"\r\n />\r\n <!-- 선택된 필터 뱃지 표시 -->\r\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\r\n <JBadge\r\n v-for=\"filter in activeFilters\"\r\n :key=\"filter.key\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n class=\"flex items-center gap-1 cursor-default\"\r\n >\r\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\r\n <span>{{ filter.value }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\r\n @click.stop=\"removeFilter(filter.key)\"\r\n >\r\n <X class=\"h-3 w-3\" />\r\n </button>\r\n </JBadge>\r\n </div>\r\n </div>\r\n <div class=\"flex items-center gap-2\">\r\n <slot name=\"actions\" />\r\n <JButton\r\n v-if=\"showResetButton\"\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n @click=\"handleReset\"\r\n >\r\n {{ resetButtonText }}\r\n </JButton>\r\n <JButton\r\n v-if=\"showSearchButton\"\r\n styletype=\"primary\"\r\n size=\"sm\"\r\n @click=\"handleSearch\"\r\n >\r\n {{ searchButtonText }}\r\n </JButton>\r\n </div>\r\n </div>\r\n\r\n <!-- Row 2: filters -->\r\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\r\n <slot name=\"filters\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { ChevronDown, X } from 'lucide-vue-next'\r\nimport JBadge from '@/components/atoms/JBadge.vue'\r\nimport JButton from '@/components/atoms/JButton.vue'\r\nimport JLabel from '@/components/atoms/JLabel.vue'\r\n\r\n/** 활성 필터 아이템 타입 */\r\nexport interface ActiveFilterItem {\r\n /** 필터 식별 키 */\r\n key: string\r\n /** 표시할 라벨 (필터명) */\r\n label: string\r\n /** 표시할 값 */\r\n value: string\r\n}\r\n\r\n/** 필터 설정 타입 */\r\nexport interface FilterDisplayItem {\r\n /** 표시할 라벨 */\r\n label: string\r\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\r\n displayValue?: (value: unknown) => string\r\n}\r\n\r\nexport interface JFilterBarProps {\r\n /** 필터바 타이틀 */\r\n title?: string\r\n /** 필터 접힘 상태 (v-model 지원) */\r\n collapsed?: boolean\r\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\r\n collapsible?: boolean\r\n /** 필터 값 객체 (v-model:filterValues 지원) */\r\n filterValues?: Record<string, unknown>\r\n /** 필터 표시 설정 (label, displayValue 등) */\r\n filterDisplay?: Record<string, FilterDisplayItem>\r\n /** 초기화 버튼 표시 여부 */\r\n showResetButton?: boolean\r\n /** 조회 버튼 표시 여부 */\r\n showSearchButton?: boolean\r\n /** 초기화 버튼 텍스트 */\r\n resetButtonText?: string\r\n /** 조회 버튼 텍스트 */\r\n searchButtonText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\r\n collapsed: true,\r\n collapsible: true,\r\n filterValues: () => ({}),\r\n filterDisplay: () => ({}),\r\n showResetButton: false,\r\n showSearchButton: false,\r\n resetButtonText: '초기화',\r\n searchButtonText: '조회',\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'update:filterValues': [value: Record<string, unknown>]\r\n /** 조회 버튼 클릭 */\r\n search: []\r\n /** 초기화 버튼 클릭 */\r\n reset: []\r\n}>()\r\n\r\nconst isExpanded = computed(() => {\r\n if (!props.collapsible) return true\r\n return !props.collapsed\r\n})\r\n\r\n/** 값이 비어있는지 확인 */\r\nfunction isEmpty(value: unknown): boolean {\r\n if (value === null || value === undefined) return true\r\n if (typeof value === 'string' && value.trim() === '') return true\r\n if (Array.isArray(value) && value.length === 0) return true\r\n return false\r\n}\r\n\r\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\r\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\r\n const filters: ActiveFilterItem[] = []\r\n\r\n for (const [key, config] of Object.entries(props.filterDisplay)) {\r\n const value = props.filterValues[key]\r\n if (isEmpty(value)) continue\r\n\r\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\r\n if (displayValue.trim() === '') continue\r\n\r\n filters.push({\r\n key,\r\n label: config.label,\r\n value: displayValue,\r\n })\r\n }\r\n\r\n return filters\r\n})\r\n\r\nfunction toggleCollapsed() {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n\r\nfunction handleReset() {\r\n // filterValues의 모든 값을 초기화\r\n const resetValues: Record<string, unknown> = {}\r\n for (const key of Object.keys(props.filterValues)) {\r\n const currentValue = props.filterValues[key]\r\n if (typeof currentValue === 'string') {\r\n resetValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n resetValues[key] = []\r\n } else {\r\n resetValues[key] = null\r\n }\r\n }\r\n emit('update:filterValues', resetValues)\r\n emit('reset')\r\n}\r\n\r\nfunction handleSearch() {\r\n emit('search')\r\n}\r\n\r\nfunction removeFilter(key: string) {\r\n // filterValues 업데이트 (해당 키 값을 초기화)\r\n const newValues = { ...props.filterValues }\r\n const currentValue = newValues[key]\r\n\r\n // 타입에 따라 적절한 초기값으로 설정\r\n if (typeof currentValue === 'string') {\r\n newValues[key] = ''\r\n } else if (Array.isArray(currentValue)) {\r\n newValues[key] = []\r\n } else {\r\n newValues[key] = null\r\n }\r\n\r\n emit('update:filterValues', newValues)\r\n}\r\n</script>\r\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_createVNode","_unref","ChevronDown","_normalizeClass","_createBlock","JLabel","_hoisted_4","_Fragment","_renderList","filter","JBadge","_hoisted_5","_toDisplayString","_withModifiers","$event","X","_hoisted_7","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_8"],"mappings":"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"}
1
+ {"version":3,"file":"JFilterBar.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("./JFilterBar.vue.cjs");exports.default=e.default;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),d=require("lucide-vue-next"),B=require("../atoms/JBadge.vue.cjs"),f=require("../atoms/JButton.vue.cjs"),v=require("../atoms/JLabel.vue.cjs"),k=require("../../lib/utils.cjs"),x={class:"flex items-center justify-between px-3 py-1.5"},_={class:"flex items-center gap-2"},b={key:2,class:"flex items-center gap-1 flex-wrap"},w={class:"text-muted-foreground"},C=["onClick"],N={class:"flex items-center gap-2"},E={class:"px-3 pb-3"},S={class:"filter-fields-grid"},D=e.defineComponent({__name:"JFilterBar",props:{class:{},title:{},collapsed:{type:Boolean,default:!0},collapsible:{type:Boolean,default:!0},filterValues:{default:()=>({})},filterDisplay:{default:()=>({})},showResetButton:{type:Boolean,default:!1},showSearchButton:{type:Boolean,default:!1},resetButtonText:{default:"초기화"},searchButtonText:{default:"조회"}},emits:["update:collapsed","update:filterValues","search","reset"],setup(s,{emit:p}){const o=s,n=p,c=e.computed(()=>o.collapsible?!o.collapsed:!0);function m(t){return!!(t==null||typeof t=="string"&&t.trim()===""||Array.isArray(t)&&t.length===0)}const i=e.computed(()=>{const t=[];for(const[l,r]of Object.entries(o.filterDisplay)){const a=o.filterValues[l];if(m(a))continue;const u=r.displayValue?r.displayValue(a):String(a);u.trim()!==""&&t.push({key:l,label:r.label,value:u})}return t});function y(){n("update:collapsed",!o.collapsed)}function h(){const t={};for(const l of Object.keys(o.filterValues)){const r=o.filterValues[l];typeof r=="string"?t[l]="":Array.isArray(r)?t[l]=[]:t[l]=null}n("update:filterValues",t),n("reset")}function V(){n("search")}function g(t){const l={...o.filterValues},r=l[t];typeof r=="string"?l[t]="":Array.isArray(r)?l[t]=[]:l[t]=null,n("update:filterValues",l)}return(t,l)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(e.unref(k.cn)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground",o.class))},[e.createElementVNode("div",x,[e.createElementVNode("div",_,[s.collapsible?(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",onClick:y},[e.createVNode(e.unref(d.ChevronDown),{class:e.normalizeClass(["h-3.5 w-3.5 transition-transform",c.value?"rotate-0":"-rotate-90"])},null,8,["class"])])):e.createCommentVNode("",!0),s.title?(e.openBlock(),e.createBlock(v.default,{key:1,text:s.title,class:"text-sm font-semibold text-foreground"},null,8,["text"])):e.createCommentVNode("",!0),i.value.length>0?(e.openBlock(),e.createElementBlock("div",b,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(i.value,r=>(e.openBlock(),e.createBlock(B.default,{key:r.key,variant:"secondary",size:"sm",class:"flex items-center gap-1 cursor-default"},{default:e.withCtx(()=>[e.createElementVNode("span",w,e.toDisplayString(r.label)+":",1),e.createElementVNode("span",null,e.toDisplayString(r.value),1),e.createElementVNode("button",{type:"button",class:"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors",onClick:e.withModifiers(a=>g(r.key),["stop"])},[e.createVNode(e.unref(d.X),{class:"h-3 w-3"})],8,C)]),_:2},1024))),128))])):e.createCommentVNode("",!0)]),e.createElementVNode("div",N,[e.renderSlot(t.$slots,"actions",{},void 0,!0),s.showResetButton?(e.openBlock(),e.createBlock(f.default,{key:0,variant:"secondary",size:"sm",onClick:h},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(s.resetButtonText),1)]),_:1})):e.createCommentVNode("",!0),s.showSearchButton?(e.openBlock(),e.createBlock(f.default,{key:1,styletype:"primary",size:"sm",onClick:V},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(s.searchButtonText),1)]),_:1})):e.createCommentVNode("",!0)])]),e.withDirectives(e.createElementVNode("div",E,[e.createElementVNode("div",S,[e.renderSlot(t.$slots,"filters",{},void 0,!0)])],512),[[e.vShow,c.value]])],2))}});exports.default=D;
2
2
  //# sourceMappingURL=JFilterBar.vue2.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue2.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"JFilterBar.vue2.cjs","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-3 py-1.5\">\n <div class=\"flex items-center gap-2\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.5rem 0.75rem;\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":"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"}
@@ -1,5 +1,146 @@
1
- import f from "./JFilterBar.vue.js";
1
+ import { defineComponent as D, computed as v, createElementBlock as u, openBlock as o, normalizeClass as x, unref as p, createElementVNode as n, withDirectives as T, createCommentVNode as i, createBlock as f, createVNode as b, Fragment as $, renderList as z, withCtx as m, toDisplayString as d, withModifiers as E, renderSlot as V, createTextVNode as B, vShow as F } from "vue";
2
+ import { ChevronDown as N, X as R } from "lucide-vue-next";
3
+ import J from "../atoms/JBadge.vue.js";
4
+ import k from "../atoms/JButton.vue.js";
5
+ import O from "../atoms/JLabel.vue.js";
6
+ import { cn as L } from "../../lib/utils.js";
7
+ const M = { class: "flex items-center justify-between px-3 py-1.5" }, X = { class: "flex items-center gap-2" }, q = {
8
+ key: 2,
9
+ class: "flex items-center gap-1 flex-wrap"
10
+ }, G = { class: "text-muted-foreground" }, H = ["onClick"], I = { class: "flex items-center gap-2" }, K = { class: "px-3 pb-3" }, P = { class: "filter-fields-grid" }, te = /* @__PURE__ */ D({
11
+ __name: "JFilterBar",
12
+ props: {
13
+ class: {},
14
+ title: {},
15
+ collapsed: { type: Boolean, default: !0 },
16
+ collapsible: { type: Boolean, default: !0 },
17
+ filterValues: { default: () => ({}) },
18
+ filterDisplay: { default: () => ({}) },
19
+ showResetButton: { type: Boolean, default: !1 },
20
+ showSearchButton: { type: Boolean, default: !1 },
21
+ resetButtonText: { default: "초기화" },
22
+ searchButtonText: { default: "조회" }
23
+ },
24
+ emits: ["update:collapsed", "update:filterValues", "search", "reset"],
25
+ setup(r, { emit: w }) {
26
+ const l = r, a = w, y = v(() => l.collapsible ? !l.collapsed : !0);
27
+ function _(e) {
28
+ return !!(e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0);
29
+ }
30
+ const h = v(() => {
31
+ const e = [];
32
+ for (const [t, s] of Object.entries(l.filterDisplay)) {
33
+ const c = l.filterValues[t];
34
+ if (_(c)) continue;
35
+ const g = s.displayValue ? s.displayValue(c) : String(c);
36
+ g.trim() !== "" && e.push({
37
+ key: t,
38
+ label: s.label,
39
+ value: g
40
+ });
41
+ }
42
+ return e;
43
+ });
44
+ function C() {
45
+ a("update:collapsed", !l.collapsed);
46
+ }
47
+ function S() {
48
+ const e = {};
49
+ for (const t of Object.keys(l.filterValues)) {
50
+ const s = l.filterValues[t];
51
+ typeof s == "string" ? e[t] = "" : Array.isArray(s) ? e[t] = [] : e[t] = null;
52
+ }
53
+ a("update:filterValues", e), a("reset");
54
+ }
55
+ function A() {
56
+ a("search");
57
+ }
58
+ function j(e) {
59
+ const t = { ...l.filterValues }, s = t[e];
60
+ typeof s == "string" ? t[e] = "" : Array.isArray(s) ? t[e] = [] : t[e] = null, a("update:filterValues", t);
61
+ }
62
+ return (e, t) => (o(), u("div", {
63
+ class: x(p(L)("j-filter-bar w-full rounded-sm border bg-card text-card-foreground", l.class))
64
+ }, [
65
+ n("div", M, [
66
+ n("div", X, [
67
+ r.collapsible ? (o(), u("button", {
68
+ key: 0,
69
+ type: "button",
70
+ class: "flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors",
71
+ onClick: C
72
+ }, [
73
+ b(p(N), {
74
+ class: x([
75
+ "h-3.5 w-3.5 transition-transform",
76
+ y.value ? "rotate-0" : "-rotate-90"
77
+ ])
78
+ }, null, 8, ["class"])
79
+ ])) : i("", !0),
80
+ r.title ? (o(), f(O, {
81
+ key: 1,
82
+ text: r.title,
83
+ class: "text-sm font-semibold text-foreground"
84
+ }, null, 8, ["text"])) : i("", !0),
85
+ h.value.length > 0 ? (o(), u("div", q, [
86
+ (o(!0), u($, null, z(h.value, (s) => (o(), f(J, {
87
+ key: s.key,
88
+ variant: "secondary",
89
+ size: "sm",
90
+ class: "flex items-center gap-1 cursor-default"
91
+ }, {
92
+ default: m(() => [
93
+ n("span", G, d(s.label) + ":", 1),
94
+ n("span", null, d(s.value), 1),
95
+ n("button", {
96
+ type: "button",
97
+ class: "ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors",
98
+ onClick: E((c) => j(s.key), ["stop"])
99
+ }, [
100
+ b(p(R), { class: "h-3 w-3" })
101
+ ], 8, H)
102
+ ]),
103
+ _: 2
104
+ }, 1024))), 128))
105
+ ])) : i("", !0)
106
+ ]),
107
+ n("div", I, [
108
+ V(e.$slots, "actions", {}, void 0, !0),
109
+ r.showResetButton ? (o(), f(k, {
110
+ key: 0,
111
+ variant: "secondary",
112
+ size: "sm",
113
+ onClick: S
114
+ }, {
115
+ default: m(() => [
116
+ B(d(r.resetButtonText), 1)
117
+ ]),
118
+ _: 1
119
+ })) : i("", !0),
120
+ r.showSearchButton ? (o(), f(k, {
121
+ key: 1,
122
+ styletype: "primary",
123
+ size: "sm",
124
+ onClick: A
125
+ }, {
126
+ default: m(() => [
127
+ B(d(r.searchButtonText), 1)
128
+ ]),
129
+ _: 1
130
+ })) : i("", !0)
131
+ ])
132
+ ]),
133
+ T(n("div", K, [
134
+ n("div", P, [
135
+ V(e.$slots, "filters", {}, void 0, !0)
136
+ ])
137
+ ], 512), [
138
+ [F, y.value]
139
+ ])
140
+ ], 2));
141
+ }
142
+ });
2
143
  export {
3
- f as default
144
+ te as default
4
145
  };
5
146
  //# sourceMappingURL=JFilterBar.vue2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFilterBar.vue2.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"JFilterBar.vue2.js","sources":["../../../../src/components/organisms/JFilterBar.vue"],"sourcesContent":["<template>\n <div :class=\"cn('j-filter-bar w-full rounded-sm border bg-card text-card-foreground', props.class)\">\n <!-- Row 1: toolbar -->\n <div class=\"flex items-center justify-between px-3 py-1.5\">\n <div class=\"flex items-center gap-2\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"flex items-center justify-center h-6 w-6 rounded hover:bg-accent hover:text-accent-foreground transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <ChevronDown\n :class=\"[\n 'h-3.5 w-3.5 transition-transform',\n isExpanded ? 'rotate-0' : '-rotate-90',\n ]\"\n />\n </button>\n <!-- 타이틀 -->\n <JLabel\n v-if=\"title\"\n :text=\"title\"\n class=\"text-sm font-semibold text-foreground\"\n />\n <!-- 선택된 필터 뱃지 표시 -->\n <div v-if=\"activeFilters.length > 0\" class=\"flex items-center gap-1 flex-wrap\">\n <JBadge\n v-for=\"filter in activeFilters\"\n :key=\"filter.key\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1 cursor-default\"\n >\n <span class=\"text-muted-foreground\">{{ filter.label }}:</span>\n <span>{{ filter.value }}</span>\n <button\n type=\"button\"\n class=\"ml-0.5 rounded-full hover:bg-gray-300 p-0.5 transition-colors\"\n @click.stop=\"removeFilter(filter.key)\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <slot name=\"actions\" />\n <JButton\n v-if=\"showResetButton\"\n variant=\"secondary\"\n size=\"sm\"\n @click=\"handleReset\"\n >\n {{ resetButtonText }}\n </JButton>\n <JButton\n v-if=\"showSearchButton\"\n styletype=\"primary\"\n size=\"sm\"\n @click=\"handleSearch\"\n >\n {{ searchButtonText }}\n </JButton>\n </div>\n </div>\n\n <!-- Row 2: filters (반응형 그리드: max 4열, 자동 축소) -->\n <div v-show=\"isExpanded\" class=\"px-3 pb-3\">\n <div class=\"filter-fields-grid\">\n <slot name=\"filters\" />\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport JBadge from '@/components/atoms/JBadge.vue'\nimport JButton from '@/components/atoms/JButton.vue'\nimport JLabel from '@/components/atoms/JLabel.vue'\nimport { cn } from '@/lib/utils'\n\n/** 활성 필터 아이템 타입 */\nexport interface ActiveFilterItem {\n /** 필터 식별 키 */\n key: string\n /** 표시할 라벨 (필터명) */\n label: string\n /** 표시할 값 */\n value: string\n}\n\n/** 필터 설정 타입 */\nexport interface FilterDisplayItem {\n /** 표시할 라벨 */\n label: string\n /** 값을 표시용 문자열로 변환 (예: combo value -> label) */\n displayValue?: (value: unknown) => string\n}\n\nexport interface JFilterBarProps {\n /** 추가 클래스 (외부 커스터마이징용) */\n class?: string\n /** 필터바 타이틀 */\n title?: string\n /** 필터 접힘 상태 (v-model 지원) */\n collapsed?: boolean\n /** 접기/펼치기 가능 여부. false면 토글 버튼 숨김 & 필터 항상 표시 */\n collapsible?: boolean\n /** 필터 값 객체 (v-model:filterValues 지원) */\n filterValues?: Record<string, unknown>\n /** 필터 표시 설정 (label, displayValue 등) */\n filterDisplay?: Record<string, FilterDisplayItem>\n /** 초기화 버튼 표시 여부 */\n showResetButton?: boolean\n /** 조회 버튼 표시 여부 */\n showSearchButton?: boolean\n /** 초기화 버튼 텍스트 */\n resetButtonText?: string\n /** 조회 버튼 텍스트 */\n searchButtonText?: string\n}\n\nconst props = withDefaults(defineProps<JFilterBarProps>(), {\n collapsed: true,\n collapsible: true,\n filterValues: () => ({}),\n filterDisplay: () => ({}),\n showResetButton: false,\n showSearchButton: false,\n resetButtonText: '초기화',\n searchButtonText: '조회',\n})\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'update:filterValues': [value: Record<string, unknown>]\n /** 조회 버튼 클릭 */\n search: []\n /** 초기화 버튼 클릭 */\n reset: []\n}>()\n\nconst isExpanded = computed(() => {\n if (!props.collapsible) return true\n return !props.collapsed\n})\n\n/** 값이 비어있는지 확인 */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true\n if (typeof value === 'string' && value.trim() === '') return true\n if (Array.isArray(value) && value.length === 0) return true\n return false\n}\n\n/** filterValues + filterDisplay 기반으로 활성 필터 목록 자동 생성 */\nconst activeFilters = computed<ActiveFilterItem[]>(() => {\n const filters: ActiveFilterItem[] = []\n\n for (const [key, config] of Object.entries(props.filterDisplay)) {\n const value = props.filterValues[key]\n if (isEmpty(value)) continue\n\n const displayValue = config.displayValue ? config.displayValue(value) : String(value)\n if (displayValue.trim() === '') continue\n\n filters.push({\n key,\n label: config.label,\n value: displayValue,\n })\n }\n\n return filters\n})\n\nfunction toggleCollapsed() {\n emit('update:collapsed', !props.collapsed)\n}\n\nfunction handleReset() {\n // filterValues의 모든 값을 초기화\n const resetValues: Record<string, unknown> = {}\n for (const key of Object.keys(props.filterValues)) {\n const currentValue = props.filterValues[key]\n if (typeof currentValue === 'string') {\n resetValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n resetValues[key] = []\n } else {\n resetValues[key] = null\n }\n }\n emit('update:filterValues', resetValues)\n emit('reset')\n}\n\nfunction handleSearch() {\n emit('search')\n}\n\nfunction removeFilter(key: string) {\n // filterValues 업데이트 (해당 키 값을 초기화)\n const newValues = { ...props.filterValues }\n const currentValue = newValues[key]\n\n // 타입에 따라 적절한 초기값으로 설정\n if (typeof currentValue === 'string') {\n newValues[key] = ''\n } else if (Array.isArray(currentValue)) {\n newValues[key] = []\n } else {\n newValues[key] = null\n }\n\n emit('update:filterValues', newValues)\n}\n</script>\n\n<style scoped>\n/* 필터 필드 반응형 그리드: max 4열, 자동 축소 (4 → 3 → 2 → 1) */\n.filter-fields-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(max(25% - 0.75rem, 220px), 1fr));\n gap: 0.5rem 0.75rem;\n}\n\n/* ========================================\n 패턴 3: Tabs 아래 배치 시 연결 스타일\n ======================================== */\n\n:deep([data-state=\"active\"]) > .j-filter-bar {\n border-top: none;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n:deep([role=\"tabpanel\"]) .j-filter-bar {\n border-top: none;\n}\n</style>\n"],"names":["props","__props","emit","__emit","isExpanded","computed","isEmpty","value","activeFilters","filters","key","config","displayValue","toggleCollapsed","handleReset","resetValues","currentValue","handleSearch","removeFilter","newValues","_createElementBlock","_normalizeClass","_unref","cn","_createElementVNode","_hoisted_1","_hoisted_2","_createVNode","ChevronDown","_createBlock","JLabel","_openBlock","_hoisted_3","_Fragment","_renderList","filter","JBadge","_hoisted_4","_toDisplayString","_withModifiers","$event","X","_hoisted_6","_renderSlot","_ctx","JButton","_withDirectives","_hoisted_7","_hoisted_8"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA4HA,UAAMA,IAAQC,GAWRC,IAAOC,GASPC,IAAaC,EAAS,MACrBL,EAAM,cACJ,CAACA,EAAM,YADiB,EAEhC;AAGD,aAASM,EAAQC,GAAyB;AAGxC,aAFI,GAAAA,KAAU,QACV,OAAOA,KAAU,YAAYA,EAAM,KAAA,MAAW,MAC9C,MAAM,QAAQA,CAAK,KAAKA,EAAM,WAAW;AAAA,IAE/C;AAGA,UAAMC,IAAgBH,EAA6B,MAAM;AACvD,YAAMI,IAA8B,CAAA;AAEpC,iBAAW,CAACC,GAAKC,CAAM,KAAK,OAAO,QAAQX,EAAM,aAAa,GAAG;AAC/D,cAAMO,IAAQP,EAAM,aAAaU,CAAG;AACpC,YAAIJ,EAAQC,CAAK,EAAG;AAEpB,cAAMK,IAAeD,EAAO,eAAeA,EAAO,aAAaJ,CAAK,IAAI,OAAOA,CAAK;AACpF,QAAIK,EAAa,KAAA,MAAW,MAE5BH,EAAQ,KAAK;AAAA,UACX,KAAAC;AAAA,UACA,OAAOC,EAAO;AAAA,UACd,OAAOC;AAAA,QAAA,CACR;AAAA,MACH;AAEA,aAAOH;AAAA,IACT,CAAC;AAED,aAASI,IAAkB;AACzB,MAAAX,EAAK,oBAAoB,CAACF,EAAM,SAAS;AAAA,IAC3C;AAEA,aAASc,IAAc;AAErB,YAAMC,IAAuC,CAAA;AAC7C,iBAAWL,KAAO,OAAO,KAAKV,EAAM,YAAY,GAAG;AACjD,cAAMgB,IAAehB,EAAM,aAAaU,CAAG;AAC3C,QAAI,OAAOM,KAAiB,WAC1BD,EAAYL,CAAG,IAAI,KACV,MAAM,QAAQM,CAAY,IACnCD,EAAYL,CAAG,IAAI,CAAA,IAEnBK,EAAYL,CAAG,IAAI;AAAA,MAEvB;AACA,MAAAR,EAAK,uBAAuBa,CAAW,GACvCb,EAAK,OAAO;AAAA,IACd;AAEA,aAASe,IAAe;AACtB,MAAAf,EAAK,QAAQ;AAAA,IACf;AAEA,aAASgB,EAAaR,GAAa;AAEjC,YAAMS,IAAY,EAAE,GAAGnB,EAAM,aAAA,GACvBgB,IAAeG,EAAUT,CAAG;AAGlC,MAAI,OAAOM,KAAiB,WAC1BG,EAAUT,CAAG,IAAI,KACR,MAAM,QAAQM,CAAY,IACnCG,EAAUT,CAAG,IAAI,CAAA,IAEjBS,EAAUT,CAAG,IAAI,MAGnBR,EAAK,uBAAuBiB,CAAS;AAAA,IACvC;2BAzNEC,EAuEM,OAAA;AAAA,MAvEA,OAAKC,EAAEC,EAAAC,CAAA,EAAE,sEAAuEvB,EAAM,KAAK,CAAA;AAAA,IAAA;MAE/FwB,EA6DM,OA7DNC,GA6DM;AAAA,QA5DJD,EAwCM,OAxCNE,GAwCM;AAAA,UAtCIzB,EAAA,oBADRmB,EAYS,UAAA;AAAA;YAVP,MAAK;AAAA,YACL,OAAM;AAAA,YACL,SAAOP;AAAA,UAAA;YAERc,EAKEL,EAAAM,CAAA,GAAA;AAAA,cAJC,OAAKP,EAAA;AAAA;gBAAoEjB,EAAA,QAAU,aAAA;AAAA,cAAA;;;UAQhFH,EAAA,cADR4B,EAIEC,GAAA;AAAA;YAFC,MAAM7B,EAAA;AAAA,YACP,OAAM;AAAA,UAAA;UAGGO,EAAA,MAAc,SAAM,KAA/BuB,KAAAX,EAkBM,OAlBNY,GAkBM;AAAA,oBAjBJZ,EAgBSa,GAAA,MAAAC,EAfU1B,EAAA,OAAa,CAAvB2B,YADTN,EAgBSO,GAAA;AAAA,cAdN,KAAKD,EAAO;AAAA,cACb,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,OAAM;AAAA,YAAA;yBAEN,MAA8D;AAAA,gBAA9DX,EAA8D,QAA9Da,GAA8DC,EAAvBH,EAAO,KAAK,IAAG,KAAC,CAAA;AAAA,gBACvDX,EAA+B,QAAA,MAAAc,EAAtBH,EAAO,KAAK,GAAA,CAAA;AAAA,gBACrBX,EAMS,UAAA;AAAA,kBALP,MAAK;AAAA,kBACL,OAAM;AAAA,kBACL,SAAKe,EAAA,CAAAC,MAAOtB,EAAaiB,EAAO,GAAG,GAAA,CAAA,MAAA,CAAA;AAAA,gBAAA;kBAEpCR,EAAqBL,EAAAmB,CAAA,GAAA,EAAlB,OAAM,WAAS;AAAA,gBAAA;;;;;;QAK1BjB,EAkBM,OAlBNkB,GAkBM;AAAA,UAjBJC,EAAuBC,EAAA,QAAA,WAAA,CAAA,GAAA,QAAA,EAAA;AAAA,UAEf3C,EAAA,wBADR4B,EAOUgB,GAAA;AAAA;YALR,SAAQ;AAAA,YACR,MAAK;AAAA,YACJ,SAAO/B;AAAA,UAAA;uBAER,MAAqB;AAAA,kBAAlBb,EAAA,eAAe,GAAA,CAAA;AAAA,YAAA;;;UAGZA,EAAA,yBADR4B,EAOUgB,GAAA;AAAA;YALR,WAAU;AAAA,YACV,MAAK;AAAA,YACJ,SAAO5B;AAAA,UAAA;uBAER,MAAsB;AAAA,kBAAnBhB,EAAA,gBAAgB,GAAA,CAAA;AAAA,YAAA;;;;;MAMzB6C,EAAAtB,EAIM,OAJNuB,GAIM;AAAA,QAHJvB,EAEM,OAFNwB,GAEM;AAAA,UADJL,EAAuBC,EAAA,QAAA,WAAA,CAAA,GAAA,QAAA,EAAA;AAAA,QAAA;;YAFdxC,EAAA,KAAU;AAAA,MAAA;;;;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const i=require("../atoms/JButton.vue.cjs");require("lucide-vue-next");require("clsx");require("tailwind-merge");require("@internationalized/date");require("md-editor-v3");;/* empty css */;/* empty css */require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */;/* empty css */;/* empty css */;/* empty css */require("vue-sonner");;/* empty css */;/* empty css */;/* empty css */;/* empty css */const x=require("./JDynamicForm.vue.cjs");;/* empty css */require("vue-router");;/* empty css */;/* empty css */const h=require("../shadcn/Dialog.vue.cjs"),y=require("../shadcn/DialogContent.vue.cjs"),C=require("../shadcn/DialogHeader.vue.cjs"),V=require("../shadcn/DialogTitle.vue.cjs"),b=require("../shadcn/DialogBody.vue.cjs"),k=require("../shadcn/DialogFooter.vue.cjs"),w={class:"space-y-4"},D={key:0,class:"text-sm text-muted-foreground"},N=e.defineComponent({__name:"JFormModal",props:{open:{type:Boolean,default:!1},title:{},description:{},schema:{},modelValue:{},size:{default:"lg"},buttonType:{default:"OkCancel"},confirmText:{default:"확인"},cancelText:{default:"취소"},confirmVariant:{default:"default"},confirmDisabled:{type:Boolean,default:!1}},emits:["update:open","update:modelValue","confirm","cancel","submit","change","error"],setup(r,{expose:d,emit:s}){const o=r,u=s,a=e.ref(null),l=e.ref(o.modelValue||{}),n=e.ref(!1),m=e.computed(()=>({sm:"!max-w-sm",md:"!max-w-md",lg:"!max-w-2xl",xl:"!max-w-4xl","2xl":"!max-w-6xl",full:"!max-w-[95vw]"})[o.size]);e.watch(()=>o.modelValue,t=>{t!=null?l.value={...t}:l.value={}},{deep:!0});const f=t=>{l.value=t,u("update:modelValue",t)},v=t=>{u("change",t)},q=t=>{u("submit",t),n.value&&(u("confirm",t),n.value=!1)},_=t=>{u("error",t),n.value=!1},p=t=>{u("update:open",t)},c=()=>{n.value=!0,a.value&&a.value.submit()},g=()=>{u("cancel"),a.value&&a.value.reset()};return d({reset:()=>a.value?.reset(),submit:()=>a.value?.submit(),formState:e.computed(()=>a.value?.formState)}),(t,B)=>(e.openBlock(),e.createBlock(e.unref(h.default),{open:r.open,"onUpdate:open":p,class:e.normalizeClass(m.value)},{default:e.withCtx(()=>[e.createVNode(e.unref(y.default),null,{default:e.withCtx(()=>[r.title?(e.openBlock(),e.createBlock(e.unref(C.default),{key:0,class:"bg-muted/50 border-b"},{default:e.withCtx(()=>[e.createVNode(e.unref(V.default),null,{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.title),1)]),_:1})]),_:1})):e.createCommentVNode("",!0),e.createVNode(e.unref(b.default),{class:"max-h-[70vh] overflow-y-auto"},{default:e.withCtx(()=>[e.createElementVNode("div",w,[r.description?(e.openBlock(),e.createElementBlock("p",D,e.toDisplayString(r.description),1)):e.createCommentVNode("",!0),e.createVNode(e.unref(x.default),{ref_key:"dynamicFormRef",ref:a,schema:r.schema,"model-value":l.value,"onUpdate:modelValue":f,onSubmit:q,onChange:v,onError:_},null,8,["schema","model-value"]),e.renderSlot(t.$slots,"body")])]),_:3}),e.createVNode(e.unref(k.default),null,{default:e.withCtx(()=>[r.buttonType==="OkCancel"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createVNode(e.unref(i.default),{variant:"outline",onClick:g},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.cancelText),1)]),_:1}),e.createVNode(e.unref(i.default),{variant:r.confirmVariant,disabled:r.confirmDisabled,onClick:c},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.confirmText),1)]),_:1},8,["variant","disabled"])],64)):r.buttonType==="Ok"?(e.openBlock(),e.createBlock(e.unref(i.default),{key:1,variant:r.confirmVariant,disabled:r.confirmDisabled,onClick:c,class:"w-full"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.confirmText),1)]),_:1},8,["variant","disabled"])):e.createCommentVNode("",!0),e.renderSlot(t.$slots,"footer")]),_:3})]),_:3})]),_:3},8,["open","class"]))}});exports.default=N;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue");require("../shadcn/index.cjs");const o=require("../atoms/JButton.vue.cjs");require("lucide-vue-next");require("clsx");require("tailwind-merge");require("@internationalized/date");require("md-editor-v3");;/* empty css */;/* empty css */require("../shadcn/badge-variants.cjs");require("@vueuse/core");require("reka-ui");;/* empty css */require("../shadcn/avatar-variants.cjs");require("dompurify");;/* empty css */require("ag-grid-vue3");require("ag-grid-community");require("ag-grid-enterprise");;/* empty css */;/* empty css */;/* empty css */;/* empty css */;/* empty css */;/* empty css */require("vue-sonner");;/* empty css */;/* empty css */;/* empty css */;/* empty css */;/* empty css */const g=require("./JDynamicForm.vue.cjs");;/* empty css */;/* empty css */require("vue-router");;/* empty css */;/* empty css */;/* empty css */const h=require("../shadcn/Dialog.vue.cjs"),y=require("../shadcn/DialogContent.vue.cjs"),C=require("../shadcn/DialogHeader.vue.cjs"),V=require("../shadcn/DialogTitle.vue.cjs"),b=require("../shadcn/DialogBody.vue.cjs"),k=require("../shadcn/DialogFooter.vue.cjs"),w={class:"space-y-4"},D={key:0,class:"text-sm text-muted-foreground"},N=e.defineComponent({__name:"JFormModal",props:{open:{type:Boolean,default:!1},title:{},description:{},schema:{},modelValue:{},size:{default:"lg"},buttonType:{default:"OkCancel"},confirmText:{default:"확인"},cancelText:{default:"취소"},confirmVariant:{default:"default"},confirmDisabled:{type:Boolean,default:!1}},emits:["update:open","update:modelValue","confirm","cancel","submit","change","error"],setup(r,{expose:d,emit:s}){const n=r,u=s,a=e.ref(null),l=e.ref(n.modelValue||{}),i=e.ref(!1),m=e.computed(()=>({sm:"!max-w-sm",md:"!max-w-md",lg:"!max-w-2xl",xl:"!max-w-4xl","2xl":"!max-w-6xl",full:"!max-w-[95vw]"})[n.size]);e.watch(()=>n.modelValue,t=>{t!=null?l.value={...t}:l.value={}},{deep:!0});const f=t=>{l.value=t,u("update:modelValue",t)},q=t=>{u("change",t)},v=t=>{u("submit",t),i.value&&(u("confirm",t),i.value=!1)},p=t=>{u("error",t),i.value=!1},x=t=>{u("update:open",t)},c=()=>{i.value=!0,a.value&&a.value.submit()},_=()=>{u("cancel"),a.value&&a.value.reset()};return d({reset:()=>a.value?.reset(),submit:()=>a.value?.submit(),formState:e.computed(()=>a.value?.formState)}),(t,B)=>(e.openBlock(),e.createBlock(e.unref(h.default),{open:r.open,"onUpdate:open":x,class:e.normalizeClass(m.value)},{default:e.withCtx(()=>[e.createVNode(e.unref(y.default),null,{default:e.withCtx(()=>[r.title?(e.openBlock(),e.createBlock(e.unref(C.default),{key:0,class:"bg-muted/50 border-b"},{default:e.withCtx(()=>[e.createVNode(e.unref(V.default),null,{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.title),1)]),_:1})]),_:1})):e.createCommentVNode("",!0),e.createVNode(e.unref(b.default),{class:"max-h-[70vh] overflow-y-auto"},{default:e.withCtx(()=>[e.createElementVNode("div",w,[r.description?(e.openBlock(),e.createElementBlock("p",D,e.toDisplayString(r.description),1)):e.createCommentVNode("",!0),e.createVNode(e.unref(g.default),{ref_key:"dynamicFormRef",ref:a,schema:r.schema,"model-value":l.value,"onUpdate:modelValue":f,onSubmit:v,onChange:q,onError:p},null,8,["schema","model-value"]),e.renderSlot(t.$slots,"body")])]),_:3}),e.createVNode(e.unref(k.default),null,{default:e.withCtx(()=>[r.buttonType==="OkCancel"?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createVNode(e.unref(o.default),{variant:"outline",onClick:_},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.cancelText),1)]),_:1}),e.createVNode(e.unref(o.default),{variant:r.confirmVariant,disabled:r.confirmDisabled,onClick:c},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.confirmText),1)]),_:1},8,["variant","disabled"])],64)):r.buttonType==="Ok"?(e.openBlock(),e.createBlock(e.unref(o.default),{key:1,variant:r.confirmVariant,disabled:r.confirmDisabled,onClick:c,class:"w-full"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(r.confirmText),1)]),_:1},8,["variant","disabled"])):e.createCommentVNode("",!0),e.renderSlot(t.$slots,"footer")]),_:3})]),_:3})]),_:3},8,["open","class"]))}});exports.default=N;
2
2
  //# sourceMappingURL=JFormModal.vue.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"JFormModal.vue.cjs","sources":["../../../../src/components/organisms/JFormModal.vue"],"sourcesContent":["<template>\r\n <Dialog\r\n :open=\"open\"\r\n @update:open=\"onOpenChange\"\r\n :class=\"sizeClass\"\r\n >\r\n <DialogContent>\r\n <!-- Header -->\r\n <DialogHeader v-if=\"title\" class=\"bg-muted/50 border-b\">\r\n <DialogTitle>\r\n {{ title }}\r\n </DialogTitle>\r\n </DialogHeader>\r\n\r\n <!-- Body -->\r\n <DialogBody class=\"max-h-[70vh] overflow-y-auto\">\r\n <div class=\"space-y-4\">\r\n <!-- Description -->\r\n <p v-if=\"description\" class=\"text-sm text-muted-foreground\">\r\n {{ description }}\r\n </p>\r\n\r\n <!-- JDynamicForm -->\r\n <JDynamicForm\r\n ref=\"dynamicFormRef\"\r\n :schema=\"schema\"\r\n :model-value=\"internalFormValue\"\r\n @update:model-value=\"handleFormChange\"\r\n @submit=\"handleFormSubmit\"\r\n @change=\"handleFieldChange\"\r\n @error=\"handleFormError\"\r\n />\r\n\r\n <!-- 커스텀 컨텐츠 슬롯 -->\r\n <slot name=\"body\" />\r\n </div>\r\n </DialogBody>\r\n\r\n <!-- Footer -->\r\n <DialogFooter>\r\n <!-- 확인/취소 버튼 -->\r\n <template v-if=\"buttonType === 'OkCancel'\">\r\n <JButton\r\n variant=\"outline\"\r\n @click=\"handleCancel\"\r\n >\r\n {{ cancelText }}\r\n </JButton>\r\n <JButton\r\n :variant=\"confirmVariant\"\r\n :disabled=\"confirmDisabled\"\r\n @click=\"handleConfirm\"\r\n >\r\n {{ confirmText }}\r\n </JButton>\r\n </template>\r\n \r\n <!-- 확인 버튼만 -->\r\n <template v-else-if=\"buttonType === 'Ok'\">\r\n <JButton\r\n :variant=\"confirmVariant\"\r\n :disabled=\"confirmDisabled\"\r\n @click=\"handleConfirm\"\r\n class=\"w-full\"\r\n >\r\n {{ confirmText }}\r\n </JButton>\r\n </template>\r\n\r\n <!-- 커스텀 푸터 슬롯 -->\r\n <slot name=\"footer\" />\r\n </DialogFooter>\r\n </DialogContent>\r\n </Dialog>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, watch } from 'vue'\r\nimport {\r\n Dialog,\r\n DialogBody,\r\n DialogContent,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogTitle,\r\n} from '@/components/shadcn'\r\nimport { JButton } from '@/components/atoms'\r\nimport { JDynamicForm } from '@/components/organisms'\r\nimport type { FormSchema } from '@/types/dynamic-form'\r\n\r\nexport interface JFormModalProps {\r\n // 모달 표시 여부\r\n open: boolean\r\n // 헤더\r\n title?: string\r\n description?: string\r\n // 다이나믹 폼 스키마 (필수)\r\n schema: FormSchema\r\n // 폼 값\r\n modelValue?: Record<string, any>\r\n // 모달 사이즈 (유동적)\r\n size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'\r\n // 버튼 설정\r\n buttonType?: 'Ok' | 'OkCancel'\r\n confirmText?: string\r\n cancelText?: string\r\n confirmVariant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'\r\n confirmDisabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFormModalProps>(), {\r\n open: false,\r\n size: 'lg',\r\n buttonType: 'OkCancel',\r\n confirmText: '확인',\r\n cancelText: '취소',\r\n confirmVariant: 'default',\r\n confirmDisabled: false,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:open': [value: boolean]\r\n 'update:modelValue': [value: Record<string, any>]\r\n 'confirm': [value: Record<string, any>]\r\n 'cancel': []\r\n 'submit': [value: Record<string, any>]\r\n 'change': [data: { field: string; value: any }]\r\n 'error': [errors: any]\r\n}>()\r\n\r\n// 다이나믹 폼 ref\r\nconst dynamicFormRef = ref<InstanceType<typeof JDynamicForm> | null>(null)\r\n\r\n// 내부 폼 값 관리\r\nconst internalFormValue = ref<Record<string, any>>(props.modelValue || {})\r\n// 확인 버튼 클릭 이후 검증 성공 시점에만 confirm 이벤트를 방출하기 위한 플래그\r\nconst isConfirming = ref<boolean>(false)\r\n\r\n// 사이즈 클래스 계산\r\nconst sizeClass = computed(() => {\r\n // Dialog.vue에 기본 max-w-lg가 하드코딩되어 있어 !max-w-*로 오버라이드\r\n const sizeMap: Record<NonNullable<JFormModalProps['size']>, string> = {\r\n 'sm': '!max-w-sm',\r\n 'md': '!max-w-md',\r\n 'lg': '!max-w-2xl',\r\n 'xl': '!max-w-4xl',\r\n '2xl': '!max-w-6xl',\r\n 'full': '!max-w-[95vw]'\r\n }\r\n return sizeMap[props.size]\r\n})\r\n\r\n// props.modelValue 변경 감지\r\nwatch(() => props.modelValue, (newValue) => {\r\n if (newValue != null) {\r\n internalFormValue.value = { ...newValue }\r\n } else {\r\n internalFormValue.value = {}\r\n }\r\n}, { deep: true })\r\n\r\n// 폼 변경 핸들러\r\nconst handleFormChange = (value: Record<string, any>) => {\r\n internalFormValue.value = value\r\n emit('update:modelValue', value)\r\n}\r\n\r\n// 필드 변경 핸들러\r\nconst handleFieldChange = (data: { field: string; value: any }) => {\r\n emit('change', data)\r\n}\r\n\r\n// 폼 제출 핸들러\r\nconst handleFormSubmit = (value: Record<string, any>) => {\r\n emit('submit', value)\r\n if (isConfirming.value) {\r\n emit('confirm', value)\r\n isConfirming.value = false\r\n }\r\n}\r\n\r\n// 폼 에러 핸들러\r\nconst handleFormError = (errors: any) => {\r\n emit('error', errors)\r\n // 검증 실패 시 confirm 방출을 차단\r\n isConfirming.value = false\r\n}\r\n\r\n// 모달 열기/닫기 핸들러\r\nconst onOpenChange = (value: boolean) => {\r\n emit('update:open', value)\r\n}\r\n\r\n// 확인 버튼 핸들러\r\nconst handleConfirm = () => {\r\n // 폼 제출 트리거 (검증 성공 시점에 handleFormSubmit에서 confirm 방출)\r\n isConfirming.value = true\r\n if (dynamicFormRef.value) {\r\n dynamicFormRef.value.submit()\r\n }\r\n}\r\n\r\n// 취소 버튼 핸들러\r\nconst handleCancel = () => {\r\n emit('cancel')\r\n // 폼 리셋\r\n if (dynamicFormRef.value) {\r\n dynamicFormRef.value.reset()\r\n }\r\n}\r\n\r\n// 외부에서 접근 가능한 메서드\r\ndefineExpose({\r\n reset: () => dynamicFormRef.value?.reset(),\r\n submit: () => dynamicFormRef.value?.submit(),\r\n formState: computed(() => dynamicFormRef.value?.formState),\r\n})\r\n</script>\r\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","internalFormValue","isConfirming","sizeClass","computed","watch","newValue","handleFormChange","value","handleFieldChange","data","handleFormSubmit","handleFormError","errors","onOpenChange","handleConfirm","handleCancel","__expose","_createBlock","_unref","Dialog","_createVNode","DialogContent","DialogHeader","DialogTitle","DialogBody","_createElementVNode","_hoisted_1","_createElementBlock","_hoisted_2","_toDisplayString","JDynamicForm","_renderSlot","_ctx","DialogFooter","_Fragment","JButton"],"mappings":"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"}
1
+ {"version":3,"file":"JFormModal.vue.cjs","sources":["../../../../src/components/organisms/JFormModal.vue"],"sourcesContent":["<template>\r\n <Dialog\r\n :open=\"open\"\r\n @update:open=\"onOpenChange\"\r\n :class=\"sizeClass\"\r\n >\r\n <DialogContent>\r\n <!-- Header -->\r\n <DialogHeader v-if=\"title\" class=\"bg-muted/50 border-b\">\r\n <DialogTitle>\r\n {{ title }}\r\n </DialogTitle>\r\n </DialogHeader>\r\n\r\n <!-- Body -->\r\n <DialogBody class=\"max-h-[70vh] overflow-y-auto\">\r\n <div class=\"space-y-4\">\r\n <!-- Description -->\r\n <p v-if=\"description\" class=\"text-sm text-muted-foreground\">\r\n {{ description }}\r\n </p>\r\n\r\n <!-- JDynamicForm -->\r\n <JDynamicForm\r\n ref=\"dynamicFormRef\"\r\n :schema=\"schema\"\r\n :model-value=\"internalFormValue\"\r\n @update:model-value=\"handleFormChange\"\r\n @submit=\"handleFormSubmit\"\r\n @change=\"handleFieldChange\"\r\n @error=\"handleFormError\"\r\n />\r\n\r\n <!-- 커스텀 컨텐츠 슬롯 -->\r\n <slot name=\"body\" />\r\n </div>\r\n </DialogBody>\r\n\r\n <!-- Footer -->\r\n <DialogFooter>\r\n <!-- 확인/취소 버튼 -->\r\n <template v-if=\"buttonType === 'OkCancel'\">\r\n <JButton\r\n variant=\"outline\"\r\n @click=\"handleCancel\"\r\n >\r\n {{ cancelText }}\r\n </JButton>\r\n <JButton\r\n :variant=\"confirmVariant\"\r\n :disabled=\"confirmDisabled\"\r\n @click=\"handleConfirm\"\r\n >\r\n {{ confirmText }}\r\n </JButton>\r\n </template>\r\n \r\n <!-- 확인 버튼만 -->\r\n <template v-else-if=\"buttonType === 'Ok'\">\r\n <JButton\r\n :variant=\"confirmVariant\"\r\n :disabled=\"confirmDisabled\"\r\n @click=\"handleConfirm\"\r\n class=\"w-full\"\r\n >\r\n {{ confirmText }}\r\n </JButton>\r\n </template>\r\n\r\n <!-- 커스텀 푸터 슬롯 -->\r\n <slot name=\"footer\" />\r\n </DialogFooter>\r\n </DialogContent>\r\n </Dialog>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, ref, watch } from 'vue'\r\nimport {\r\n Dialog,\r\n DialogBody,\r\n DialogContent,\r\n DialogFooter,\r\n DialogHeader,\r\n DialogTitle,\r\n} from '@/components/shadcn'\r\nimport { JButton } from '@/components/atoms'\r\nimport { JDynamicForm } from '@/components/organisms'\r\nimport type { FormSchema } from '@/types/dynamic-form'\r\n\r\nexport interface JFormModalProps {\r\n // 모달 표시 여부\r\n open: boolean\r\n // 헤더\r\n title?: string\r\n description?: string\r\n // 다이나믹 폼 스키마 (필수)\r\n schema: FormSchema\r\n // 폼 값\r\n modelValue?: Record<string, any>\r\n // 모달 사이즈 (유동적)\r\n size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'\r\n // 버튼 설정\r\n buttonType?: 'Ok' | 'OkCancel'\r\n confirmText?: string\r\n cancelText?: string\r\n confirmVariant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'\r\n confirmDisabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<JFormModalProps>(), {\r\n open: false,\r\n size: 'lg',\r\n buttonType: 'OkCancel',\r\n confirmText: '확인',\r\n cancelText: '취소',\r\n confirmVariant: 'default',\r\n confirmDisabled: false,\r\n})\r\n\r\nconst emit = defineEmits<{\r\n 'update:open': [value: boolean]\r\n 'update:modelValue': [value: Record<string, any>]\r\n 'confirm': [value: Record<string, any>]\r\n 'cancel': []\r\n 'submit': [value: Record<string, any>]\r\n 'change': [data: { field: string; value: any }]\r\n 'error': [errors: any]\r\n}>()\r\n\r\n// 다이나믹 폼 ref\r\nconst dynamicFormRef = ref<InstanceType<typeof JDynamicForm> | null>(null)\r\n\r\n// 내부 폼 값 관리\r\nconst internalFormValue = ref<Record<string, any>>(props.modelValue || {})\r\n// 확인 버튼 클릭 이후 검증 성공 시점에만 confirm 이벤트를 방출하기 위한 플래그\r\nconst isConfirming = ref<boolean>(false)\r\n\r\n// 사이즈 클래스 계산\r\nconst sizeClass = computed(() => {\r\n // Dialog.vue에 기본 max-w-lg가 하드코딩되어 있어 !max-w-*로 오버라이드\r\n const sizeMap: Record<NonNullable<JFormModalProps['size']>, string> = {\r\n 'sm': '!max-w-sm',\r\n 'md': '!max-w-md',\r\n 'lg': '!max-w-2xl',\r\n 'xl': '!max-w-4xl',\r\n '2xl': '!max-w-6xl',\r\n 'full': '!max-w-[95vw]'\r\n }\r\n return sizeMap[props.size]\r\n})\r\n\r\n// props.modelValue 변경 감지\r\nwatch(() => props.modelValue, (newValue) => {\r\n if (newValue != null) {\r\n internalFormValue.value = { ...newValue }\r\n } else {\r\n internalFormValue.value = {}\r\n }\r\n}, { deep: true })\r\n\r\n// 폼 변경 핸들러\r\nconst handleFormChange = (value: Record<string, any>) => {\r\n internalFormValue.value = value\r\n emit('update:modelValue', value)\r\n}\r\n\r\n// 필드 변경 핸들러\r\nconst handleFieldChange = (data: { field: string; value: any }) => {\r\n emit('change', data)\r\n}\r\n\r\n// 폼 제출 핸들러\r\nconst handleFormSubmit = (value: Record<string, any>) => {\r\n emit('submit', value)\r\n if (isConfirming.value) {\r\n emit('confirm', value)\r\n isConfirming.value = false\r\n }\r\n}\r\n\r\n// 폼 에러 핸들러\r\nconst handleFormError = (errors: any) => {\r\n emit('error', errors)\r\n // 검증 실패 시 confirm 방출을 차단\r\n isConfirming.value = false\r\n}\r\n\r\n// 모달 열기/닫기 핸들러\r\nconst onOpenChange = (value: boolean) => {\r\n emit('update:open', value)\r\n}\r\n\r\n// 확인 버튼 핸들러\r\nconst handleConfirm = () => {\r\n // 폼 제출 트리거 (검증 성공 시점에 handleFormSubmit에서 confirm 방출)\r\n isConfirming.value = true\r\n if (dynamicFormRef.value) {\r\n dynamicFormRef.value.submit()\r\n }\r\n}\r\n\r\n// 취소 버튼 핸들러\r\nconst handleCancel = () => {\r\n emit('cancel')\r\n // 폼 리셋\r\n if (dynamicFormRef.value) {\r\n dynamicFormRef.value.reset()\r\n }\r\n}\r\n\r\n// 외부에서 접근 가능한 메서드\r\ndefineExpose({\r\n reset: () => dynamicFormRef.value?.reset(),\r\n submit: () => dynamicFormRef.value?.submit(),\r\n formState: computed(() => dynamicFormRef.value?.formState),\r\n})\r\n</script>\r\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","internalFormValue","isConfirming","sizeClass","computed","watch","newValue","handleFormChange","value","handleFieldChange","data","handleFormSubmit","handleFormError","errors","onOpenChange","handleConfirm","handleCancel","__expose","_createBlock","_unref","Dialog","_createVNode","DialogContent","DialogHeader","DialogTitle","DialogBody","_createElementVNode","_hoisted_1","_createElementBlock","_hoisted_2","_toDisplayString","JDynamicForm","_renderSlot","_ctx","DialogFooter","_Fragment","JButton"],"mappings":"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"}