@j-solution/components 1.9.0 → 1.9.2
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.
- package/README.md +6 -5
- package/assets/{jwms-portal-frontend-Ct2Tc7yj.css → jwms-portal-frontend-DQAXe8y7.css} +1 -1
- package/assets/styles/j-components.css +1 -1
- package/assets/styles/main.css +29 -29
- package/components/atoms/JAvatar.vue.cjs.map +1 -1
- package/components/atoms/JAvatar.vue.js.map +1 -1
- package/components/atoms/JBadge.vue.cjs.map +1 -1
- package/components/atoms/JBadge.vue.js.map +1 -1
- package/components/atoms/JCombo.vue.cjs.map +1 -1
- package/components/atoms/JCombo.vue.js.map +1 -1
- package/components/atoms/JDatepicker.vue.cjs.map +1 -1
- package/components/atoms/JDatepicker.vue.js.map +1 -1
- package/components/atoms/JDivider.vue.cjs.map +1 -1
- package/components/atoms/JDivider.vue.js.map +1 -1
- package/components/atoms/JEditor.vue.cjs +1 -1
- package/components/atoms/JEditor.vue.js +2 -2
- package/components/atoms/JEditor.vue2.cjs.map +1 -1
- package/components/atoms/JEditor.vue2.js.map +1 -1
- package/components/atoms/JGrid.vue.cjs +1 -1
- package/components/atoms/JGrid.vue.js +1 -1
- package/components/atoms/JGrid.vue2.cjs +1 -1
- package/components/atoms/JGrid.vue2.cjs.map +1 -1
- package/components/atoms/JGrid.vue2.js +81 -85
- package/components/atoms/JGrid.vue2.js.map +1 -1
- package/components/atoms/JIcon.vue.cjs.map +1 -1
- package/components/atoms/JIcon.vue.js.map +1 -1
- package/components/atoms/JImage.vue.cjs.map +1 -1
- package/components/atoms/JImage.vue.js.map +1 -1
- package/components/atoms/JKbd.vue.cjs.map +1 -1
- package/components/atoms/JKbd.vue.js.map +1 -1
- package/components/atoms/JPreview.vue.cjs +1 -1
- package/components/atoms/JPreview.vue.js +7 -7
- package/components/atoms/JPreview.vue2.cjs.map +1 -1
- package/components/atoms/JPreview.vue2.js.map +1 -1
- package/components/atoms/JProgress.vue.cjs.map +1 -1
- package/components/atoms/JProgress.vue.js.map +1 -1
- package/components/atoms/JRadio.vue.cjs.map +1 -1
- package/components/atoms/JRadio.vue.js.map +1 -1
- package/components/atoms/JSearchCombo.vue.cjs.map +1 -1
- package/components/atoms/JSearchCombo.vue.js.map +1 -1
- package/components/atoms/JSectionTitle.vue2.cjs +1 -1
- package/components/atoms/JSectionTitle.vue2.cjs.map +1 -1
- package/components/atoms/JSectionTitle.vue2.js +5 -8
- package/components/atoms/JSectionTitle.vue2.js.map +1 -1
- package/components/atoms/JSpinner.vue.cjs.map +1 -1
- package/components/atoms/JSpinner.vue.js.map +1 -1
- package/components/atoms/JToast.vue.cjs.map +1 -1
- package/components/atoms/JToast.vue.js.map +1 -1
- package/components/atoms/JTooltip.vue.cjs.map +1 -1
- package/components/atoms/JTooltip.vue.js.map +1 -1
- package/components/molecules/JAlert.vue.cjs +1 -1
- package/components/molecules/JAlert.vue.cjs.map +1 -1
- package/components/molecules/JAlert.vue.js +2 -5
- package/components/molecules/JAlert.vue.js.map +1 -1
- package/components/molecules/JBreadcrumb.vue.cjs.map +1 -1
- package/components/molecules/JBreadcrumb.vue.js.map +1 -1
- package/components/molecules/JEmptyState.vue2.cjs +1 -1
- package/components/molecules/JEmptyState.vue2.cjs.map +1 -1
- package/components/molecules/JEmptyState.vue2.js +15 -18
- package/components/molecules/JEmptyState.vue2.js.map +1 -1
- package/components/molecules/JFormField.vue2.cjs +1 -1
- package/components/molecules/JFormField.vue2.cjs.map +1 -1
- package/components/molecules/JFormField.vue2.js +2 -5
- package/components/molecules/JFormField.vue2.js.map +1 -1
- package/components/molecules/JTitlebar.vue.cjs +1 -1
- package/components/molecules/JTitlebar.vue.cjs.map +1 -1
- package/components/molecules/JTitlebar.vue.js +16 -19
- package/components/molecules/JTitlebar.vue.js.map +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs +1 -1
- package/components/organisms/JDynamicForm.vue2.cjs.map +1 -1
- package/components/organisms/JDynamicForm.vue2.js +2 -5
- package/components/organisms/JDynamicForm.vue2.js.map +1 -1
- package/components/organisms/JFilterBar.vue.cjs +1 -1
- package/components/organisms/JFilterBar.vue.js +2 -2
- package/components/organisms/JFilterBar.vue2.cjs.map +1 -1
- package/components/organisms/JFilterBar.vue2.js.map +1 -1
- package/components/organisms/JFormModal.vue.cjs +1 -1
- package/components/organisms/JFormModal.vue.cjs.map +1 -1
- package/components/organisms/JFormModal.vue.js +14 -17
- package/components/organisms/JFormModal.vue.js.map +1 -1
- package/components/organisms/JModal.vue.cjs +1 -1
- package/components/organisms/JModal.vue.cjs.map +1 -1
- package/components/organisms/JModal.vue.js +2 -5
- package/components/organisms/JModal.vue.js.map +1 -1
- package/components/organisms/JSearchPanel.vue2.cjs +1 -1
- package/components/organisms/JSearchPanel.vue2.cjs.map +1 -1
- package/components/organisms/JSearchPanel.vue2.js +20 -23
- package/components/organisms/JSearchPanel.vue2.js.map +1 -1
- package/components/organisms/JSidebar/JSidebar.vue.cjs.map +1 -1
- package/components/organisms/JSidebar/JSidebar.vue.js.map +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.cjs.map +1 -1
- package/components/organisms/JSidebar/JSidebarGroup.vue.js.map +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.cjs.map +1 -1
- package/components/organisms/JSidebar/JSidebarItem.vue.js.map +1 -1
- package/components/organisms/JSidebarAdvanced.vue.cjs +1 -1
- package/components/organisms/JSidebarAdvanced.vue.js +7 -7
- package/components/organisms/JSidebarAdvanced.vue2.cjs.map +1 -1
- package/components/organisms/JSidebarAdvanced.vue2.js.map +1 -1
- package/components/organisms/JSidebarSimple.vue.cjs +1 -1
- package/components/organisms/JSidebarSimple.vue.js +2 -2
- package/components/organisms/JSidebarSimple.vue2.cjs.map +1 -1
- package/components/organisms/JSidebarSimple.vue2.js.map +1 -1
- package/components/shadcn/AccordionTrigger.vue.cjs.map +1 -1
- package/components/shadcn/AccordionTrigger.vue.js.map +1 -1
- package/components/shadcn/CardDescription.vue.cjs.map +1 -1
- package/components/shadcn/CardDescription.vue.js.map +1 -1
- package/components/shadcn/CardFooter.vue.cjs.map +1 -1
- package/components/shadcn/CardFooter.vue.js.map +1 -1
- package/components/shadcn/CardTitle.vue.cjs.map +1 -1
- package/components/shadcn/CardTitle.vue.js.map +1 -1
- package/components/shadcn/Checkbox.vue.cjs.map +1 -1
- package/components/shadcn/Checkbox.vue.js.map +1 -1
- package/components/shadcn/Combobox.vue.cjs.map +1 -1
- package/components/shadcn/Combobox.vue.js.map +1 -1
- package/components/shadcn/ComboboxAnchor.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxAnchor.vue.js.map +1 -1
- package/components/shadcn/ComboboxEmpty.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxEmpty.vue.js.map +1 -1
- package/components/shadcn/ComboboxGroup.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxGroup.vue.js.map +1 -1
- package/components/shadcn/ComboboxInput.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxInput.vue.js.map +1 -1
- package/components/shadcn/ComboboxItem.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxItem.vue.js.map +1 -1
- package/components/shadcn/ComboboxList.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxList.vue.js.map +1 -1
- package/components/shadcn/ComboboxTrigger.vue.cjs.map +1 -1
- package/components/shadcn/ComboboxTrigger.vue.js.map +1 -1
- package/components/shadcn/ContextMenu.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenu.vue.js.map +1 -1
- package/components/shadcn/ContextMenuContent.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuContent.vue.js.map +1 -1
- package/components/shadcn/ContextMenuGroup.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuGroup.vue.js.map +1 -1
- package/components/shadcn/ContextMenuItem.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuItem.vue.js.map +1 -1
- package/components/shadcn/ContextMenuLabel.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuLabel.vue.js.map +1 -1
- package/components/shadcn/ContextMenuSeparator.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuSeparator.vue.js.map +1 -1
- package/components/shadcn/ContextMenuSub.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuSub.vue.js.map +1 -1
- package/components/shadcn/ContextMenuSubContent.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuSubContent.vue.js.map +1 -1
- package/components/shadcn/ContextMenuSubTrigger.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuSubTrigger.vue.js.map +1 -1
- package/components/shadcn/ContextMenuTrigger.vue.cjs.map +1 -1
- package/components/shadcn/ContextMenuTrigger.vue.js.map +1 -1
- package/components/shadcn/Field.vue.cjs.map +1 -1
- package/components/shadcn/Field.vue.js.map +1 -1
- package/components/shadcn/FieldContent.vue.cjs.map +1 -1
- package/components/shadcn/FieldContent.vue.js.map +1 -1
- package/components/shadcn/FieldDescription.vue.cjs.map +1 -1
- package/components/shadcn/FieldDescription.vue.js.map +1 -1
- package/components/shadcn/FieldError.vue.cjs.map +1 -1
- package/components/shadcn/FieldError.vue.js.map +1 -1
- package/components/shadcn/FieldGroup.vue.cjs.map +1 -1
- package/components/shadcn/FieldGroup.vue.js.map +1 -1
- package/components/shadcn/FieldLabel.vue.cjs.map +1 -1
- package/components/shadcn/FieldLabel.vue.js.map +1 -1
- package/components/shadcn/Label.vue.cjs.map +1 -1
- package/components/shadcn/Label.vue.js.map +1 -1
- package/components/shadcn/RadioGroup.vue.cjs.map +1 -1
- package/components/shadcn/RadioGroup.vue.js.map +1 -1
- package/components/shadcn/RadioGroupItem.vue.cjs.map +1 -1
- package/components/shadcn/RadioGroupItem.vue.js.map +1 -1
- package/components/shadcn/Select.vue.cjs.map +1 -1
- package/components/shadcn/Select.vue.js.map +1 -1
- package/components/shadcn/SelectContent.vue.cjs.map +1 -1
- package/components/shadcn/SelectContent.vue.js.map +1 -1
- package/components/shadcn/SelectGroup.vue.cjs.map +1 -1
- package/components/shadcn/SelectGroup.vue.js.map +1 -1
- package/components/shadcn/SelectItem.vue.cjs.map +1 -1
- package/components/shadcn/SelectItem.vue.js.map +1 -1
- package/components/shadcn/SelectLabel.vue.cjs.map +1 -1
- package/components/shadcn/SelectLabel.vue.js.map +1 -1
- package/components/shadcn/SelectScrollDownButton.vue2.cjs.map +1 -1
- package/components/shadcn/SelectScrollDownButton.vue2.js.map +1 -1
- package/components/shadcn/SelectScrollUpButton.vue2.cjs.map +1 -1
- package/components/shadcn/SelectScrollUpButton.vue2.js.map +1 -1
- package/components/shadcn/SelectValue.vue.cjs.map +1 -1
- package/components/shadcn/SelectValue.vue.js.map +1 -1
- package/components/shadcn/Separator.vue.cjs.map +1 -1
- package/components/shadcn/Separator.vue.js.map +1 -1
- package/components/shadcn/Switch.vue.cjs.map +1 -1
- package/components/shadcn/Switch.vue.js.map +1 -1
- package/components/shadcn/Tabs.vue.cjs.map +1 -1
- package/components/shadcn/Tabs.vue.js.map +1 -1
- package/components/shadcn/TabsTrigger.vue.cjs.map +1 -1
- package/components/shadcn/TabsTrigger.vue.js.map +1 -1
- package/components/shadcn/Toaster.vue.cjs.map +1 -1
- package/components/shadcn/Toaster.vue.js.map +1 -1
- package/components/shadcn/resizable/ResizableHandle.vue.cjs.map +1 -1
- package/components/shadcn/resizable/ResizableHandle.vue.js.map +1 -1
- package/components/shadcn/resizable/ResizablePanelGroup.vue.cjs.map +1 -1
- package/components/shadcn/resizable/ResizablePanelGroup.vue.js.map +1 -1
- package/lib/styleTypePreset.cjs.map +1 -1
- package/lib/styleTypePreset.js.map +1 -1
- package/lib/theme-utils.cjs.map +1 -1
- package/lib/theme-utils.js.map +1 -1
- package/package.json +1 -1
- package/tailwind.config.js +81 -81
- package/types/index.d.ts +46 -46
- package/types/sidebar.types.cjs.map +1 -1
- package/types/sidebar.types.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSearchPanel.vue2.cjs","sources":["../../../../src/components/organisms/JSearchPanel.vue"],"sourcesContent":["<template>\n <Card class=\"w-full\">\n <!-- 헤더: 제목, Badge 목록, 초기화 버튼 -->\n <CardHeader class=\"pt-4 pb-3 px-6\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-3 flex-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n @click=\"isExpanded = !isExpanded\"\n class=\"flex items-center gap-2 font-semibold hover:text-primary transition-colors\"\n >\n <ChevronDown\n :class=\"['h-4 w-4 transition-transform', isExpanded ? 'rotate-0' : '-rotate-90']\"\n />\n {{ title }}\n </button>\n <CardTitle v-else class=\"mb-0\">{{ title }}</CardTitle>\n \n <!-- 조건 Badge 목록 -->\n <div v-if=\"conditionBadges.length > 0\" class=\"flex flex-wrap items-center gap-2 ml-2\">\n <JBadge\n v-for=\"badge in conditionBadges\"\n :key=\"badge.fieldName\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1.5 pr-1\"\n >\n <span>{{ badge.label }}: {{ badge.value }}</span>\n <button\n type=\"button\"\n @click.stop=\"handleFieldReset(badge.fieldName)\"\n class=\"h-4 w-4 rounded-full hover:bg-destructive/20 hover:text-destructive transition-colors flex items-center justify-center\"\n :aria-label=\"`${badge.label} 조건 제거`\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n \n <JButton\n variant=\"outline\"\n size=\"sm\"\n @click.stop=\"handleReset\"\n >\n 초기화\n </JButton>\n </div>\n </CardHeader>\n \n <!-- 폼 내용 -->\n <CardContent v-show=\"isExpanded || !collapsible\" class=\"px-6 pb-6 pt-0\">\n <JDynamicForm\n ref=\"dynamicFormRef\"\n :schema=\"schema\"\n :model-value=\"localModelValue\"\n @update:model-value=\"handleFormValueUpdate\"\n @submit=\"handleSubmit\"\n />\n </CardContent>\n </Card>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, reactive, watch } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport { Card, CardHeader, CardTitle, CardContent } from '@/components/shadcn'\nimport { JButton, JBadge } from '@/components/atoms'\nimport JDynamicForm from './JDynamicForm.vue'\nimport type { FormSchema, DynamicFormField } from '@/types/dynamic-form'\nimport type { ComponentPublicInstance } from 'vue'\n\ninterface ConditionBadge {\n fieldName: string\n label: string\n value: string\n}\n\nexport interface JSearchPanelProps {\n /** 패널 제목 */\n title?: string\n /** JDynamicForm에 전달할 FormSchema */\n schema: FormSchema\n /** JDynamicForm의 v-model 값 */\n modelValue?: Record<string, any>\n /** 기본 접힘 상태 */\n defaultCollapsed?: boolean\n /** 접기/펼치기 가능 여부 */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<JSearchPanelProps>(), {\n title: '조회조건',\n defaultCollapsed: false,\n collapsible: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Record<string, any>]\n 'submit': [values: Record<string, any>]\n 'reset': []\n}>()\n\nconst dynamicFormRef = ref<ComponentPublicInstance & { reset: () => void } | null>(null)\nconst isExpanded = ref(!props.defaultCollapsed)\n\n// 로컬 modelValue - reactive로 관리하여 양방향 바인딩\nconst localModelValue = reactive<Record<string, any>>(props.modelValue ? { ...props.modelValue } : {})\n\n// 내부 변경인지 외부 변경인지 구분하는 플래그\nlet isInternalUpdate = false\n\n// props.modelValue 변경 시 localModelValue 동기화 (외부 변경)\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue && !isInternalUpdate) {\n // 기존 키 중 새 값에 없는 것은 삭제\n Object.keys(localModelValue).forEach(key => {\n if (!(key in newValue)) {\n delete localModelValue[key]\n }\n })\n // 새 값으로 업데이트\n Object.assign(localModelValue, newValue)\n }\n isInternalUpdate = false\n },\n { deep: true, immediate: true }\n)\n\n// JDynamicForm에서 값이 변경되었을 때 처리\nfunction handleFormValueUpdate(value: Record<string, any>) {\n // JDynamicForm에서 emit된 값을 localModelValue에 반영\n isInternalUpdate = true\n Object.assign(localModelValue, value)\n // 상위로 emit\n emit('update:modelValue', { ...value })\n}\n\n// 모든 필드 가져오기\nconst allFields = computed((): DynamicFormField[] => {\n if (!props.schema) return []\n \n const fields: DynamicFormField[] = []\n if (props.schema.type === 'simple' && props.schema.fields) {\n fields.push(...props.schema.fields)\n } else if (props.schema.type === 'sectioned' && props.schema.sections) {\n props.schema.sections.forEach(section => {\n if (section.fields) fields.push(...section.fields)\n })\n } else if (props.schema.type === 'wizard' && props.schema.steps) {\n props.schema.steps.forEach(step => {\n if (step.fields) fields.push(...step.fields)\n })\n }\n return fields\n})\n\n// 조건 Badge 목록 생성\nconst conditionBadges = computed((): ConditionBadge[] => {\n if (!localModelValue || !props.schema) {\n return []\n }\n\n const badges: ConditionBadge[] = []\n const formState = localModelValue\n\n allFields.value.forEach((field) => {\n const value = formState[field.controlName]\n \n // 빈 값 체크 (빈 문자열, null, undefined, 'ALL', 'SELECT' 제외)\n if (\n value === undefined ||\n value === null ||\n value === '' ||\n value === 'ALL' ||\n value === 'SELECT' ||\n (field.type === 'checkbox' && value === 'N') ||\n (field.type === 'switch' && value === 'N')\n ) {\n return\n }\n\n // 필드 타입에 따라 값 표시\n let displayValue = String(value)\n\n // 콤보/검색콤보인 경우 옵션의 label 찾기\n if ((field.type === 'combo' || field.type === 'searchcombo') && field.options) {\n const option = field.options.find(opt => opt.value === value)\n if (option) {\n displayValue = option.label\n }\n }\n\n // 체크박스/스위치는 'Y'일 때만 표시\n if (field.type === 'checkbox' || field.type === 'switch') {\n if (value === 'Y') {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: field.inlineLabel || field.label\n })\n }\n } else {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: displayValue\n })\n }\n })\n\n return badges\n})\n\n// submit 핸들러\nfunction handleSubmit(values: Record<string, any>) {\n emit('submit', values)\n}\n\n// 전체 초기화 핸들러\nfunction handleReset() {\n if (dynamicFormRef.value) {\n dynamicFormRef.value.reset()\n emit('reset')\n }\n}\n\n// 필드별 초기화 핸들러\nfunction handleFieldReset(fieldName: string) {\n if (!localModelValue || !props.schema) return\n \n const field = allFields.value.find(f => f.controlName === fieldName)\n \n if (field) {\n // 필드 타입에 따라 초기값 설정 - reactive 객체 직접 수정\n if (field.type === 'checkbox' || field.type === 'switch') {\n localModelValue[fieldName] = 'N'\n } else {\n localModelValue[fieldName] = ''\n }\n \n // localModelValue 변경 후 상위로 emit하여 JDynamicForm에도 반영\n isInternalUpdate = true\n emit('update:modelValue', { ...localModelValue })\n }\n}\n</script>\n\n<style scoped>\n/* 필요시 스타일 추가 */\n</style>\n\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","isExpanded","localModelValue","reactive","isInternalUpdate","watch","newValue","key","handleFormValueUpdate","value","allFields","computed","fields","section","step","conditionBadges","badges","formState","field","displayValue","option","opt","handleSubmit","values","handleReset","handleFieldReset","fieldName","f","_createBlock","_unref","Card","_createVNode","CardHeader","_createElementVNode","_hoisted_1","_hoisted_2","_createElementBlock","_cache","$event","ChevronDown","_createTextVNode","CardTitle","_openBlock","_hoisted_3","_Fragment","_renderList","badge","JBadge","_toDisplayString","_withModifiers","X","JButton","CardContent","JDynamicForm","_vShow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JSearchPanel.vue2.cjs","sources":["../../../../src/components/organisms/JSearchPanel.vue"],"sourcesContent":["<template>\n <Card class=\"w-full\">\n <!-- 헤더: 제목, Badge 목록, 초기화 버튼 -->\n <CardHeader class=\"pt-4 pb-3 px-6\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-3 flex-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n @click=\"isExpanded = !isExpanded\"\n class=\"flex items-center gap-2 font-semibold hover:text-primary transition-colors\"\n >\n <ChevronDown\n :class=\"['h-4 w-4 transition-transform', isExpanded ? 'rotate-0' : '-rotate-90']\"\n />\n {{ title }}\n </button>\n <CardTitle v-else class=\"mb-0\">{{ title }}</CardTitle>\n \n <!-- 조건 Badge 목록 -->\n <div v-if=\"conditionBadges.length > 0\" class=\"flex flex-wrap items-center gap-2 ml-2\">\n <JBadge\n v-for=\"badge in conditionBadges\"\n :key=\"badge.fieldName\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1.5 pr-1\"\n >\n <span>{{ badge.label }}: {{ badge.value }}</span>\n <button\n type=\"button\"\n @click.stop=\"handleFieldReset(badge.fieldName)\"\n class=\"h-4 w-4 rounded-full hover:bg-destructive/20 hover:text-destructive transition-colors flex items-center justify-center\"\n :aria-label=\"`${badge.label} 조건 제거`\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n \n <JButton\n variant=\"outline\"\n size=\"sm\"\n @click.stop=\"handleReset\"\n >\n 초기화\n </JButton>\n </div>\n </CardHeader>\n \n <!-- 폼 내용 -->\n <CardContent v-show=\"isExpanded || !collapsible\" class=\"px-6 pb-6 pt-0\">\n <JDynamicForm\n ref=\"dynamicFormRef\"\n :schema=\"schema\"\n :model-value=\"localModelValue\"\n @update:model-value=\"handleFormValueUpdate\"\n @submit=\"handleSubmit\"\n />\n </CardContent>\n </Card>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, reactive, watch } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport { Card, CardHeader, CardTitle, CardContent } from '@/components/shadcn'\nimport { JButton, JBadge } from '@/components/atoms'\nimport JDynamicForm from './JDynamicForm.vue'\nimport type { FormSchema, DynamicFormField } from '@/types/dynamic-form'\nimport type { ComponentPublicInstance } from 'vue'\n\ninterface ConditionBadge {\n fieldName: string\n label: string\n value: string\n}\n\nexport interface JSearchPanelProps {\n /** 패널 제목 */\n title?: string\n /** JDynamicForm에 전달할 FormSchema */\n schema: FormSchema\n /** JDynamicForm의 v-model 값 */\n modelValue?: Record<string, any>\n /** 기본 접힘 상태 */\n defaultCollapsed?: boolean\n /** 접기/펼치기 가능 여부 */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<JSearchPanelProps>(), {\n title: '조회조건',\n defaultCollapsed: false,\n collapsible: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Record<string, any>]\n 'submit': [values: Record<string, any>]\n 'reset': []\n}>()\n\nconst dynamicFormRef = ref<ComponentPublicInstance & { reset: () => void } | null>(null)\nconst isExpanded = ref(!props.defaultCollapsed)\n\n// 로컬 modelValue - reactive로 관리하여 양방향 바인딩\nconst localModelValue = reactive<Record<string, any>>(props.modelValue ? { ...props.modelValue } : {})\n\n// 내부 변경인지 외부 변경인지 구분하는 플래그\nlet isInternalUpdate = false\n\n// props.modelValue 변경 시 localModelValue 동기화 (외부 변경)\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue && !isInternalUpdate) {\n // 기존 키 중 새 값에 없는 것은 삭제\n Object.keys(localModelValue).forEach(key => {\n if (!(key in newValue)) {\n delete localModelValue[key]\n }\n })\n // 새 값으로 업데이트\n Object.assign(localModelValue, newValue)\n }\n isInternalUpdate = false\n },\n { deep: true, immediate: true }\n)\n\n// JDynamicForm에서 값이 변경되었을 때 처리\nfunction handleFormValueUpdate(value: Record<string, any>) {\n // JDynamicForm에서 emit된 값을 localModelValue에 반영\n isInternalUpdate = true\n Object.assign(localModelValue, value)\n // 상위로 emit\n emit('update:modelValue', { ...value })\n}\n\n// 모든 필드 가져오기\nconst allFields = computed((): DynamicFormField[] => {\n if (!props.schema) return []\n \n const fields: DynamicFormField[] = []\n if (props.schema.type === 'simple' && props.schema.fields) {\n fields.push(...props.schema.fields)\n } else if (props.schema.type === 'sectioned' && props.schema.sections) {\n props.schema.sections.forEach(section => {\n if (section.fields) fields.push(...section.fields)\n })\n } else if (props.schema.type === 'wizard' && props.schema.steps) {\n props.schema.steps.forEach(step => {\n if (step.fields) fields.push(...step.fields)\n })\n }\n return fields\n})\n\n// 조건 Badge 목록 생성\nconst conditionBadges = computed((): ConditionBadge[] => {\n if (!localModelValue || !props.schema) {\n return []\n }\n\n const badges: ConditionBadge[] = []\n const formState = localModelValue\n\n allFields.value.forEach((field) => {\n const value = formState[field.controlName]\n \n // 빈 값 체크 (빈 문자열, null, undefined, 'ALL', 'SELECT' 제외)\n if (\n value === undefined ||\n value === null ||\n value === '' ||\n value === 'ALL' ||\n value === 'SELECT' ||\n (field.type === 'checkbox' && value === 'N') ||\n (field.type === 'switch' && value === 'N')\n ) {\n return\n }\n\n // 필드 타입에 따라 값 표시\n let displayValue = String(value)\n\n // 콤보/검색콤보인 경우 옵션의 label 찾기\n if ((field.type === 'combo' || field.type === 'searchcombo') && field.options) {\n const option = field.options.find(opt => opt.value === value)\n if (option) {\n displayValue = option.label\n }\n }\n\n // 체크박스/스위치는 'Y'일 때만 표시\n if (field.type === 'checkbox' || field.type === 'switch') {\n if (value === 'Y') {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: field.inlineLabel || field.label\n })\n }\n } else {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: displayValue\n })\n }\n })\n\n return badges\n})\n\n// submit 핸들러\nfunction handleSubmit(values: Record<string, any>) {\n emit('submit', values)\n}\n\n// 전체 초기화 핸들러\nfunction handleReset() {\n if (dynamicFormRef.value) {\n dynamicFormRef.value.reset()\n emit('reset')\n }\n}\n\n// 필드별 초기화 핸들러\nfunction handleFieldReset(fieldName: string) {\n if (!localModelValue || !props.schema) return\n \n const field = allFields.value.find(f => f.controlName === fieldName)\n \n if (field) {\n // 필드 타입에 따라 초기값 설정 - reactive 객체 직접 수정\n if (field.type === 'checkbox' || field.type === 'switch') {\n localModelValue[fieldName] = 'N'\n } else {\n localModelValue[fieldName] = ''\n }\n \n // localModelValue 변경 후 상위로 emit하여 JDynamicForm에도 반영\n isInternalUpdate = true\n emit('update:modelValue', { ...localModelValue })\n }\n}\n</script>\n\n<style scoped>\n/* 필요시 스타일 추가 */\n</style>\n\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","isExpanded","localModelValue","reactive","isInternalUpdate","watch","newValue","key","handleFormValueUpdate","value","allFields","computed","fields","section","step","conditionBadges","badges","formState","field","displayValue","option","opt","handleSubmit","values","handleReset","handleFieldReset","fieldName","f","_createBlock","_unref","Card","_createVNode","CardHeader","_createElementVNode","_hoisted_1","_hoisted_2","_createElementBlock","_cache","$event","ChevronDown","_createTextVNode","CardTitle","_openBlock","_hoisted_3","_Fragment","_renderList","badge","JBadge","_toDisplayString","_withModifiers","X","JButton","CardContent","JDynamicForm","_vShow"],"mappings":"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"}
|
|
@@ -18,9 +18,6 @@ import "dompurify";
|
|
|
18
18
|
import "ag-grid-vue3";
|
|
19
19
|
import "ag-grid-community";
|
|
20
20
|
import "ag-grid-enterprise";
|
|
21
|
-
/* empty css */
|
|
22
|
-
/* empty css */
|
|
23
|
-
/* empty css */
|
|
24
21
|
/* empty css */
|
|
25
22
|
/* empty css */
|
|
26
23
|
/* empty css */
|
|
@@ -33,7 +30,7 @@ import K from "../shadcn/CardContent.vue.js";
|
|
|
33
30
|
const Q = { class: "flex items-center justify-between" }, W = { class: "flex items-center gap-3 flex-1" }, Z = {
|
|
34
31
|
key: 2,
|
|
35
32
|
class: "flex flex-wrap items-center gap-2 ml-2"
|
|
36
|
-
}, ee = ["onClick", "aria-label"],
|
|
33
|
+
}, ee = ["onClick", "aria-label"], Be = /* @__PURE__ */ L({
|
|
37
34
|
__name: "JSearchPanel",
|
|
38
35
|
props: {
|
|
39
36
|
title: { default: "조회조건" },
|
|
@@ -44,13 +41,13 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
44
41
|
},
|
|
45
42
|
emits: ["update:modelValue", "submit", "reset"],
|
|
46
43
|
setup(r, { emit: E }) {
|
|
47
|
-
const
|
|
44
|
+
const o = r, u = E, b = V(null), p = V(!o.defaultCollapsed), a = z(o.modelValue ? { ...o.modelValue } : {});
|
|
48
45
|
let f = !1;
|
|
49
46
|
D(
|
|
50
|
-
() =>
|
|
47
|
+
() => o.modelValue,
|
|
51
48
|
(t) => {
|
|
52
|
-
t && !f && (Object.keys(a).forEach((
|
|
53
|
-
|
|
49
|
+
t && !f && (Object.keys(a).forEach((l) => {
|
|
50
|
+
l in t || delete a[l];
|
|
54
51
|
}), Object.assign(a, t)), f = !1;
|
|
55
52
|
},
|
|
56
53
|
{ deep: !0, immediate: !0 }
|
|
@@ -59,19 +56,19 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
59
56
|
f = !0, Object.assign(a, t), u("update:modelValue", { ...t });
|
|
60
57
|
}
|
|
61
58
|
const _ = C(() => {
|
|
62
|
-
if (!
|
|
59
|
+
if (!o.schema) return [];
|
|
63
60
|
const t = [];
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
}) :
|
|
67
|
-
|
|
61
|
+
return o.schema.type === "simple" && o.schema.fields ? t.push(...o.schema.fields) : o.schema.type === "sectioned" && o.schema.sections ? o.schema.sections.forEach((l) => {
|
|
62
|
+
l.fields && t.push(...l.fields);
|
|
63
|
+
}) : o.schema.type === "wizard" && o.schema.steps && o.schema.steps.forEach((l) => {
|
|
64
|
+
l.fields && t.push(...l.fields);
|
|
68
65
|
}), t;
|
|
69
66
|
}), k = C(() => {
|
|
70
|
-
if (!a || !
|
|
67
|
+
if (!a || !o.schema)
|
|
71
68
|
return [];
|
|
72
|
-
const t = [],
|
|
69
|
+
const t = [], l = a;
|
|
73
70
|
return _.value.forEach((e) => {
|
|
74
|
-
const s =
|
|
71
|
+
const s = l[e.controlName];
|
|
75
72
|
if (s == null || s === "" || s === "ALL" || s === "SELECT" || e.type === "checkbox" && s === "N" || e.type === "switch" && s === "N")
|
|
76
73
|
return;
|
|
77
74
|
let w = String(s);
|
|
@@ -97,11 +94,11 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
97
94
|
b.value && (b.value.reset(), u("reset"));
|
|
98
95
|
}
|
|
99
96
|
function F(t) {
|
|
100
|
-
if (!a || !
|
|
101
|
-
const
|
|
102
|
-
|
|
97
|
+
if (!a || !o.schema) return;
|
|
98
|
+
const l = _.value.find((e) => e.controlName === t);
|
|
99
|
+
l && (l.type === "checkbox" || l.type === "switch" ? a[t] = "N" : a[t] = "", f = !0, u("update:modelValue", { ...a }));
|
|
103
100
|
}
|
|
104
|
-
return (t,
|
|
101
|
+
return (t, l) => (n(), v(i(q), { class: "w-full" }, {
|
|
105
102
|
default: m(() => [
|
|
106
103
|
c(i(G), { class: "pt-4 pb-3 px-6" }, {
|
|
107
104
|
default: m(() => [
|
|
@@ -110,7 +107,7 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
110
107
|
r.collapsible ? (n(), y("button", {
|
|
111
108
|
key: 0,
|
|
112
109
|
type: "button",
|
|
113
|
-
onClick:
|
|
110
|
+
onClick: l[0] || (l[0] = (e) => p.value = !p.value),
|
|
114
111
|
class: "flex items-center gap-2 font-semibold hover:text-primary transition-colors"
|
|
115
112
|
}, [
|
|
116
113
|
c(i(A), {
|
|
@@ -153,7 +150,7 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
153
150
|
size: "sm",
|
|
154
151
|
onClick: g(B, ["stop"])
|
|
155
152
|
}, {
|
|
156
|
-
default: m(() => [...
|
|
153
|
+
default: m(() => [...l[1] || (l[1] = [
|
|
157
154
|
x(" 초기화 ", -1)
|
|
158
155
|
])]),
|
|
159
156
|
_: 1
|
|
@@ -183,6 +180,6 @@ const Q = { class: "flex items-center justify-between" }, W = { class: "flex ite
|
|
|
183
180
|
}
|
|
184
181
|
});
|
|
185
182
|
export {
|
|
186
|
-
|
|
183
|
+
Be as default
|
|
187
184
|
};
|
|
188
185
|
//# sourceMappingURL=JSearchPanel.vue2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSearchPanel.vue2.js","sources":["../../../../src/components/organisms/JSearchPanel.vue"],"sourcesContent":["<template>\n <Card class=\"w-full\">\n <!-- 헤더: 제목, Badge 목록, 초기화 버튼 -->\n <CardHeader class=\"pt-4 pb-3 px-6\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-3 flex-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n @click=\"isExpanded = !isExpanded\"\n class=\"flex items-center gap-2 font-semibold hover:text-primary transition-colors\"\n >\n <ChevronDown\n :class=\"['h-4 w-4 transition-transform', isExpanded ? 'rotate-0' : '-rotate-90']\"\n />\n {{ title }}\n </button>\n <CardTitle v-else class=\"mb-0\">{{ title }}</CardTitle>\n \n <!-- 조건 Badge 목록 -->\n <div v-if=\"conditionBadges.length > 0\" class=\"flex flex-wrap items-center gap-2 ml-2\">\n <JBadge\n v-for=\"badge in conditionBadges\"\n :key=\"badge.fieldName\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1.5 pr-1\"\n >\n <span>{{ badge.label }}: {{ badge.value }}</span>\n <button\n type=\"button\"\n @click.stop=\"handleFieldReset(badge.fieldName)\"\n class=\"h-4 w-4 rounded-full hover:bg-destructive/20 hover:text-destructive transition-colors flex items-center justify-center\"\n :aria-label=\"`${badge.label} 조건 제거`\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n \n <JButton\n variant=\"outline\"\n size=\"sm\"\n @click.stop=\"handleReset\"\n >\n 초기화\n </JButton>\n </div>\n </CardHeader>\n \n <!-- 폼 내용 -->\n <CardContent v-show=\"isExpanded || !collapsible\" class=\"px-6 pb-6 pt-0\">\n <JDynamicForm\n ref=\"dynamicFormRef\"\n :schema=\"schema\"\n :model-value=\"localModelValue\"\n @update:model-value=\"handleFormValueUpdate\"\n @submit=\"handleSubmit\"\n />\n </CardContent>\n </Card>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, reactive, watch } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport { Card, CardHeader, CardTitle, CardContent } from '@/components/shadcn'\nimport { JButton, JBadge } from '@/components/atoms'\nimport JDynamicForm from './JDynamicForm.vue'\nimport type { FormSchema, DynamicFormField } from '@/types/dynamic-form'\nimport type { ComponentPublicInstance } from 'vue'\n\ninterface ConditionBadge {\n fieldName: string\n label: string\n value: string\n}\n\nexport interface JSearchPanelProps {\n /** 패널 제목 */\n title?: string\n /** JDynamicForm에 전달할 FormSchema */\n schema: FormSchema\n /** JDynamicForm의 v-model 값 */\n modelValue?: Record<string, any>\n /** 기본 접힘 상태 */\n defaultCollapsed?: boolean\n /** 접기/펼치기 가능 여부 */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<JSearchPanelProps>(), {\n title: '조회조건',\n defaultCollapsed: false,\n collapsible: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Record<string, any>]\n 'submit': [values: Record<string, any>]\n 'reset': []\n}>()\n\nconst dynamicFormRef = ref<ComponentPublicInstance & { reset: () => void } | null>(null)\nconst isExpanded = ref(!props.defaultCollapsed)\n\n// 로컬 modelValue - reactive로 관리하여 양방향 바인딩\nconst localModelValue = reactive<Record<string, any>>(props.modelValue ? { ...props.modelValue } : {})\n\n// 내부 변경인지 외부 변경인지 구분하는 플래그\nlet isInternalUpdate = false\n\n// props.modelValue 변경 시 localModelValue 동기화 (외부 변경)\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue && !isInternalUpdate) {\n // 기존 키 중 새 값에 없는 것은 삭제\n Object.keys(localModelValue).forEach(key => {\n if (!(key in newValue)) {\n delete localModelValue[key]\n }\n })\n // 새 값으로 업데이트\n Object.assign(localModelValue, newValue)\n }\n isInternalUpdate = false\n },\n { deep: true, immediate: true }\n)\n\n// JDynamicForm에서 값이 변경되었을 때 처리\nfunction handleFormValueUpdate(value: Record<string, any>) {\n // JDynamicForm에서 emit된 값을 localModelValue에 반영\n isInternalUpdate = true\n Object.assign(localModelValue, value)\n // 상위로 emit\n emit('update:modelValue', { ...value })\n}\n\n// 모든 필드 가져오기\nconst allFields = computed((): DynamicFormField[] => {\n if (!props.schema) return []\n \n const fields: DynamicFormField[] = []\n if (props.schema.type === 'simple' && props.schema.fields) {\n fields.push(...props.schema.fields)\n } else if (props.schema.type === 'sectioned' && props.schema.sections) {\n props.schema.sections.forEach(section => {\n if (section.fields) fields.push(...section.fields)\n })\n } else if (props.schema.type === 'wizard' && props.schema.steps) {\n props.schema.steps.forEach(step => {\n if (step.fields) fields.push(...step.fields)\n })\n }\n return fields\n})\n\n// 조건 Badge 목록 생성\nconst conditionBadges = computed((): ConditionBadge[] => {\n if (!localModelValue || !props.schema) {\n return []\n }\n\n const badges: ConditionBadge[] = []\n const formState = localModelValue\n\n allFields.value.forEach((field) => {\n const value = formState[field.controlName]\n \n // 빈 값 체크 (빈 문자열, null, undefined, 'ALL', 'SELECT' 제외)\n if (\n value === undefined ||\n value === null ||\n value === '' ||\n value === 'ALL' ||\n value === 'SELECT' ||\n (field.type === 'checkbox' && value === 'N') ||\n (field.type === 'switch' && value === 'N')\n ) {\n return\n }\n\n // 필드 타입에 따라 값 표시\n let displayValue = String(value)\n\n // 콤보/검색콤보인 경우 옵션의 label 찾기\n if ((field.type === 'combo' || field.type === 'searchcombo') && field.options) {\n const option = field.options.find(opt => opt.value === value)\n if (option) {\n displayValue = option.label\n }\n }\n\n // 체크박스/스위치는 'Y'일 때만 표시\n if (field.type === 'checkbox' || field.type === 'switch') {\n if (value === 'Y') {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: field.inlineLabel || field.label\n })\n }\n } else {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: displayValue\n })\n }\n })\n\n return badges\n})\n\n// submit 핸들러\nfunction handleSubmit(values: Record<string, any>) {\n emit('submit', values)\n}\n\n// 전체 초기화 핸들러\nfunction handleReset() {\n if (dynamicFormRef.value) {\n dynamicFormRef.value.reset()\n emit('reset')\n }\n}\n\n// 필드별 초기화 핸들러\nfunction handleFieldReset(fieldName: string) {\n if (!localModelValue || !props.schema) return\n \n const field = allFields.value.find(f => f.controlName === fieldName)\n \n if (field) {\n // 필드 타입에 따라 초기값 설정 - reactive 객체 직접 수정\n if (field.type === 'checkbox' || field.type === 'switch') {\n localModelValue[fieldName] = 'N'\n } else {\n localModelValue[fieldName] = ''\n }\n \n // localModelValue 변경 후 상위로 emit하여 JDynamicForm에도 반영\n isInternalUpdate = true\n emit('update:modelValue', { ...localModelValue })\n }\n}\n</script>\n\n<style scoped>\n/* 필요시 스타일 추가 */\n</style>\n\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","isExpanded","localModelValue","reactive","isInternalUpdate","watch","newValue","key","handleFormValueUpdate","value","allFields","computed","fields","section","step","conditionBadges","badges","formState","field","displayValue","option","opt","handleSubmit","values","handleReset","handleFieldReset","fieldName","f","_createBlock","_unref","Card","_createVNode","CardHeader","_createElementVNode","_hoisted_1","_hoisted_2","_createElementBlock","_cache","$event","ChevronDown","_createTextVNode","CardTitle","_openBlock","_hoisted_3","_Fragment","_renderList","badge","JBadge","_toDisplayString","_withModifiers","X","JButton","CardContent","JDynamicForm","_vShow"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JSearchPanel.vue2.js","sources":["../../../../src/components/organisms/JSearchPanel.vue"],"sourcesContent":["<template>\n <Card class=\"w-full\">\n <!-- 헤더: 제목, Badge 목록, 초기화 버튼 -->\n <CardHeader class=\"pt-4 pb-3 px-6\">\n <div class=\"flex items-center justify-between\">\n <div class=\"flex items-center gap-3 flex-1\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n @click=\"isExpanded = !isExpanded\"\n class=\"flex items-center gap-2 font-semibold hover:text-primary transition-colors\"\n >\n <ChevronDown\n :class=\"['h-4 w-4 transition-transform', isExpanded ? 'rotate-0' : '-rotate-90']\"\n />\n {{ title }}\n </button>\n <CardTitle v-else class=\"mb-0\">{{ title }}</CardTitle>\n \n <!-- 조건 Badge 목록 -->\n <div v-if=\"conditionBadges.length > 0\" class=\"flex flex-wrap items-center gap-2 ml-2\">\n <JBadge\n v-for=\"badge in conditionBadges\"\n :key=\"badge.fieldName\"\n variant=\"secondary\"\n size=\"sm\"\n class=\"flex items-center gap-1.5 pr-1\"\n >\n <span>{{ badge.label }}: {{ badge.value }}</span>\n <button\n type=\"button\"\n @click.stop=\"handleFieldReset(badge.fieldName)\"\n class=\"h-4 w-4 rounded-full hover:bg-destructive/20 hover:text-destructive transition-colors flex items-center justify-center\"\n :aria-label=\"`${badge.label} 조건 제거`\"\n >\n <X class=\"h-3 w-3\" />\n </button>\n </JBadge>\n </div>\n </div>\n \n <JButton\n variant=\"outline\"\n size=\"sm\"\n @click.stop=\"handleReset\"\n >\n 초기화\n </JButton>\n </div>\n </CardHeader>\n \n <!-- 폼 내용 -->\n <CardContent v-show=\"isExpanded || !collapsible\" class=\"px-6 pb-6 pt-0\">\n <JDynamicForm\n ref=\"dynamicFormRef\"\n :schema=\"schema\"\n :model-value=\"localModelValue\"\n @update:model-value=\"handleFormValueUpdate\"\n @submit=\"handleSubmit\"\n />\n </CardContent>\n </Card>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, reactive, watch } from 'vue'\nimport { ChevronDown, X } from 'lucide-vue-next'\nimport { Card, CardHeader, CardTitle, CardContent } from '@/components/shadcn'\nimport { JButton, JBadge } from '@/components/atoms'\nimport JDynamicForm from './JDynamicForm.vue'\nimport type { FormSchema, DynamicFormField } from '@/types/dynamic-form'\nimport type { ComponentPublicInstance } from 'vue'\n\ninterface ConditionBadge {\n fieldName: string\n label: string\n value: string\n}\n\nexport interface JSearchPanelProps {\n /** 패널 제목 */\n title?: string\n /** JDynamicForm에 전달할 FormSchema */\n schema: FormSchema\n /** JDynamicForm의 v-model 값 */\n modelValue?: Record<string, any>\n /** 기본 접힘 상태 */\n defaultCollapsed?: boolean\n /** 접기/펼치기 가능 여부 */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<JSearchPanelProps>(), {\n title: '조회조건',\n defaultCollapsed: false,\n collapsible: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Record<string, any>]\n 'submit': [values: Record<string, any>]\n 'reset': []\n}>()\n\nconst dynamicFormRef = ref<ComponentPublicInstance & { reset: () => void } | null>(null)\nconst isExpanded = ref(!props.defaultCollapsed)\n\n// 로컬 modelValue - reactive로 관리하여 양방향 바인딩\nconst localModelValue = reactive<Record<string, any>>(props.modelValue ? { ...props.modelValue } : {})\n\n// 내부 변경인지 외부 변경인지 구분하는 플래그\nlet isInternalUpdate = false\n\n// props.modelValue 변경 시 localModelValue 동기화 (외부 변경)\nwatch(\n () => props.modelValue,\n (newValue) => {\n if (newValue && !isInternalUpdate) {\n // 기존 키 중 새 값에 없는 것은 삭제\n Object.keys(localModelValue).forEach(key => {\n if (!(key in newValue)) {\n delete localModelValue[key]\n }\n })\n // 새 값으로 업데이트\n Object.assign(localModelValue, newValue)\n }\n isInternalUpdate = false\n },\n { deep: true, immediate: true }\n)\n\n// JDynamicForm에서 값이 변경되었을 때 처리\nfunction handleFormValueUpdate(value: Record<string, any>) {\n // JDynamicForm에서 emit된 값을 localModelValue에 반영\n isInternalUpdate = true\n Object.assign(localModelValue, value)\n // 상위로 emit\n emit('update:modelValue', { ...value })\n}\n\n// 모든 필드 가져오기\nconst allFields = computed((): DynamicFormField[] => {\n if (!props.schema) return []\n \n const fields: DynamicFormField[] = []\n if (props.schema.type === 'simple' && props.schema.fields) {\n fields.push(...props.schema.fields)\n } else if (props.schema.type === 'sectioned' && props.schema.sections) {\n props.schema.sections.forEach(section => {\n if (section.fields) fields.push(...section.fields)\n })\n } else if (props.schema.type === 'wizard' && props.schema.steps) {\n props.schema.steps.forEach(step => {\n if (step.fields) fields.push(...step.fields)\n })\n }\n return fields\n})\n\n// 조건 Badge 목록 생성\nconst conditionBadges = computed((): ConditionBadge[] => {\n if (!localModelValue || !props.schema) {\n return []\n }\n\n const badges: ConditionBadge[] = []\n const formState = localModelValue\n\n allFields.value.forEach((field) => {\n const value = formState[field.controlName]\n \n // 빈 값 체크 (빈 문자열, null, undefined, 'ALL', 'SELECT' 제외)\n if (\n value === undefined ||\n value === null ||\n value === '' ||\n value === 'ALL' ||\n value === 'SELECT' ||\n (field.type === 'checkbox' && value === 'N') ||\n (field.type === 'switch' && value === 'N')\n ) {\n return\n }\n\n // 필드 타입에 따라 값 표시\n let displayValue = String(value)\n\n // 콤보/검색콤보인 경우 옵션의 label 찾기\n if ((field.type === 'combo' || field.type === 'searchcombo') && field.options) {\n const option = field.options.find(opt => opt.value === value)\n if (option) {\n displayValue = option.label\n }\n }\n\n // 체크박스/스위치는 'Y'일 때만 표시\n if (field.type === 'checkbox' || field.type === 'switch') {\n if (value === 'Y') {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: field.inlineLabel || field.label\n })\n }\n } else {\n badges.push({\n fieldName: field.controlName,\n label: field.label,\n value: displayValue\n })\n }\n })\n\n return badges\n})\n\n// submit 핸들러\nfunction handleSubmit(values: Record<string, any>) {\n emit('submit', values)\n}\n\n// 전체 초기화 핸들러\nfunction handleReset() {\n if (dynamicFormRef.value) {\n dynamicFormRef.value.reset()\n emit('reset')\n }\n}\n\n// 필드별 초기화 핸들러\nfunction handleFieldReset(fieldName: string) {\n if (!localModelValue || !props.schema) return\n \n const field = allFields.value.find(f => f.controlName === fieldName)\n \n if (field) {\n // 필드 타입에 따라 초기값 설정 - reactive 객체 직접 수정\n if (field.type === 'checkbox' || field.type === 'switch') {\n localModelValue[fieldName] = 'N'\n } else {\n localModelValue[fieldName] = ''\n }\n \n // localModelValue 변경 후 상위로 emit하여 JDynamicForm에도 반영\n isInternalUpdate = true\n emit('update:modelValue', { ...localModelValue })\n }\n}\n</script>\n\n<style scoped>\n/* 필요시 스타일 추가 */\n</style>\n\n"],"names":["props","__props","emit","__emit","dynamicFormRef","ref","isExpanded","localModelValue","reactive","isInternalUpdate","watch","newValue","key","handleFormValueUpdate","value","allFields","computed","fields","section","step","conditionBadges","badges","formState","field","displayValue","option","opt","handleSubmit","values","handleReset","handleFieldReset","fieldName","f","_createBlock","_unref","Card","_createVNode","CardHeader","_createElementVNode","_hoisted_1","_hoisted_2","_createElementBlock","_cache","$event","ChevronDown","_createTextVNode","CardTitle","_openBlock","_hoisted_3","_Fragment","_renderList","badge","JBadge","_toDisplayString","_withModifiers","X","JButton","CardContent","JDynamicForm","_vShow"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebar.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, provide, watch, reactive } from 'vue'\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JInput from '@/components/atoms/JInput.vue'\nimport JSidebarGroup from './JSidebarGroup.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebar - 통합 사이드바 컴포넌트\n *\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\n */\n\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 트리 데이터 */\n items: SidebarMenuItem[]\n /** 접힘 상태 (v-model:collapsed) */\n collapsed?: boolean\n /** 현재 활성 경로 */\n activePath?: string\n /** 펼침 너비 */\n width?: string\n /** 접힘 너비 */\n collapsedWidth?: string\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\n storageKey?: string\n /** 검색 표시 여부 */\n showSearch?: boolean\n /** 즐겨찾기 섹션 표시 여부 */\n showFavorites?: boolean\n }>(),\n {\n collapsed: false,\n width: '220px',\n collapsedWidth: '56px',\n showSearch: true,\n showFavorites: true,\n },\n)\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\n// ── 즐겨찾기 (localStorage) ──\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\n\nfunction loadFavorites(): string[] {\n if (!props.storageKey) return []\n try {\n const raw = localStorage.getItem(props.storageKey)\n return raw ? JSON.parse(raw) : []\n } catch {\n return []\n }\n}\n\nfunction saveFavorites() {\n if (!props.storageKey) return\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\n}\n\nfunction toggleFavorite(id: string) {\n if (favorites.value.has(id)) {\n favorites.value.delete(id)\n } else {\n favorites.value.add(id)\n }\n favorites.value = new Set(favorites.value) // trigger reactivity\n saveFavorites()\n}\n\n// ── provide/inject 상태 ──\nconst sidebarState = reactive<SidebarState>({\n collapsed: props.collapsed,\n activePath: props.activePath,\n favorites: favorites.value,\n toggleFavorite,\n})\n\n// props 변경 시 state 동기화\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\nwatch(favorites, (v) => { sidebarState.favorites = v })\n\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\n\n// ── 검색 ──\nconst searchQuery = ref('')\n\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\nconst favoriteItems = computed(() => {\n if (!props.storageKey || favorites.value.size === 0) return []\n const result: SidebarMenuItem[] = []\n const flatten = (items: SidebarMenuItem[]) => {\n for (const item of items) {\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\n result.push(item)\n }\n if (item.children) flatten(item.children)\n }\n }\n flatten(props.items)\n return result\n})\n\n/** 검색 필터링 (재귀) */\nconst filteredItems = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return props.items\n\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\n const result: SidebarMenuItem[] = []\n for (const item of items) {\n const matchLabel = item.label.toLowerCase().includes(q)\n const filteredChildren = item.children ? filter(item.children) : undefined\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\n result.push({ ...item, children: filteredChildren })\n }\n }\n return result\n }\n return filter(props.items)\n})\n\n/** 즐겨찾기 검색 필터링 */\nconst filteredFavorites = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return favoriteItems.value\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\n})\n\n// ── 즐겨찾기 그룹 펼침 ──\nconst favoritesExpanded = ref(true)\n\n// ── 이벤트 핸들러 ──\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\nconst toggleCollapsed = () => {\n emit('update:collapsed', !props.collapsed)\n}\n\n// ── 사이드바 너비 ──\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\n</script>\n\n<template>\n <aside\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\n :style=\"{ width: sidebarWidth }\"\n >\n <!-- 검색바 (펼침 + showSearch) -->\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\n <div class=\"relative\">\n <JIcon\n name=\"search\"\n size=\"sm\"\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <JInput\n v-model=\"searchQuery\"\n placeholder=\"메뉴 검색...\"\n class=\"pl-8 h-7 text-xs\"\n />\n </div>\n </div>\n\n <!-- 메뉴 영역 -->\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\n <!-- 즐겨찾기 섹션 -->\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\n <!-- 펼침: 즐겨찾기 그룹 -->\n <template v-if=\"!collapsed\">\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\n @click=\"favoritesExpanded = !favoritesExpanded\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n favoritesExpanded && 'rotate-90'\n )\"\n />\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\n <span class=\"flex-1 text-left\">즐겨찾기</span>\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\n </button>\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden\">\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </div>\n </div>\n </template>\n <!-- 접힘: 별 아이콘 + 구분선 -->\n <template v-else>\n <div class=\"flex justify-center py-1\">\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\n </div>\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-c-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n <div class=\"h-px bg-border mx-2 my-1\" />\n </template>\n\n <!-- 메인 메뉴 -->\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\n <JSidebarGroup\n v-if=\"item.menuType === 'F'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n <JSidebarItem\n v-else-if=\"item.menuType === 'L'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n\n <!-- 검색 결과 없음 -->\n <div\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\n class=\"text-center py-6 text-muted-foreground text-xs\"\n >\n 검색 결과가 없습니다.\n </div>\n </nav>\n\n <!-- 하단: collapse 토글 -->\n <div class=\"flex-shrink-0 border-t border-border p-1\">\n <button\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <JIcon\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\n size=\"sm\"\n />\n <span v-if=\"!collapsed\">접기</span>\n </button>\n </div>\n </aside>\n</template>\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JSidebar.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, provide, watch, reactive } from 'vue'\r\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JInput from '@/components/atoms/JInput.vue'\r\nimport JSidebarGroup from './JSidebarGroup.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebar - 통합 사이드바 컴포넌트\r\n *\r\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\r\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\r\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\r\n */\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 트리 데이터 */\r\n items: SidebarMenuItem[]\r\n /** 접힘 상태 (v-model:collapsed) */\r\n collapsed?: boolean\r\n /** 현재 활성 경로 */\r\n activePath?: string\r\n /** 펼침 너비 */\r\n width?: string\r\n /** 접힘 너비 */\r\n collapsedWidth?: string\r\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\r\n storageKey?: string\r\n /** 검색 표시 여부 */\r\n showSearch?: boolean\r\n /** 즐겨찾기 섹션 표시 여부 */\r\n showFavorites?: boolean\r\n }>(),\r\n {\r\n collapsed: false,\r\n width: '220px',\r\n collapsedWidth: '56px',\r\n showSearch: true,\r\n showFavorites: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\n// ── 즐겨찾기 (localStorage) ──\r\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\r\n\r\nfunction loadFavorites(): string[] {\r\n if (!props.storageKey) return []\r\n try {\r\n const raw = localStorage.getItem(props.storageKey)\r\n return raw ? JSON.parse(raw) : []\r\n } catch {\r\n return []\r\n }\r\n}\r\n\r\nfunction saveFavorites() {\r\n if (!props.storageKey) return\r\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\r\n}\r\n\r\nfunction toggleFavorite(id: string) {\r\n if (favorites.value.has(id)) {\r\n favorites.value.delete(id)\r\n } else {\r\n favorites.value.add(id)\r\n }\r\n favorites.value = new Set(favorites.value) // trigger reactivity\r\n saveFavorites()\r\n}\r\n\r\n// ── provide/inject 상태 ──\r\nconst sidebarState = reactive<SidebarState>({\r\n collapsed: props.collapsed,\r\n activePath: props.activePath,\r\n favorites: favorites.value,\r\n toggleFavorite,\r\n})\r\n\r\n// props 변경 시 state 동기화\r\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\r\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\r\nwatch(favorites, (v) => { sidebarState.favorites = v })\r\n\r\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\r\n\r\n// ── 검색 ──\r\nconst searchQuery = ref('')\r\n\r\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\r\nconst favoriteItems = computed(() => {\r\n if (!props.storageKey || favorites.value.size === 0) return []\r\n const result: SidebarMenuItem[] = []\r\n const flatten = (items: SidebarMenuItem[]) => {\r\n for (const item of items) {\r\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\r\n result.push(item)\r\n }\r\n if (item.children) flatten(item.children)\r\n }\r\n }\r\n flatten(props.items)\r\n return result\r\n})\r\n\r\n/** 검색 필터링 (재귀) */\r\nconst filteredItems = computed(() => {\r\n const q = searchQuery.value.trim().toLowerCase()\r\n if (!q) return props.items\r\n\r\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n for (const item of items) {\r\n const matchLabel = item.label.toLowerCase().includes(q)\r\n const filteredChildren = item.children ? filter(item.children) : undefined\r\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\r\n result.push({ ...item, children: filteredChildren })\r\n }\r\n }\r\n return result\r\n }\r\n return filter(props.items)\r\n})\r\n\r\n/** 즐겨찾기 검색 필터링 */\r\nconst filteredFavorites = computed(() => {\r\n const q = searchQuery.value.trim().toLowerCase()\r\n if (!q) return favoriteItems.value\r\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\r\n})\r\n\r\n// ── 즐겨찾기 그룹 펼침 ──\r\nconst favoritesExpanded = ref(true)\r\n\r\n// ── 이벤트 핸들러 ──\r\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\nconst toggleCollapsed = () => {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n\r\n// ── 사이드바 너비 ──\r\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\r\n</script>\r\n\r\n<template>\r\n <aside\r\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\r\n :style=\"{ width: sidebarWidth }\"\r\n >\r\n <!-- 검색바 (펼침 + showSearch) -->\r\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\r\n <div class=\"relative\">\r\n <JIcon\r\n name=\"search\"\r\n size=\"sm\"\r\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\r\n />\r\n <JInput\r\n v-model=\"searchQuery\"\r\n placeholder=\"메뉴 검색...\"\r\n class=\"pl-8 h-7 text-xs\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- 메뉴 영역 -->\r\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\r\n <!-- 즐겨찾기 섹션 -->\r\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\r\n <!-- 펼침: 즐겨찾기 그룹 -->\r\n <template v-if=\"!collapsed\">\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\r\n @click=\"favoritesExpanded = !favoritesExpanded\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n favoritesExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\r\n <span class=\"flex-1 text-left\">즐겨찾기</span>\r\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\r\n </button>\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden\">\r\n <JSidebarItem\r\n v-for=\"fav in filteredFavorites\"\r\n :key=\"'fav-' + fav.id\"\r\n :item=\"fav\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <!-- 접힘: 별 아이콘 + 구분선 -->\r\n <template v-else>\r\n <div class=\"flex justify-center py-1\">\r\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\r\n </div>\r\n <JSidebarItem\r\n v-for=\"fav in filteredFavorites\"\r\n :key=\"'fav-c-' + fav.id\"\r\n :item=\"fav\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </template>\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n </template>\r\n\r\n <!-- 메인 메뉴 -->\r\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\r\n <JSidebarGroup\r\n v-if=\"item.menuType === 'F'\"\r\n :item=\"item\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n <JSidebarItem\r\n v-else-if=\"item.menuType === 'L'\"\r\n :item=\"item\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </template>\r\n\r\n <!-- 검색 결과 없음 -->\r\n <div\r\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\r\n class=\"text-center py-6 text-muted-foreground text-xs\"\r\n >\r\n 검색 결과가 없습니다.\r\n </div>\r\n </nav>\r\n\r\n <!-- 하단: collapse 토글 -->\r\n <div class=\"flex-shrink-0 border-t border-border p-1\">\r\n <button\r\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <JIcon\r\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\r\n size=\"sm\"\r\n />\r\n <span v-if=\"!collapsed\">접기</span>\r\n </button>\r\n </div>\r\n </aside>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebar.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, provide, watch, reactive } from 'vue'\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JInput from '@/components/atoms/JInput.vue'\nimport JSidebarGroup from './JSidebarGroup.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebar - 통합 사이드바 컴포넌트\n *\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\n */\n\nconst props = withDefaults(\n defineProps<{\n /** 메뉴 트리 데이터 */\n items: SidebarMenuItem[]\n /** 접힘 상태 (v-model:collapsed) */\n collapsed?: boolean\n /** 현재 활성 경로 */\n activePath?: string\n /** 펼침 너비 */\n width?: string\n /** 접힘 너비 */\n collapsedWidth?: string\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\n storageKey?: string\n /** 검색 표시 여부 */\n showSearch?: boolean\n /** 즐겨찾기 섹션 표시 여부 */\n showFavorites?: boolean\n }>(),\n {\n collapsed: false,\n width: '220px',\n collapsedWidth: '56px',\n showSearch: true,\n showFavorites: true,\n },\n)\n\nconst emit = defineEmits<{\n 'update:collapsed': [value: boolean]\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\n// ── 즐겨찾기 (localStorage) ──\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\n\nfunction loadFavorites(): string[] {\n if (!props.storageKey) return []\n try {\n const raw = localStorage.getItem(props.storageKey)\n return raw ? JSON.parse(raw) : []\n } catch {\n return []\n }\n}\n\nfunction saveFavorites() {\n if (!props.storageKey) return\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\n}\n\nfunction toggleFavorite(id: string) {\n if (favorites.value.has(id)) {\n favorites.value.delete(id)\n } else {\n favorites.value.add(id)\n }\n favorites.value = new Set(favorites.value) // trigger reactivity\n saveFavorites()\n}\n\n// ── provide/inject 상태 ──\nconst sidebarState = reactive<SidebarState>({\n collapsed: props.collapsed,\n activePath: props.activePath,\n favorites: favorites.value,\n toggleFavorite,\n})\n\n// props 변경 시 state 동기화\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\nwatch(favorites, (v) => { sidebarState.favorites = v })\n\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\n\n// ── 검색 ──\nconst searchQuery = ref('')\n\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\nconst favoriteItems = computed(() => {\n if (!props.storageKey || favorites.value.size === 0) return []\n const result: SidebarMenuItem[] = []\n const flatten = (items: SidebarMenuItem[]) => {\n for (const item of items) {\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\n result.push(item)\n }\n if (item.children) flatten(item.children)\n }\n }\n flatten(props.items)\n return result\n})\n\n/** 검색 필터링 (재귀) */\nconst filteredItems = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return props.items\n\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\n const result: SidebarMenuItem[] = []\n for (const item of items) {\n const matchLabel = item.label.toLowerCase().includes(q)\n const filteredChildren = item.children ? filter(item.children) : undefined\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\n result.push({ ...item, children: filteredChildren })\n }\n }\n return result\n }\n return filter(props.items)\n})\n\n/** 즐겨찾기 검색 필터링 */\nconst filteredFavorites = computed(() => {\n const q = searchQuery.value.trim().toLowerCase()\n if (!q) return favoriteItems.value\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\n})\n\n// ── 즐겨찾기 그룹 펼침 ──\nconst favoritesExpanded = ref(true)\n\n// ── 이벤트 핸들러 ──\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\nconst toggleCollapsed = () => {\n emit('update:collapsed', !props.collapsed)\n}\n\n// ── 사이드바 너비 ──\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\n</script>\n\n<template>\n <aside\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\n :style=\"{ width: sidebarWidth }\"\n >\n <!-- 검색바 (펼침 + showSearch) -->\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\n <div class=\"relative\">\n <JIcon\n name=\"search\"\n size=\"sm\"\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <JInput\n v-model=\"searchQuery\"\n placeholder=\"메뉴 검색...\"\n class=\"pl-8 h-7 text-xs\"\n />\n </div>\n </div>\n\n <!-- 메뉴 영역 -->\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\n <!-- 즐겨찾기 섹션 -->\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\n <!-- 펼침: 즐겨찾기 그룹 -->\n <template v-if=\"!collapsed\">\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\n @click=\"favoritesExpanded = !favoritesExpanded\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n favoritesExpanded && 'rotate-90'\n )\"\n />\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\n <span class=\"flex-1 text-left\">즐겨찾기</span>\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\n </button>\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden\">\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </div>\n </div>\n </template>\n <!-- 접힘: 별 아이콘 + 구분선 -->\n <template v-else>\n <div class=\"flex justify-center py-1\">\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\n </div>\n <JSidebarItem\n v-for=\"fav in filteredFavorites\"\n :key=\"'fav-c-' + fav.id\"\n :item=\"fav\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n <div class=\"h-px bg-border mx-2 my-1\" />\n </template>\n\n <!-- 메인 메뉴 -->\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\n <JSidebarGroup\n v-if=\"item.menuType === 'F'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n <JSidebarItem\n v-else-if=\"item.menuType === 'L'\"\n :item=\"item\"\n @menu-click=\"handleMenuClick\"\n />\n </template>\n\n <!-- 검색 결과 없음 -->\n <div\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\n class=\"text-center py-6 text-muted-foreground text-xs\"\n >\n 검색 결과가 없습니다.\n </div>\n </nav>\n\n <!-- 하단: collapse 토글 -->\n <div class=\"flex-shrink-0 border-t border-border p-1\">\n <button\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\n @click=\"toggleCollapsed\"\n >\n <JIcon\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\n size=\"sm\"\n />\n <span v-if=\"!collapsed\">접기</span>\n </button>\n </div>\n </aside>\n</template>\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"JSidebar.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, provide, watch, reactive } from 'vue'\r\nimport type { SidebarMenuItem, SidebarState } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JInput from '@/components/atoms/JInput.vue'\r\nimport JSidebarGroup from './JSidebarGroup.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebar - 통합 사이드바 컴포넌트\r\n *\r\n * DB 트리 데이터 기반, provide/inject로 상태 공유.\r\n * collapsed 모드, 검색, 즐겨찾기(localStorage) 지원.\r\n * 라우터를 모름 — @menu-click으로 아이템을 올려보냄.\r\n */\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 메뉴 트리 데이터 */\r\n items: SidebarMenuItem[]\r\n /** 접힘 상태 (v-model:collapsed) */\r\n collapsed?: boolean\r\n /** 현재 활성 경로 */\r\n activePath?: string\r\n /** 펼침 너비 */\r\n width?: string\r\n /** 접힘 너비 */\r\n collapsedWidth?: string\r\n /** 즐겨찾기 localStorage 키 (없으면 즐겨찾기 비활성) */\r\n storageKey?: string\r\n /** 검색 표시 여부 */\r\n showSearch?: boolean\r\n /** 즐겨찾기 섹션 표시 여부 */\r\n showFavorites?: boolean\r\n }>(),\r\n {\r\n collapsed: false,\r\n width: '220px',\r\n collapsedWidth: '56px',\r\n showSearch: true,\r\n showFavorites: true,\r\n },\r\n)\r\n\r\nconst emit = defineEmits<{\r\n 'update:collapsed': [value: boolean]\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\n// ── 즐겨찾기 (localStorage) ──\r\nconst favorites = ref<Set<string>>(new Set(loadFavorites()))\r\n\r\nfunction loadFavorites(): string[] {\r\n if (!props.storageKey) return []\r\n try {\r\n const raw = localStorage.getItem(props.storageKey)\r\n return raw ? JSON.parse(raw) : []\r\n } catch {\r\n return []\r\n }\r\n}\r\n\r\nfunction saveFavorites() {\r\n if (!props.storageKey) return\r\n localStorage.setItem(props.storageKey, JSON.stringify([...favorites.value]))\r\n}\r\n\r\nfunction toggleFavorite(id: string) {\r\n if (favorites.value.has(id)) {\r\n favorites.value.delete(id)\r\n } else {\r\n favorites.value.add(id)\r\n }\r\n favorites.value = new Set(favorites.value) // trigger reactivity\r\n saveFavorites()\r\n}\r\n\r\n// ── provide/inject 상태 ──\r\nconst sidebarState = reactive<SidebarState>({\r\n collapsed: props.collapsed,\r\n activePath: props.activePath,\r\n favorites: favorites.value,\r\n toggleFavorite,\r\n})\r\n\r\n// props 변경 시 state 동기화\r\nwatch(() => props.collapsed, (v) => { sidebarState.collapsed = v })\r\nwatch(() => props.activePath, (v) => { sidebarState.activePath = v })\r\nwatch(favorites, (v) => { sidebarState.favorites = v })\r\n\r\nprovide(SIDEBAR_INJECTION_KEY, sidebarState)\r\n\r\n// ── 검색 ──\r\nconst searchQuery = ref('')\r\n\r\n/** 즐겨찾기 아이템 (L 타입만, 트리 평탄화) */\r\nconst favoriteItems = computed(() => {\r\n if (!props.storageKey || favorites.value.size === 0) return []\r\n const result: SidebarMenuItem[] = []\r\n const flatten = (items: SidebarMenuItem[]) => {\r\n for (const item of items) {\r\n if (item.menuType === 'L' && favorites.value.has(item.id)) {\r\n result.push(item)\r\n }\r\n if (item.children) flatten(item.children)\r\n }\r\n }\r\n flatten(props.items)\r\n return result\r\n})\r\n\r\n/** 검색 필터링 (재귀) */\r\nconst filteredItems = computed(() => {\r\n const q = searchQuery.value.trim().toLowerCase()\r\n if (!q) return props.items\r\n\r\n const filter = (items: SidebarMenuItem[]): SidebarMenuItem[] => {\r\n const result: SidebarMenuItem[] = []\r\n for (const item of items) {\r\n const matchLabel = item.label.toLowerCase().includes(q)\r\n const filteredChildren = item.children ? filter(item.children) : undefined\r\n if (matchLabel || (filteredChildren && filteredChildren.length > 0)) {\r\n result.push({ ...item, children: filteredChildren })\r\n }\r\n }\r\n return result\r\n }\r\n return filter(props.items)\r\n})\r\n\r\n/** 즐겨찾기 검색 필터링 */\r\nconst filteredFavorites = computed(() => {\r\n const q = searchQuery.value.trim().toLowerCase()\r\n if (!q) return favoriteItems.value\r\n return favoriteItems.value.filter(item => item.label.toLowerCase().includes(q))\r\n})\r\n\r\n// ── 즐겨찾기 그룹 펼침 ──\r\nconst favoritesExpanded = ref(true)\r\n\r\n// ── 이벤트 핸들러 ──\r\nconst handleMenuClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\nconst toggleCollapsed = () => {\r\n emit('update:collapsed', !props.collapsed)\r\n}\r\n\r\n// ── 사이드바 너비 ──\r\nconst sidebarWidth = computed(() => props.collapsed ? props.collapsedWidth : props.width)\r\n</script>\r\n\r\n<template>\r\n <aside\r\n class=\"h-full bg-background border-r border-border flex flex-col flex-shrink-0 overflow-hidden transition-[width] duration-200 ease-out\"\r\n :style=\"{ width: sidebarWidth }\"\r\n >\r\n <!-- 검색바 (펼침 + showSearch) -->\r\n <div v-if=\"!collapsed && showSearch\" class=\"p-2 flex-shrink-0\">\r\n <div class=\"relative\">\r\n <JIcon\r\n name=\"search\"\r\n size=\"sm\"\r\n class=\"absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground\"\r\n />\r\n <JInput\r\n v-model=\"searchQuery\"\r\n placeholder=\"메뉴 검색...\"\r\n class=\"pl-8 h-7 text-xs\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <!-- 메뉴 영역 -->\r\n <nav class=\"flex-1 overflow-y-auto overflow-x-hidden px-1 py-1\">\r\n <!-- 즐겨찾기 섹션 -->\r\n <template v-if=\"showFavorites && storageKey && filteredFavorites.length > 0\">\r\n <!-- 펼침: 즐겨찾기 그룹 -->\r\n <template v-if=\"!collapsed\">\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 text-xs font-semibold text-yellow-600 cursor-pointer select-none transition-colors hover:text-yellow-700\"\r\n @click=\"favoritesExpanded = !favoritesExpanded\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n favoritesExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <JIcon name=\"star\" size=\"sm\" class=\"flex-shrink-0 text-yellow-500\" />\r\n <span class=\"flex-1 text-left\">즐겨찾기</span>\r\n <span class=\"text-[10px] text-yellow-500/60\">{{ filteredFavorites.length }}</span>\r\n </button>\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n favoritesExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden\">\r\n <JSidebarItem\r\n v-for=\"fav in filteredFavorites\"\r\n :key=\"'fav-' + fav.id\"\r\n :item=\"fav\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </div>\r\n </div>\r\n </template>\r\n <!-- 접힘: 별 아이콘 + 구분선 -->\r\n <template v-else>\r\n <div class=\"flex justify-center py-1\">\r\n <JIcon name=\"star\" size=\"sm\" class=\"text-yellow-500\" />\r\n </div>\r\n <JSidebarItem\r\n v-for=\"fav in filteredFavorites\"\r\n :key=\"'fav-c-' + fav.id\"\r\n :item=\"fav\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </template>\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n </template>\r\n\r\n <!-- 메인 메뉴 -->\r\n <template v-for=\"item in filteredItems\" :key=\"item.id\">\r\n <JSidebarGroup\r\n v-if=\"item.menuType === 'F'\"\r\n :item=\"item\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n <JSidebarItem\r\n v-else-if=\"item.menuType === 'L'\"\r\n :item=\"item\"\r\n @menu-click=\"handleMenuClick\"\r\n />\r\n </template>\r\n\r\n <!-- 검색 결과 없음 -->\r\n <div\r\n v-if=\"filteredItems.length === 0 && searchQuery.trim()\"\r\n class=\"text-center py-6 text-muted-foreground text-xs\"\r\n >\r\n 검색 결과가 없습니다.\r\n </div>\r\n </nav>\r\n\r\n <!-- 하단: collapse 토글 -->\r\n <div class=\"flex-shrink-0 border-t border-border p-1\">\r\n <button\r\n class=\"flex items-center gap-2 w-full px-2 py-1 rounded-md text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors\"\r\n @click=\"toggleCollapsed\"\r\n >\r\n <JIcon\r\n :name=\"collapsed ? 'panelLeftOpen' : 'panelLeftClose'\"\r\n size=\"sm\"\r\n />\r\n <span v-if=\"!collapsed\">접기</span>\r\n </button>\r\n </div>\r\n </aside>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","favorites","ref","loadFavorites","raw","saveFavorites","toggleFavorite","id","sidebarState","reactive","watch","v","provide","SIDEBAR_INJECTION_KEY","searchQuery","favoriteItems","computed","result","flatten","items","item","filteredItems","q","filter","matchLabel","filteredChildren","filteredFavorites","favoritesExpanded","handleMenuClick","event","toggleCollapsed","sidebarWidth","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_hoisted_2","_createVNode","JIcon","JInput","$event","_hoisted_3","_Fragment","_hoisted_6","_renderList","fav","_createBlock","JSidebarItem","_cache","_unref","cn","_hoisted_4","_toDisplayString","_hoisted_5","JSidebarGroup","_hoisted_7","_hoisted_8"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarGroup.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, inject, watch } from 'vue'\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\n * 링크(L)는 JSidebarItem에서 처리.\n */\n\nconst props = defineProps<{\n item: SidebarMenuItem\n}>()\n\nconst emit = defineEmits<{\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\nconst state = inject(SIDEBAR_INJECTION_KEY)!\n\nconst isExpanded = ref(false)\n\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\nconst childCount = computed(() => {\n if (!props.item.children) return 0\n return props.item.children.filter(c => c.menuType === 'L').length\n})\n\nconst toggleExpand = () => {\n isExpanded.value = !isExpanded.value\n}\n\n/** 자식의 menu-click을 상위로 전파 */\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\nconst hasActiveChild = computed(() => {\n if (!state.activePath || !props.item.children) return false\n const checkActive = (items: SidebarMenuItem[]): boolean => {\n for (const child of items) {\n if (child.menuType === 'L' && child.path === state.activePath) return true\n if (child.children && checkActive(child.children)) return true\n }\n return false\n }\n return checkActive(props.item.children)\n})\n\nwatch(hasActiveChild, (active) => {\n if (active) isExpanded.value = true\n}, { immediate: true })\n</script>\n\n<template>\n <!-- Collapsed: 구분선만 -->\n <template v-if=\"state.collapsed\">\n <div class=\"h-px bg-border mx-2 my-1\" />\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\n </template>\n </template>\n\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\n <div v-else>\n <!-- 그룹 헤더 -->\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 mt-1 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\n @click=\"toggleExpand\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n isExpanded && 'rotate-90'\n )\"\n />\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\n <span\n v-if=\"childCount > 0\"\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\n >\n {{ childCount }}\n </span>\n </button>\n\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더: 재귀 -->\n <JSidebarGroup\n v-else-if=\"child.menuType === 'F'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n </template>\n </div>\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":"wjBAcA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAaC,EAAAA,IAAI,EAAK,EAGtBC,EAAaC,EAAAA,SAAS,IACrBV,EAAM,KAAK,SACTA,EAAM,KAAK,SAAS,UAAYW,EAAE,WAAa,GAAG,EAAE,OAD1B,CAElC,EAEKC,EAAe,IAAM,CACzBL,EAAW,MAAQ,CAACA,EAAW,KACjC,EAGMM,EAAmB,CAACC,EAAuBC,IAAsB,CACrEb,EAAK,aAAcY,EAAMC,CAAK,CAChC,EAGMC,EAAiBN,EAAAA,SAAS,IAAM,CACpC,GAAI,CAACN,EAAM,YAAc,CAACJ,EAAM,KAAK,SAAU,MAAO,GACtD,MAAMiB,EAAeC,GAAsC,CACzD,UAAWC,KAASD,EAElB,GADIC,EAAM,WAAa,KAAOA,EAAM,OAASf,EAAM,YAC/Ce,EAAM,UAAYF,EAAYE,EAAM,QAAQ,EAAG,MAAO,GAE5D,MAAO,EACT,EACA,OAAOF,EAAYjB,EAAM,KAAK,QAAQ,CACxC,CAAC,EAEDoB,OAAAA,QAAMJ,EAAiBK,GAAW,CAC5BA,MAAmB,MAAQ,GACjC,EAAG,CAAE,UAAW,GAAM,yDAKJ,OAAAC,QAAAlB,CAAA,EAAM,yBAAtBmB,EAAAA,mBAWWC,WAAA,CAAA,IAAA,GAAA,aAVTC,EAAAA,mBAAwC,MAAA,CAAnC,MAAM,0BAAA,EAA0B,KAAA,EAAA,IAErCC,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAOWC,WAAA,KAAAG,EAAAA,WAPe1B,EAAA,KAAK,SAAdkB,mDAA8B,IAAAA,EAAM,EAAA,GAE3CA,EAAM,WAAQ,mBADtBS,EAAAA,YAIEC,EAAAA,QAAA,OAFC,KAAMV,EACN,YAAYN,CAAA,kFAOnBU,EAAAA,mBA8CM,MAAAO,EAAA,CA5CJL,EAAAA,mBAmBS,SAAA,CAlBP,MAAM,mKACL,QAAOb,CAAA,GAERmB,EAAAA,YAOEC,EAAAA,QAAA,CANA,KAAK,eACL,KAAK,KACJ,uBAAOV,EAAAA,MAAAW,IAAA,
|
|
1
|
+
{"version":3,"file":"JSidebarGroup.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 mt-1 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\r\n @click=\"toggleExpand\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n isExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\r\n <span\r\n v-if=\"childCount > 0\"\r\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\r\n >\r\n {{ childCount }}\r\n </span>\r\n </button>\r\n\r\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더: 재귀 -->\r\n <JSidebarGroup\r\n v-else-if=\"child.menuType === 'F'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":"wjBAcA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAaC,EAAAA,IAAI,EAAK,EAGtBC,EAAaC,EAAAA,SAAS,IACrBV,EAAM,KAAK,SACTA,EAAM,KAAK,SAAS,UAAYW,EAAE,WAAa,GAAG,EAAE,OAD1B,CAElC,EAEKC,EAAe,IAAM,CACzBL,EAAW,MAAQ,CAACA,EAAW,KACjC,EAGMM,EAAmB,CAACC,EAAuBC,IAAsB,CACrEb,EAAK,aAAcY,EAAMC,CAAK,CAChC,EAGMC,EAAiBN,EAAAA,SAAS,IAAM,CACpC,GAAI,CAACN,EAAM,YAAc,CAACJ,EAAM,KAAK,SAAU,MAAO,GACtD,MAAMiB,EAAeC,GAAsC,CACzD,UAAWC,KAASD,EAElB,GADIC,EAAM,WAAa,KAAOA,EAAM,OAASf,EAAM,YAC/Ce,EAAM,UAAYF,EAAYE,EAAM,QAAQ,EAAG,MAAO,GAE5D,MAAO,EACT,EACA,OAAOF,EAAYjB,EAAM,KAAK,QAAQ,CACxC,CAAC,EAEDoB,OAAAA,QAAMJ,EAAiBK,GAAW,CAC5BA,MAAmB,MAAQ,GACjC,EAAG,CAAE,UAAW,GAAM,yDAKJ,OAAAC,QAAAlB,CAAA,EAAM,yBAAtBmB,EAAAA,mBAWWC,WAAA,CAAA,IAAA,GAAA,aAVTC,EAAAA,mBAAwC,MAAA,CAAnC,MAAM,0BAAA,EAA0B,KAAA,EAAA,IAErCC,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAOWC,WAAA,KAAAG,EAAAA,WAPe1B,EAAA,KAAK,SAAdkB,mDAA8B,IAAAA,EAAM,EAAA,GAE3CA,EAAM,WAAQ,mBADtBS,EAAAA,YAIEC,EAAAA,QAAA,OAFC,KAAMV,EACN,YAAYN,CAAA,kFAOnBU,EAAAA,mBA8CM,MAAAO,EAAA,CA5CJL,EAAAA,mBAmBS,SAAA,CAlBP,MAAM,mKACL,QAAOb,CAAA,GAERmB,EAAAA,YAOEC,EAAAA,QAAA,CANA,KAAK,eACL,KAAK,KACJ,uBAAOV,EAAAA,MAAAW,IAAA,oDAA6E1B,EAAA,OAAU,WAAA,sBAKjGkB,qBAA+D,OAA/DS,EAA+DC,EAAAA,gBAApBlC,EAAA,KAAK,KAAK,EAAA,CAAA,EAE7CQ,EAAA,MAAU,iBADlBc,EAAAA,mBAKO,OALPa,EAKOD,EAAAA,gBADF1B,EAAA,KAAU,EAAA,CAAA,iCAKjBgB,EAAAA,mBAqBM,MAAA,CApBH,uBAAOH,EAAAA,MAAAW,IAAA,+DAAoF1B,EAAA,MAAU,kBAAA,iBAAA,KAKtGkB,EAAAA,mBAcM,MAdNY,EAcM,EAbJX,EAAAA,UAAA,EAAA,EAAAH,EAAAA,mBAYWC,WAAA,KAAAG,EAAAA,WAZe1B,EAAA,KAAK,SAAdkB,mDAA8B,IAAAA,EAAM,EAAA,GAE3CA,EAAM,WAAQ,mBADtBS,EAAAA,YAIEC,EAAAA,QAAA,OAFC,KAAMV,EACN,YAAYN,CAAA,oBAIFM,EAAM,WAAQ,mBAD3BS,EAAAA,YAIEU,EAAA,OAFC,KAAMnB,EACN,YAAYN,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarGroup.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, inject, watch } from 'vue'\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JSidebarItem from './JSidebarItem.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\n * 링크(L)는 JSidebarItem에서 처리.\n */\n\nconst props = defineProps<{\n item: SidebarMenuItem\n}>()\n\nconst emit = defineEmits<{\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\nconst state = inject(SIDEBAR_INJECTION_KEY)!\n\nconst isExpanded = ref(false)\n\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\nconst childCount = computed(() => {\n if (!props.item.children) return 0\n return props.item.children.filter(c => c.menuType === 'L').length\n})\n\nconst toggleExpand = () => {\n isExpanded.value = !isExpanded.value\n}\n\n/** 자식의 menu-click을 상위로 전파 */\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\n emit('menu-click', item, event)\n}\n\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\nconst hasActiveChild = computed(() => {\n if (!state.activePath || !props.item.children) return false\n const checkActive = (items: SidebarMenuItem[]): boolean => {\n for (const child of items) {\n if (child.menuType === 'L' && child.path === state.activePath) return true\n if (child.children && checkActive(child.children)) return true\n }\n return false\n }\n return checkActive(props.item.children)\n})\n\nwatch(hasActiveChild, (active) => {\n if (active) isExpanded.value = true\n}, { immediate: true })\n</script>\n\n<template>\n <!-- Collapsed: 구분선만 -->\n <template v-if=\"state.collapsed\">\n <div class=\"h-px bg-border mx-2 my-1\" />\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\n </template>\n </template>\n\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\n <div v-else>\n <!-- 그룹 헤더 -->\n <button\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 mt-1 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\n @click=\"toggleExpand\"\n >\n <JIcon\n name=\"chevronRight\"\n size=\"sm\"\n :class=\"cn(\n 'flex-shrink-0 transition-transform duration-200',\n isExpanded && 'rotate-90'\n )\"\n />\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\n <span\n v-if=\"childCount > 0\"\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\n >\n {{ childCount }}\n </span>\n </button>\n\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\n <div\n :class=\"cn(\n 'grid transition-[grid-template-rows] duration-200 ease-out',\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n )\"\n >\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\n <template v-for=\"child in item.children\" :key=\"child.id\">\n <JSidebarItem\n v-if=\"child.menuType === 'L'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n <!-- 중첩 폴더: 재귀 -->\n <JSidebarGroup\n v-else-if=\"child.menuType === 'F'\"\n :item=\"child\"\n @menu-click=\"handleChildClick\"\n />\n </template>\n </div>\n </div>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":";;;;;;;;;;;;;;;AAcA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAaC,EAAI,EAAK,GAGtBC,IAAaC,EAAS,MACrBV,EAAM,KAAK,WACTA,EAAM,KAAK,SAAS,OAAO,OAAKW,EAAE,aAAa,GAAG,EAAE,SAD1B,CAElC,GAEKC,IAAe,MAAM;AACzB,MAAAL,EAAW,QAAQ,CAACA,EAAW;AAAA,IACjC,GAGMM,IAAmB,CAACC,GAAuBC,MAAsB;AACrE,MAAAb,EAAK,cAAcY,GAAMC,CAAK;AAAA,IAChC,GAGMC,IAAiBN,EAAS,MAAM;AACpC,UAAI,CAACN,EAAM,cAAc,CAACJ,EAAM,KAAK,SAAU,QAAO;AACtD,YAAMiB,IAAc,CAACC,MAAsC;AACzD,mBAAWC,KAASD;AAElB,cADIC,EAAM,aAAa,OAAOA,EAAM,SAASf,EAAM,cAC/Ce,EAAM,YAAYF,EAAYE,EAAM,QAAQ,EAAG,QAAO;AAE5D,eAAO;AAAA,MACT;AACA,aAAOF,EAAYjB,EAAM,KAAK,QAAQ;AAAA,IACxC,CAAC;AAED,WAAAoB,EAAMJ,GAAgB,CAACK,MAAW;AAChC,MAAIA,QAAmB,QAAQ;AAAA,IACjC,GAAG,EAAE,WAAW,IAAM;;AAKJ,aAAAC,EAAAlB,CAAA,EAAM,kBAAtBmB,EAWWC,GAAA,EAAA,KAAA,KAAA;AAAA,wBAVTC,EAAwC,OAAA,EAAnC,OAAM,2BAAA,GAA0B,MAAA,EAAA;AAAA,SAErCC,EAAA,EAAA,GAAAH,EAOWC,GAAA,MAAAG,EAPe1B,EAAA,KAAK,WAAdkB;UAA8B,KAAAA,EAAM;AAAA,QAAA;UAE3CA,EAAM,aAAQ,YADtBS,EAIEC,GAAA;AAAA;YAFC,MAAMV;AAAA,YACN,aAAYN;AAAA,UAAA;;sBAOnBU,EA8CM,OAAAO,GAAA;AAAA,QA5CJL,EAmBS,UAAA;AAAA,UAlBP,OAAM;AAAA,UACL,SAAOb;AAAA,QAAA;UAERmB,EAOEC,GAAA;AAAA,YANA,MAAK;AAAA,YACL,MAAK;AAAA,YACJ,SAAOV,EAAAW,CAAA;AAAA;
|
|
1
|
+
{"version":3,"file":"JSidebarGroup.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { ref, computed, inject, watch } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JSidebarItem from './JSidebarItem.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarGroup - 그룹 헤더 (menuType='F' 전용)\r\n * 폴더 타입 메뉴를 렌더링. 펼침/접힘 토글 + 하위 메뉴 재귀 렌더링.\r\n * 링크(L)는 JSidebarItem에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isExpanded = ref(false)\r\n\r\n/** 직계 자식 중 L 타입 개수 (재귀하지 않음) */\r\nconst childCount = computed(() => {\r\n if (!props.item.children) return 0\r\n return props.item.children.filter(c => c.menuType === 'L').length\r\n})\r\n\r\nconst toggleExpand = () => {\r\n isExpanded.value = !isExpanded.value\r\n}\r\n\r\n/** 자식의 menu-click을 상위로 전파 */\r\nconst handleChildClick = (item: SidebarMenuItem, event: MouseEvent) => {\r\n emit('menu-click', item, event)\r\n}\r\n\r\n/** 현재 경로가 이 그룹의 자식에 포함되면 자동 펼침 */\r\nconst hasActiveChild = computed(() => {\r\n if (!state.activePath || !props.item.children) return false\r\n const checkActive = (items: SidebarMenuItem[]): boolean => {\r\n for (const child of items) {\r\n if (child.menuType === 'L' && child.path === state.activePath) return true\r\n if (child.children && checkActive(child.children)) return true\r\n }\r\n return false\r\n }\r\n return checkActive(props.item.children)\r\n})\r\n\r\nwatch(hasActiveChild, (active) => {\r\n if (active) isExpanded.value = true\r\n}, { immediate: true })\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 구분선만 -->\r\n <template v-if=\"state.collapsed\">\r\n <div class=\"h-px bg-border mx-2 my-1\" />\r\n <!-- collapsed 상태에서 자식 아이템 직접 렌더링 -->\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더는 collapsed에서 무시 (1단계만) -->\r\n </template>\r\n </template>\r\n\r\n <!-- Expanded: 그룹 헤더 + 하위 메뉴 -->\r\n <div v-else>\r\n <!-- 그룹 헤더 -->\r\n <button\r\n class=\"flex items-center gap-1.5 w-full px-2 py-1.5 mt-1 text-xs font-semibold text-muted-foreground cursor-pointer select-none transition-colors hover:text-foreground\"\r\n @click=\"toggleExpand\"\r\n >\r\n <JIcon\r\n name=\"chevronRight\"\r\n size=\"sm\"\r\n :class=\"cn(\r\n 'flex-shrink-0 transition-transform duration-200',\r\n isExpanded && 'rotate-90'\r\n )\"\r\n />\r\n <span class=\"flex-1 truncate text-left\">{{ item.label }}</span>\r\n <span\r\n v-if=\"childCount > 0\"\r\n class=\"text-[10px] text-muted-foreground/60 flex-shrink-0\"\r\n >\r\n {{ childCount }}\r\n </span>\r\n </button>\r\n\r\n <!-- 하위 메뉴 (grid-template-rows 애니메이션) -->\r\n <div\r\n :class=\"cn(\r\n 'grid transition-[grid-template-rows] duration-200 ease-out',\r\n isExpanded ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\r\n )\"\r\n >\r\n <div class=\"overflow-hidden ml-3 pl-2 border-l border-border/50\">\r\n <template v-for=\"child in item.children\" :key=\"child.id\">\r\n <JSidebarItem\r\n v-if=\"child.menuType === 'L'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n <!-- 중첩 폴더: 재귀 -->\r\n <JSidebarGroup\r\n v-else-if=\"child.menuType === 'F'\"\r\n :item=\"child\"\r\n @menu-click=\"handleChildClick\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isExpanded","ref","childCount","computed","c","toggleExpand","handleChildClick","item","event","hasActiveChild","checkActive","items","child","watch","active","_unref","_createElementBlock","_Fragment","_createElementVNode","_openBlock","_renderList","_createBlock","JSidebarItem","_hoisted_1","_createVNode","JIcon","cn","_hoisted_2","_toDisplayString","_hoisted_3","_hoisted_4","_component_JSidebarGroup"],"mappings":";;;;;;;;;;;;;;;AAcA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAaC,EAAI,EAAK,GAGtBC,IAAaC,EAAS,MACrBV,EAAM,KAAK,WACTA,EAAM,KAAK,SAAS,OAAO,OAAKW,EAAE,aAAa,GAAG,EAAE,SAD1B,CAElC,GAEKC,IAAe,MAAM;AACzB,MAAAL,EAAW,QAAQ,CAACA,EAAW;AAAA,IACjC,GAGMM,IAAmB,CAACC,GAAuBC,MAAsB;AACrE,MAAAb,EAAK,cAAcY,GAAMC,CAAK;AAAA,IAChC,GAGMC,IAAiBN,EAAS,MAAM;AACpC,UAAI,CAACN,EAAM,cAAc,CAACJ,EAAM,KAAK,SAAU,QAAO;AACtD,YAAMiB,IAAc,CAACC,MAAsC;AACzD,mBAAWC,KAASD;AAElB,cADIC,EAAM,aAAa,OAAOA,EAAM,SAASf,EAAM,cAC/Ce,EAAM,YAAYF,EAAYE,EAAM,QAAQ,EAAG,QAAO;AAE5D,eAAO;AAAA,MACT;AACA,aAAOF,EAAYjB,EAAM,KAAK,QAAQ;AAAA,IACxC,CAAC;AAED,WAAAoB,EAAMJ,GAAgB,CAACK,MAAW;AAChC,MAAIA,QAAmB,QAAQ;AAAA,IACjC,GAAG,EAAE,WAAW,IAAM;;AAKJ,aAAAC,EAAAlB,CAAA,EAAM,kBAAtBmB,EAWWC,GAAA,EAAA,KAAA,KAAA;AAAA,wBAVTC,EAAwC,OAAA,EAAnC,OAAM,2BAAA,GAA0B,MAAA,EAAA;AAAA,SAErCC,EAAA,EAAA,GAAAH,EAOWC,GAAA,MAAAG,EAPe1B,EAAA,KAAK,WAAdkB;UAA8B,KAAAA,EAAM;AAAA,QAAA;UAE3CA,EAAM,aAAQ,YADtBS,EAIEC,GAAA;AAAA;YAFC,MAAMV;AAAA,YACN,aAAYN;AAAA,UAAA;;sBAOnBU,EA8CM,OAAAO,GAAA;AAAA,QA5CJL,EAmBS,UAAA;AAAA,UAlBP,OAAM;AAAA,UACL,SAAOb;AAAA,QAAA;UAERmB,EAOEC,GAAA;AAAA,YANA,MAAK;AAAA,YACL,MAAK;AAAA,YACJ,SAAOV,EAAAW,CAAA;AAAA;cAA6E1B,EAAA,SAAU;AAAA,YAAA;;UAKjGkB,EAA+D,QAA/DS,GAA+DC,EAApBlC,EAAA,KAAK,KAAK,GAAA,CAAA;AAAA,UAE7CQ,EAAA,QAAU,UADlBc,EAKO,QALPa,GAKOD,EADF1B,EAAA,KAAU,GAAA,CAAA;;QAKjBgB,EAqBM,OAAA;AAAA,UApBH,SAAOH,EAAAW,CAAA;AAAA;YAAoF1B,EAAA,QAAU,oBAAA;AAAA,UAAA;;UAKtGkB,EAcM,OAdNY,GAcM;AAAA,aAbJX,EAAA,EAAA,GAAAH,EAYWC,GAAA,MAAAG,EAZe1B,EAAA,KAAK,WAAdkB;cAA8B,KAAAA,EAAM;AAAA,YAAA;cAE3CA,EAAM,aAAQ,YADtBS,EAIEC,GAAA;AAAA;gBAFC,MAAMV;AAAA,gBACN,aAAYN;AAAA,cAAA,yBAIFM,EAAM,aAAQ,YAD3BS,EAIEU,GAAA;AAAA;gBAFC,MAAMnB;AAAA,gBACN,aAAYN;AAAA,cAAA;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JTooltip from '@/components/atoms/JTooltip.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\n */\n\nconst props = defineProps<{\n item: SidebarMenuItem\n}>()\n\nconst emit = defineEmits<{\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\nconst state = inject(SIDEBAR_INJECTION_KEY)!\n\nconst isActive = computed(() => {\n if (!props.item.path || !state.activePath) return false\n return state.activePath === props.item.path\n})\n\nconst isFav = computed(() => state.favorites.has(props.item.id))\n\nconst handleClick = (event: MouseEvent) => {\n if (props.item.disabled) return\n emit('menu-click', props.item, event)\n}\n\nconst handleFavoriteClick = (event: MouseEvent) => {\n event.stopPropagation()\n state.toggleFavorite(props.item.id)\n}\n</script>\n\n<template>\n <!-- Collapsed: 아이콘 + Tooltip -->\n <JTooltip\n v-if=\"state.collapsed\"\n :content=\"item.label\"\n side=\"right\"\n size=\"sm\"\n >\n <template #trigger>\n <div\n :class=\"cn(\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\n isActive\n ? 'bg-primary/10 text-primary'\n : 'text-foreground hover:bg-accent/50',\n item.disabled && 'opacity-40 cursor-not-allowed'\n )\"\n @click=\"handleClick\"\n >\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\n </div>\n </template>\n </JTooltip>\n\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\n <div\n v-else\n :class=\"cn(\n 'group flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors relative',\n isActive\n ? 'bg-primary/10 text-primary font-medium'\n : 'text-foreground hover:bg-accent/50',\n item.disabled && 'opacity-40 cursor-not-allowed'\n )\"\n @click=\"handleClick\"\n >\n <!-- 활성 좌측 바 -->\n <div\n v-if=\"isActive\"\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\n />\n\n <JIcon\n v-if=\"item.icon\"\n :name=\"item.icon\"\n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\n\n <!-- 즐겨찾기 별 -->\n <button\n :class=\"cn(\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\n isFav\n ? 'opacity-100 text-yellow-500'\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\n )\"\n @click=\"handleFavoriteClick\"\n >\n <JIcon\n name=\"star\"\n size=\"sm\"\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\n />\n </button>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":"0fAaA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAWC,EAAAA,SAAS,IACpB,CAACR,EAAM,KAAK,MAAQ,CAACI,EAAM,WAAmB,GAC3CA,EAAM,aAAeJ,EAAM,KAAK,IACxC,EAEKS,EAAQD,WAAS,IAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,EAEzDU,EAAeC,GAAsB,CACrCX,EAAM,KAAK,UACfE,EAAK,aAAcF,EAAM,KAAMW,CAAK,CACtC,EAEMC,EAAuBD,GAAsB,CACjDA,EAAM,gBAAA,EACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE,CACpC,eAMUa,EAAAA,MAAAT,CAAA,EAAM,yBADdU,EAAAA,YAoBWC,UAAA,OAlBR,QAASd,EAAA,KAAK,MACf,KAAK,QACL,KAAK,IAAA,GAEM,kBACT,IAWM,CAXNe,EAAAA,mBAWM,MAAA,CAVH,uBAAOH,EAAAA,MAAAI,IAAA,
|
|
1
|
+
{"version":3,"file":"JSidebarItem.vue.cjs","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors relative',\r\n isActive\r\n ? 'bg-primary/10 text-primary font-medium'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <!-- 활성 좌측 바 -->\r\n <div\r\n v-if=\"isActive\"\r\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\r\n />\r\n\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\r\n\r\n <!-- 즐겨찾기 별 -->\r\n <button\r\n :class=\"cn(\r\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\r\n isFav\r\n ? 'opacity-100 text-yellow-500'\r\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\r\n )\"\r\n @click=\"handleFavoriteClick\"\r\n >\r\n <JIcon\r\n name=\"star\"\r\n size=\"sm\"\r\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\r\n />\r\n </button>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":"0fAaA,MAAMA,EAAQC,EAIRC,EAAOC,EAIPC,EAAQC,EAAAA,OAAOC,uBAAqB,EAEpCC,EAAWC,EAAAA,SAAS,IACpB,CAACR,EAAM,KAAK,MAAQ,CAACI,EAAM,WAAmB,GAC3CA,EAAM,aAAeJ,EAAM,KAAK,IACxC,EAEKS,EAAQD,WAAS,IAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,EAEzDU,EAAeC,GAAsB,CACrCX,EAAM,KAAK,UACfE,EAAK,aAAcF,EAAM,KAAMW,CAAK,CACtC,EAEMC,EAAuBD,GAAsB,CACjDA,EAAM,gBAAA,EACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE,CACpC,eAMUa,EAAAA,MAAAT,CAAA,EAAM,yBADdU,EAAAA,YAoBWC,UAAA,OAlBR,QAASd,EAAA,KAAK,MACf,KAAK,QACL,KAAK,IAAA,GAEM,kBACT,IAWM,CAXNe,EAAAA,mBAWM,MAAA,CAVH,uBAAOH,EAAAA,MAAAI,IAAA,6FAAsHV,EAAA,wEAAqHN,EAAA,KAAK,UAAQ,+BAAA,GAO/P,QAAOS,CAAA,GAERQ,EAAAA,YAA+CC,EAAAA,QAAA,CAAvC,KAAMlB,EAAA,KAAK,MAAI,OAAY,KAAK,IAAA,6DAM9CmB,EAAAA,mBAyCM,MAAA,OAvCH,uBAAOP,EAAAA,MAAAI,IAAA,iGAAkHV,EAAA,oFAAqHN,EAAA,KAAK,UAAQ,+BAAA,GAO3P,QAAOS,CAAA,GAIAH,EAAA,OADRc,EAAAA,UAAA,EAAAD,EAAAA,mBAGE,MAHFE,CAGE,+BAGMrB,EAAA,KAAK,oBADba,EAAAA,YAKEK,EAAAA,QAAA,OAHC,KAAMlB,EAAA,KAAK,KACZ,KAAK,KACL,MAAM,eAAA,gDAERe,qBAA6D,OAA7DO,EAA6DC,EAAAA,gBAApBvB,EAAA,KAAK,KAAK,EAAA,CAAA,EAGnDe,EAAAA,mBAcS,SAAA,CAbN,uBAAOH,EAAAA,MAAAI,IAAA,mDAAwER,EAAA,sHAM/E,QAAOG,CAAA,GAERM,EAAAA,YAIEC,EAAAA,QAAA,CAHA,KAAK,OACL,KAAK,KACJ,MAAKM,EAAAA,eAAEhB,EAAA,MAAK,kBAAuB,MAAS,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JSidebarItem.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\nimport JIcon from '@/components/atoms/JIcon.vue'\nimport JTooltip from '@/components/atoms/JTooltip.vue'\nimport { cn } from '@/lib/utils'\n\n/**\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\n */\n\nconst props = defineProps<{\n item: SidebarMenuItem\n}>()\n\nconst emit = defineEmits<{\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\n}>()\n\nconst state = inject(SIDEBAR_INJECTION_KEY)!\n\nconst isActive = computed(() => {\n if (!props.item.path || !state.activePath) return false\n return state.activePath === props.item.path\n})\n\nconst isFav = computed(() => state.favorites.has(props.item.id))\n\nconst handleClick = (event: MouseEvent) => {\n if (props.item.disabled) return\n emit('menu-click', props.item, event)\n}\n\nconst handleFavoriteClick = (event: MouseEvent) => {\n event.stopPropagation()\n state.toggleFavorite(props.item.id)\n}\n</script>\n\n<template>\n <!-- Collapsed: 아이콘 + Tooltip -->\n <JTooltip\n v-if=\"state.collapsed\"\n :content=\"item.label\"\n side=\"right\"\n size=\"sm\"\n >\n <template #trigger>\n <div\n :class=\"cn(\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\n isActive\n ? 'bg-primary/10 text-primary'\n : 'text-foreground hover:bg-accent/50',\n item.disabled && 'opacity-40 cursor-not-allowed'\n )\"\n @click=\"handleClick\"\n >\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\n </div>\n </template>\n </JTooltip>\n\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\n <div\n v-else\n :class=\"cn(\n 'group flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors relative',\n isActive\n ? 'bg-primary/10 text-primary font-medium'\n : 'text-foreground hover:bg-accent/50',\n item.disabled && 'opacity-40 cursor-not-allowed'\n )\"\n @click=\"handleClick\"\n >\n <!-- 활성 좌측 바 -->\n <div\n v-if=\"isActive\"\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\n />\n\n <JIcon\n v-if=\"item.icon\"\n :name=\"item.icon\"\n size=\"sm\"\n class=\"flex-shrink-0\"\n />\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\n\n <!-- 즐겨찾기 별 -->\n <button\n :class=\"cn(\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\n isFav\n ? 'opacity-100 text-yellow-500'\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\n )\"\n @click=\"handleFavoriteClick\"\n >\n <JIcon\n name=\"star\"\n size=\"sm\"\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\n />\n </button>\n </div>\n</template>\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":";;;;;;;;;;;;;;;AAaA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAWC,EAAS,MACpB,CAACR,EAAM,KAAK,QAAQ,CAACI,EAAM,aAAmB,KAC3CA,EAAM,eAAeJ,EAAM,KAAK,IACxC,GAEKS,IAAQD,EAAS,MAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,GAEzDU,IAAc,CAACC,MAAsB;AACzC,MAAIX,EAAM,KAAK,YACfE,EAAK,cAAcF,EAAM,MAAMW,CAAK;AAAA,IACtC,GAEMC,IAAsB,CAACD,MAAsB;AACjD,MAAAA,EAAM,gBAAA,GACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE;AAAA,IACpC;qBAMUa,EAAAT,CAAA,EAAM,kBADdU,EAoBWC,GAAA;AAAA;MAlBR,SAASd,EAAA,KAAK;AAAA,MACf,MAAK;AAAA,MACL,MAAK;AAAA,IAAA;MAEM,WACT,MAWM;AAAA,QAXNe,EAWM,OAAA;AAAA,UAVH,SAAOH,EAAAI,CAAA;AAAA;
|
|
1
|
+
{"version":3,"file":"JSidebarItem.vue.js","sources":["../../../../../src/components/organisms/JSidebar/JSidebarItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport type { SidebarMenuItem } from '@/types/sidebar.types'\r\nimport { SIDEBAR_INJECTION_KEY } from '@/types/sidebar.types'\r\nimport JIcon from '@/components/atoms/JIcon.vue'\r\nimport JTooltip from '@/components/atoms/JTooltip.vue'\r\nimport { cn } from '@/lib/utils'\r\n\r\n/**\r\n * JSidebarItem - 메뉴 아이템 (menuType='L' 전용)\r\n * 링크 타입 메뉴만 렌더링. 폴더(F)는 JSidebarGroup에서 처리.\r\n */\r\n\r\nconst props = defineProps<{\r\n item: SidebarMenuItem\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n 'menu-click': [item: SidebarMenuItem, event: MouseEvent]\r\n}>()\r\n\r\nconst state = inject(SIDEBAR_INJECTION_KEY)!\r\n\r\nconst isActive = computed(() => {\r\n if (!props.item.path || !state.activePath) return false\r\n return state.activePath === props.item.path\r\n})\r\n\r\nconst isFav = computed(() => state.favorites.has(props.item.id))\r\n\r\nconst handleClick = (event: MouseEvent) => {\r\n if (props.item.disabled) return\r\n emit('menu-click', props.item, event)\r\n}\r\n\r\nconst handleFavoriteClick = (event: MouseEvent) => {\r\n event.stopPropagation()\r\n state.toggleFavorite(props.item.id)\r\n}\r\n</script>\r\n\r\n<template>\r\n <!-- Collapsed: 아이콘 + Tooltip -->\r\n <JTooltip\r\n v-if=\"state.collapsed\"\r\n :content=\"item.label\"\r\n side=\"right\"\r\n size=\"sm\"\r\n >\r\n <template #trigger>\r\n <div\r\n :class=\"cn(\r\n 'flex items-center justify-center py-1.5 mx-1 rounded-md cursor-pointer transition-colors',\r\n isActive\r\n ? 'bg-primary/10 text-primary'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <JIcon :name=\"item.icon || 'file'\" size=\"sm\" />\r\n </div>\r\n </template>\r\n </JTooltip>\r\n\r\n <!-- Expanded: 아이콘 + 라벨 + 즐겨찾기 -->\r\n <div\r\n v-else\r\n :class=\"cn(\r\n 'group flex items-center gap-2 py-1 px-2 rounded-md cursor-pointer transition-colors relative',\r\n isActive\r\n ? 'bg-primary/10 text-primary font-medium'\r\n : 'text-foreground hover:bg-accent/50',\r\n item.disabled && 'opacity-40 cursor-not-allowed'\r\n )\"\r\n @click=\"handleClick\"\r\n >\r\n <!-- 활성 좌측 바 -->\r\n <div\r\n v-if=\"isActive\"\r\n class=\"absolute left-0 top-1 bottom-1 w-[3px] rounded-r bg-primary\"\r\n />\r\n\r\n <JIcon\r\n v-if=\"item.icon\"\r\n :name=\"item.icon\"\r\n size=\"sm\"\r\n class=\"flex-shrink-0\"\r\n />\r\n <span class=\"flex-1 truncate text-xs\">{{ item.label }}</span>\r\n\r\n <!-- 즐겨찾기 별 -->\r\n <button\r\n :class=\"cn(\r\n 'flex-shrink-0 p-0.5 rounded transition-opacity',\r\n isFav\r\n ? 'opacity-100 text-yellow-500'\r\n : 'opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-yellow-500'\r\n )\"\r\n @click=\"handleFavoriteClick\"\r\n >\r\n <JIcon\r\n name=\"star\"\r\n size=\"sm\"\r\n :class=\"isFav ? 'fill-yellow-500' : undefined\"\r\n />\r\n </button>\r\n </div>\r\n</template>\r\n"],"names":["props","__props","emit","__emit","state","inject","SIDEBAR_INJECTION_KEY","isActive","computed","isFav","handleClick","event","handleFavoriteClick","_unref","_createBlock","JTooltip","_createElementVNode","cn","_createVNode","JIcon","_createElementBlock","_openBlock","_hoisted_1","_hoisted_2","_toDisplayString","_normalizeClass"],"mappings":";;;;;;;;;;;;;;;AAaA,UAAMA,IAAQC,GAIRC,IAAOC,GAIPC,IAAQC,EAAOC,CAAqB,GAEpCC,IAAWC,EAAS,MACpB,CAACR,EAAM,KAAK,QAAQ,CAACI,EAAM,aAAmB,KAC3CA,EAAM,eAAeJ,EAAM,KAAK,IACxC,GAEKS,IAAQD,EAAS,MAAMJ,EAAM,UAAU,IAAIJ,EAAM,KAAK,EAAE,CAAC,GAEzDU,IAAc,CAACC,MAAsB;AACzC,MAAIX,EAAM,KAAK,YACfE,EAAK,cAAcF,EAAM,MAAMW,CAAK;AAAA,IACtC,GAEMC,IAAsB,CAACD,MAAsB;AACjD,MAAAA,EAAM,gBAAA,GACNP,EAAM,eAAeJ,EAAM,KAAK,EAAE;AAAA,IACpC;qBAMUa,EAAAT,CAAA,EAAM,kBADdU,EAoBWC,GAAA;AAAA;MAlBR,SAASd,EAAA,KAAK;AAAA,MACf,MAAK;AAAA,MACL,MAAK;AAAA,IAAA;MAEM,WACT,MAWM;AAAA,QAXNe,EAWM,OAAA;AAAA,UAVH,SAAOH,EAAAI,CAAA;AAAA;YAAsHV,EAAA;YAAqHN,EAAA,KAAK,YAAQ;AAAA,UAAA;UAO/P,SAAOS;AAAA,QAAA;UAERQ,EAA+CC,GAAA;AAAA,YAAvC,MAAMlB,EAAA,KAAK,QAAI;AAAA,YAAY,MAAK;AAAA,UAAA;;;;gCAM9CmB,EAyCM,OAAA;AAAA;MAvCH,SAAOP,EAAAI,CAAA;AAAA;QAAkHV,EAAA;QAAqHN,EAAA,KAAK,YAAQ;AAAA,MAAA;MAO3P,SAAOS;AAAA,IAAA;MAIAH,EAAA,SADRc,EAAA,GAAAD,EAGE,OAHFE,CAGE;MAGMrB,EAAA,KAAK,aADba,EAKEK,GAAA;AAAA;QAHC,MAAMlB,EAAA,KAAK;AAAA,QACZ,MAAK;AAAA,QACL,OAAM;AAAA,MAAA;MAERe,EAA6D,QAA7DO,GAA6DC,EAApBvB,EAAA,KAAK,KAAK,GAAA,CAAA;AAAA,MAGnDe,EAcS,UAAA;AAAA,QAbN,SAAOH,EAAAI,CAAA;AAAA;UAAwER,EAAA;;QAM/E,SAAOG;AAAA,MAAA;QAERM,EAIEC,GAAA;AAAA,UAHA,MAAK;AAAA,UACL,MAAK;AAAA,UACJ,OAAKM,EAAEhB,EAAA,QAAK,oBAAuB,MAAS;AAAA,QAAA;;;;;"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
for (const [t_key, t_val] of t_opts)
|
|
4
4
|
t_merged[t_key] = t_val;
|
|
5
5
|
return t_merged;
|
|
6
|
-
};,u=t(e.default,[["__scopeId","data-v-
|
|
6
|
+
};,u=t(e.default,[["__scopeId","data-v-16c5d086"]]);exports.default=u;
|
|
7
7
|
//# sourceMappingURL=JSidebarAdvanced.vue.cjs.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import o from "./JSidebarAdvanced.vue2.js";
|
|
2
2
|
/* empty css */
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
for (const [
|
|
6
|
-
|
|
7
|
-
return
|
|
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
8
|
};
|
|
9
|
-
const
|
|
9
|
+
const c = /* @__PURE__ */ r(o, [["__scopeId", "data-v-16c5d086"]]);
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
c as default
|
|
12
12
|
};
|
|
13
13
|
//# sourceMappingURL=JSidebarAdvanced.vue.js.map
|