This commit is contained in:
SeoJHeasdw 2025-06-13 14:37:39 +09:00
parent 7439ce9c6c
commit 985f780ab9

View File

@ -220,42 +220,178 @@
</v-card> </v-card>
</v-col> </v-col>
<!-- AI 추천 요약 --> <!-- AI 추천 활용 - 단일 상세 콘텐츠로 변경 -->
<v-col cols="12" lg="4"> <v-col cols="12" lg="4">
<v-card elevation="0" border class="ai-recommend-card h-100"> <v-card elevation="0" border class="ai-recommend-card h-100">
<v-card-title class="pa-6 pb-0"> <v-card-title class="pa-4 pb-0">
<div class="d-flex align-center mb-1"> <div class="d-flex align-center justify-space-between w-100">
<v-icon color="primary" class="mr-2">mdi-robot</v-icon> <div class="d-flex align-center">
<h3 class="text-h6 font-weight-bold">AI 추천 활용</h3> <v-icon color="primary" class="mr-2">mdi-robot</v-icon>
<div>
<h3 class="text-h6 font-weight-bold mb-0">AI 추천 활용</h3>
<p class="text-caption text-grey-darken-1 mb-0">맞춤형 마케팅 제안</p>
</div>
</div>
<v-btn
icon="mdi-refresh"
size="small"
variant="text"
color="primary"
:loading="aiLoading"
@click="refreshAiRecommendation"
/>
</div> </div>
<p class="text-body-2 text-grey-darken-1 mb-0">맞춤형 마케팅 제안</p>
</v-card-title> </v-card-title>
<v-card-text class="pa-6">
<v-card-text class="pa-4 pt-2">
<div v-if="aiLoading" class="text-center py-8"> <div v-if="aiLoading" class="text-center py-8">
<v-progress-circular color="primary" indeterminate size="40" /> <v-progress-circular color="primary" indeterminate size="40" />
<p class="text-body-2 text-grey mt-3">AI가 분석 ...</p> <p class="text-body-2 text-grey mt-3">AI가 분석 ...</p>
</div> </div>
<div v-else>
<div <div v-else-if="aiRecommendation" class="ai-recommendation-content">
v-for="(recommendation, index) in aiRecommendations" <!-- 추천 제목 -->
:key="index" <div class="recommendation-header mb-4">
class="ai-recommendation-item mb-4" <div class="d-flex align-center mb-2">
> <span class="recommendation-emoji mr-2">{{ aiRecommendation.emoji }}</span>
<v-alert <h4 class="text-h6 font-weight-bold text-primary">
:type="recommendation.type" {{ aiRecommendation.title }}
variant="tonal" </h4>
density="compact" </div>
:icon="recommendation.icon"
class="mb-0"
>
<v-alert-title class="text-subtitle-2 font-weight-bold mb-1">
{{ recommendation.title }}
</v-alert-title>
<div class="text-body-2">
{{ recommendation.content }}
</div>
</v-alert>
</div> </div>
<!-- 스크롤 가능한 콘텐츠 영역 -->
<div class="recommendation-scroll-content" style="max-height: 400px; overflow-y: auto;">
<!-- 기획 아이디어 섹션 -->
<div v-if="aiRecommendation.sections?.ideas" class="recommendation-section mb-4">
<h5 class="text-subtitle-1 font-weight-bold mb-2 d-flex align-center">
<v-icon icon="mdi-lightbulb" size="16" color="warning" class="mr-1" />
{{ aiRecommendation.sections.ideas.title }}
</h5>
<div class="ml-4">
<div
v-for="(idea, index) in aiRecommendation.sections.ideas.items"
:key="index"
class="idea-item mb-2"
>
<div class="d-flex align-start">
<v-icon icon="mdi-circle-small" size="12" color="primary" class="mt-1 mr-1" />
<span class="text-body-2" v-html="idea"></span>
</div>
</div>
</div>
</div>
<!-- 비용 효과 섹션 -->
<div v-if="aiRecommendation.sections?.costs" class="recommendation-section mb-4">
<h5 class="text-subtitle-1 font-weight-bold mb-3 d-flex align-center">
<v-icon icon="mdi-calculator" size="16" color="success" class="mr-1" />
{{ aiRecommendation.sections.costs.title }}
</h5>
<!-- 비용 테이블 -->
<div v-if="aiRecommendation.sections.costs.items" class="ml-4 mb-3">
<v-table density="compact" class="cost-table">
<tbody>
<tr v-for="(cost, index) in aiRecommendation.sections.costs.items" :key="index">
<td class="text-body-2 font-weight-medium">{{ cost.item }}</td>
<td class="text-body-2 text-primary font-weight-bold text-right">{{ cost.amount }}</td>
</tr>
</tbody>
</v-table>
</div>
<!-- 기대 효과 -->
<div v-if="aiRecommendation.sections.costs.effects" class="ml-4">
<p class="text-body-2 font-weight-bold mb-2">📈 기대 효과:</p>
<div
v-for="(effect, index) in aiRecommendation.sections.costs.effects"
:key="index"
class="effect-item mb-1"
>
<div class="d-flex align-start">
<v-icon icon="mdi-check-circle" size="12" color="success" class="mt-1 mr-1" />
<span class="text-body-2">{{ effect }}</span>
</div>
</div>
</div>
</div>
<!-- 주의사항 섹션 -->
<div v-if="aiRecommendation.sections?.warnings" class="recommendation-section mb-4">
<h5 class="text-subtitle-1 font-weight-bold mb-2 d-flex align-center">
<v-icon icon="mdi-alert" size="16" color="warning" class="mr-1" />
{{ aiRecommendation.sections.warnings.title }}
</h5>
<div class="ml-4">
<div
v-for="(warning, index) in aiRecommendation.sections.warnings.items"
:key="index"
class="warning-item mb-2"
>
<div class="d-flex align-start">
<v-icon icon="mdi-alert-circle" size="12" color="warning" class="mt-1 mr-1" />
<span class="text-body-2" v-html="warning"></span>
</div>
</div>
</div>
</div>
<!-- 현재 상황 정보 -->
<div v-if="aiRecommendation.currentInfo" class="recommendation-section">
<h5 class="text-subtitle-1 font-weight-bold mb-2 d-flex align-center">
<v-icon :icon="aiRecommendation.currentInfo.icon" size="16" :color="aiRecommendation.currentInfo.color" class="mr-1" />
{{ aiRecommendation.currentInfo.title }}
</h5>
<div class="current-info-box pa-3" :class="`bg-${aiRecommendation.currentInfo.color}-lighten-5`">
<div
v-for="(info, index) in aiRecommendation.currentInfo.items"
:key="index"
class="info-item mb-1"
>
<span class="text-body-2">
<strong>{{ info.label }}:</strong> {{ info.value }}
</span>
</div>
<div v-if="aiRecommendation.currentInfo.insight" class="insight-box mt-2 pa-2 bg-white rounded">
<div class="d-flex align-start">
<v-icon icon="mdi-lightbulb" size="16" color="amber" class="mt-1 mr-2" />
<span class="text-body-2 font-weight-medium" v-html="aiRecommendation.currentInfo.insight"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 에러 상태 -->
<div v-else-if="aiError" class="text-center py-8">
<v-icon icon="mdi-alert-circle" size="48" color="error" class="mb-4" />
<p class="text-body-2 text-error mb-4">{{ aiError }}</p>
<v-btn
color="primary"
variant="outlined"
@click="refreshAiRecommendation"
>
다시 시도
</v-btn>
</div>
<!-- 초기 상태 -->
<div v-else class="text-center py-8">
<v-icon icon="mdi-robot" size="48" color="grey-lighten-2" class="mb-4" />
<p class="text-body-2 text-grey-darken-1 mb-4">
AI 추천을 불러오고 있습니다
</p>
<v-btn
color="primary"
variant="outlined"
@click="refreshAiRecommendation"
>
추천 받기
</v-btn>
</div> </div>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -287,9 +423,8 @@ import { formatCurrency, formatNumber, formatRelativeTime } from '@/utils/format
/** /**
* 대시보드 메인 페이지 - App.vue의 단일 AppBar 사용 * 대시보드 메인 페이지 - App.vue의 단일 AppBar 사용
* - 중복된 AppBar 제거 (App.vue에서 제공) * - AI 추천을 단일 상세 콘텐츠로 변경
* - 로그아웃 확인 다이얼로그 유지 * - Claude API 연동 준비된 구조
* - 메인 컨텐츠는 그대로 유지
*/ */
const router = useRouter() const router = useRouter()
@ -304,6 +439,7 @@ const animatedValues = ref({})
const logoutDialog = ref(false) const logoutDialog = ref(false)
const chartCanvas = ref(null) const chartCanvas = ref(null)
const currentTime = ref('') const currentTime = ref('')
const aiError = ref('')
// //
const tooltip = ref({ const tooltip = ref({
@ -315,7 +451,7 @@ const tooltip = ref({
target: 0 target: 0
}) })
// - //
const dashboardMetrics = ref([ const dashboardMetrics = ref([
{ {
title: '오늘의 매출', title: '오늘의 매출',
@ -346,7 +482,7 @@ const dashboardMetrics = ref([
}, },
]) ])
// ( ) //
const chartData = ref({ const chartData = ref({
'7d': [ '7d': [
{ label: '6일전', sales: 45, target: 50, date: '06-04' }, { label: '6일전', sales: 45, target: 50, date: '06-04' },
@ -372,49 +508,69 @@ const chartData = ref({
], ],
}) })
// Y
const yAxisLabels = ref(['0', '25', '50', '75', '100']) const yAxisLabels = ref(['0', '25', '50', '75', '100'])
// AI // AI (Claude API )
const aiRecommendations = ref([ const aiRecommendation = ref({
{ emoji: '☀️',
type: 'info', title: '여름 시즌 인스타그램 마케팅 계획',
sections: {
ideas: {
title: '1. 기획 아이디어',
items: [
'여름 음료 메뉴 개발 예: 시원한 아이스 아메리카노, 프라페 등',
'카페 내부에서 <strong>음료와 함께 촬영한 인스타그램용 사진 및 영상</strong> 제작',
'<strong>지역 인플루언서</strong>와 협업하여 방문 후기 및 신메뉴 소개 게시물 게시',
'<strong>인스타그램 스토리</strong>를 활용해 <strong>매일 음료 프로모션</strong> 소식 공유'
]
},
costs: {
title: '2. 예상 비용 및 기대 효과',
items: [
{ item: '촬영 및 편집', amount: '약 300,000원' },
{ item: '인플루언서 협찬', amount: '약 200,000원' }
],
effects: [
'고객 관심 유도 및 매출 상승',
'SNS를 통한 브랜드 인지도 상승',
'재방문율 및 공유 유도'
]
},
warnings: {
title: '3. 주의사항 및 유의점',
items: [
'인스타그램 콘텐츠는 <strong>창의적이고 시각적으로 매력적</strong>이어야 함',
'인플루언서 협업 시, <strong>합리적인 혜택과 협의 조건</strong> 필요'
]
}
},
currentInfo: {
title: '현재 지역 날씨 (서울 강남구 역삼동 기준)',
icon: 'mdi-weather-sunny', icon: 'mdi-weather-sunny',
title: '날씨 기반 추천', color: 'orange',
content: '오늘은 맑은 날씨로 야외 테이크아웃 주문이 증가할 예정입니다.', items: [
}, { label: '기온', value: '30도' },
{ { label: '기상 상황', value: '무더위 지속' }
type: 'success', ],
icon: 'mdi-trending-up', insight: '<strong>시원한 음료에 대한 수요가 매우 높을 것으로 예상</strong>'
title: '트렌드 알림', }
content: '최근 #떡볶이챌린지가 인기입니다. 관련 콘텐츠를 만들어보세요.', })
},
{
type: 'warning',
icon: 'mdi-clock-outline',
title: '시간대 팁',
content: '점심시간(12-14시) 주문 집중. 미리 재료를 준비하세요.',
},
])
// //
const currentChartData = computed(() => chartData.value[chartPeriod.value]) const currentChartData = computed(() => chartData.value[chartPeriod.value])
// chartDataPoints -
const chartDataPoints = computed(() => { const chartDataPoints = computed(() => {
const data = currentChartData.value const data = currentChartData.value
const maxSales = Math.max(...data.map(d => Math.max(d.sales, d.target))) const maxSales = Math.max(...data.map(d => Math.max(d.sales, d.target)))
return data.map((item, index) => { return data.map((item, index) => {
// const chartStartPercent = 8
// Y (60px) (20px) const chartEndPercent = 92
const chartStartPercent = 8 // ( )
const chartEndPercent = 92 // ( )
const chartWidth = chartEndPercent - chartStartPercent const chartWidth = chartEndPercent - chartStartPercent
return { return {
x: chartStartPercent + (index * chartWidth / (data.length - 1)), x: chartStartPercent + (index * chartWidth / (data.length - 1)),
y: 10 + ((item.sales / maxSales) * 80), // 10% 90% y: 10 + ((item.sales / maxSales) * 80),
targetY: 10 + ((item.target / maxSales) * 80), targetY: 10 + ((item.target / maxSales) * 80),
sales: item.sales, sales: item.sales,
target: item.target, target: item.target,
@ -444,7 +600,7 @@ const achievementRate = computed(() => {
return Math.round((totalSales / totalTarget) * 100) return Math.round((totalSales / totalTarget) * 100)
}) })
// //
const getCurrentPeriodLabel = () => { const getCurrentPeriodLabel = () => {
switch (chartPeriod.value) { switch (chartPeriod.value) {
case '7d': return '7일' case '7d': return '7일'
@ -454,21 +610,6 @@ const getCurrentPeriodLabel = () => {
} }
} }
//
const updateTime = () => {
const now = new Date()
const options = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
weekday: 'short',
hour: '2-digit',
minute: '2-digit'
}
currentTime.value = now.toLocaleString('ko-KR', options)
}
//
const animateNumber = (targetValue, index, duration = 2000) => { const animateNumber = (targetValue, index, duration = 2000) => {
const startValue = 0 const startValue = 0
const startTime = Date.now() const startTime = Date.now()
@ -477,17 +618,13 @@ const animateNumber = (targetValue, index, duration = 2000) => {
const elapsed = Date.now() - startTime const elapsed = Date.now() - startTime
const progress = Math.min(elapsed / duration, 1) const progress = Math.min(elapsed / duration, 1)
// easeOutCubic
const easedProgress = 1 - Math.pow(1 - progress, 3) const easedProgress = 1 - Math.pow(1 - progress, 3)
const currentValue = Math.floor(startValue + (targetValue - startValue) * easedProgress) const currentValue = Math.floor(startValue + (targetValue - startValue) * easedProgress)
//
if (typeof targetValue === 'number') { if (typeof targetValue === 'number') {
if (index === 0 || index === 1) { if (index === 0 || index === 1) {
// -
animatedValues.value[index] = `${currentValue.toLocaleString()}` animatedValues.value[index] = `${currentValue.toLocaleString()}`
} else { } else {
// ( )
animatedValues.value[index] = currentValue.toLocaleString() animatedValues.value[index] = currentValue.toLocaleString()
} }
} }
@ -500,7 +637,6 @@ const animateNumber = (targetValue, index, duration = 2000) => {
updateValue() updateValue()
} }
//
const startMetricsAnimation = () => { const startMetricsAnimation = () => {
dashboardMetrics.value.forEach((metric, index) => { dashboardMetrics.value.forEach((metric, index) => {
setTimeout(() => { setTimeout(() => {
@ -509,7 +645,6 @@ const startMetricsAnimation = () => {
}) })
} }
//
const drawChart = async () => { const drawChart = async () => {
await nextTick() await nextTick()
@ -519,7 +654,6 @@ const drawChart = async () => {
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
const data = currentChartData.value const data = currentChartData.value
//
ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.clearRect(0, 0, canvas.width, canvas.height)
const padding = 60 const padding = 60
@ -569,14 +703,12 @@ const drawChart = async () => {
ctx.setLineDash([]) ctx.setLineDash([])
} }
//
const updateChart = async (period) => { const updateChart = async (period) => {
console.log('차트 기간 변경:', period) console.log('차트 기간 변경:', period)
await nextTick() await nextTick()
drawChart() drawChart()
} }
//
const showDataTooltip = (index, event) => { const showDataTooltip = (index, event) => {
const data = currentChartData.value[index] const data = currentChartData.value[index]
const unit = chartPeriod.value === '90d' ? 100 : chartPeriod.value === '30d' ? 10 : 1 const unit = chartPeriod.value === '90d' ? 100 : chartPeriod.value === '30d' ? 10 : 1
@ -603,14 +735,76 @@ const hideTooltip = () => {
tooltip.value.show = false tooltip.value.show = false
} }
// (App.vue ) // AI
const handleLogout = () => { const refreshAiRecommendation = async () => {
logoutDialog.value = true console.log('AI 추천 새로고침')
aiLoading.value = true
aiError.value = ''
try {
// Claude API
await new Promise(resolve => setTimeout(resolve, 2000))
// Claude API
// const response = await callClaudeAPI(prompt)
// aiRecommendation.value = parseClaudeResponse(response)
console.log('AI 추천 새로고침 완료')
appStore.showSnackbar('AI 추천이 업데이트되었습니다', 'success')
} catch (error) {
console.error('AI 추천 로드 실패:', error)
aiError.value = 'AI 추천을 불러오는데 실패했습니다'
appStore.showSnackbar('AI 추천 로드에 실패했습니다', 'error')
} finally {
aiLoading.value = false
}
}
const copyRecommendation = async () => {
try {
let text = `${aiRecommendation.value.emoji} ${aiRecommendation.value.title}\n\n`
//
Object.values(aiRecommendation.value.sections).forEach(section => {
text += `${section.title}\n`
if (section.items) {
section.items.forEach(item => {
// HTML
const cleanItem = item.replace(/<[^>]*>/g, '')
text += `${cleanItem}\n`
})
}
if (section.effects) {
text += '\n기대 효과:\n'
section.effects.forEach(effect => {
text += `${effect}\n`
})
}
text += '\n'
})
await navigator.clipboard.writeText(text)
appStore.showSnackbar('추천 내용이 복사되었습니다', 'success')
} catch (error) {
console.error('복사 실패:', error)
appStore.showSnackbar('복사에 실패했습니다', 'error')
}
}
const createContentFromRecommendation = () => {
//
router.push({
path: '/content/create',
query: {
type: 'sns',
title: aiRecommendation.value.title,
template: 'ai_recommendation'
}
})
} }
const confirmLogout = () => { const confirmLogout = () => {
try { try {
//
authStore.logout() authStore.logout()
appStore.showSnackbar('로그아웃되었습니다.', 'success') appStore.showSnackbar('로그아웃되었습니다.', 'success')
router.push('/login') router.push('/login')
@ -622,40 +816,11 @@ const confirmLogout = () => {
} }
} }
//
const refreshData = async () => {
try {
loading.value = true
// API
await new Promise((resolve) => setTimeout(resolve, 1000))
console.log('대시보드 데이터 새로고침 완료')
} catch (error) {
console.error('데이터 새로고침 실패:', error)
appStore.showSnackbar('데이터를 불러오는데 실패했습니다.', 'error')
} finally {
loading.value = false
}
}
// (App.vue )
defineExpose({
handleLogout
})
// //
onMounted(async () => { onMounted(async () => {
console.log('DashboardView 마운트됨') console.log('DashboardView 마운트됨')
try { try {
//
updateTime()
setInterval(updateTime, 60000) // 1
//
await refreshData()
// //
setTimeout(() => { setTimeout(() => {
startMetricsAnimation() startMetricsAnimation()
@ -665,19 +830,24 @@ onMounted(async () => {
setTimeout(() => { setTimeout(() => {
drawChart() drawChart()
}, 1000) }, 1000)
// AI
setTimeout(() => {
// AI
// refreshAiRecommendation()
}, 1500)
} catch (error) { } catch (error) {
console.error('대시보드 초기화 실패:', error) console.error('대시보드 초기화 실패:', error)
} }
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
//
animatedValues.value = {} animatedValues.value = {}
}) })
</script> </script>
<style scoped> <style scoped>
/* 메트릭 카드 스타일 - 수정된 버전 */ /* 기존 스타일들 유지 */
.metric-card { .metric-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative; position: relative;
@ -707,7 +877,6 @@ onBeforeUnmount(() => {
background: linear-gradient(90deg, var(--v-theme-warning), var(--v-theme-warning-lighten-1)); background: linear-gradient(90deg, var(--v-theme-warning), var(--v-theme-warning-lighten-1));
} }
/* 아이콘을 카드 맨 오른쪽 상단에 절대 위치로 배치 */
.metric-icon-wrapper-absolute { .metric-icon-wrapper-absolute {
position: absolute; position: absolute;
top: 16px; top: 16px;
@ -725,7 +894,7 @@ onBeforeUnmount(() => {
.metric-title { .metric-title {
color: var(--v-theme-on-surface); color: var(--v-theme-on-surface);
font-weight: 600; font-weight: 600;
padding-right: 48px; /* 아이콘 공간 확보 */ padding-right: 48px;
} }
.metric-value { .metric-value {
@ -739,12 +908,7 @@ onBeforeUnmount(() => {
font-weight: 600; font-weight: 600;
} }
.metric-detail { /* 차트 스타일들 유지 */
font-size: 0.75rem;
opacity: 0.7;
}
/* 차트 카드 스타일 */
.chart-card { .chart-card {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -759,7 +923,6 @@ onBeforeUnmount(() => {
height: 100%; height: 100%;
} }
/* 실제 차트 스타일 */
.real-chart { .real-chart {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -873,7 +1036,6 @@ onBeforeUnmount(() => {
transform: scale(1.5); transform: scale(1.5);
} }
/* 수정된 X축 라벨 스타일 */
.x-axis-labels { .x-axis-labels {
position: relative; position: relative;
margin-top: 12px; margin-top: 12px;
@ -891,7 +1053,6 @@ onBeforeUnmount(() => {
display: inline-block; display: inline-block;
} }
/* 툴팁 스타일 */
.chart-tooltip { .chart-tooltip {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
@ -917,7 +1078,7 @@ onBeforeUnmount(() => {
margin: 2px 0; margin: 2px 0;
} }
/* AI 추천 카드 스타일 */ /* AI 추천 카드 새로운 스타일 */
.ai-recommend-card { .ai-recommend-card {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
@ -927,12 +1088,88 @@ onBeforeUnmount(() => {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
} }
.ai-recommendation-item { .ai-recommendation-content {
transition: all 0.2s ease; animation: fadeInUp 0.5s ease-out;
} }
.ai-recommendation-item:hover { @keyframes fadeInUp {
transform: translateX(2px); from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.recommendation-emoji {
font-size: 1.5rem;
}
.recommendation-section {
border-left: 3px solid var(--v-theme-primary);
padding-left: 12px;
}
.recommendation-scroll-content {
scrollbar-width: thin;
scrollbar-color: #ddd transparent;
}
.recommendation-scroll-content::-webkit-scrollbar {
width: 4px;
}
.recommendation-scroll-content::-webkit-scrollbar-track {
background: transparent;
}
.recommendation-scroll-content::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 2px;
}
.recommendation-scroll-content::-webkit-scrollbar-thumb:hover {
background: #bbb;
}
.idea-item,
.warning-item,
.effect-item {
line-height: 1.5;
}
.cost-table {
border: 1px solid #e0e0e0;
border-radius: 6px;
overflow: hidden;
}
.cost-table td {
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
}
.cost-table tr:last-child td {
border-bottom: none;
}
.current-info-box {
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.insight-box {
border: 1px solid #e8f5e8;
}
.recommendation-actions {
border-top: 1px solid #e0e0e0;
}
.info-item {
line-height: 1.4;
} }
/* 반응형 디자인 */ /* 반응형 디자인 */
@ -955,6 +1192,10 @@ onBeforeUnmount(() => {
.chart-area { .chart-area {
height: 250px !important; height: 250px !important;
} }
.recommendation-scroll-content {
max-height: 300px !important;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@ -985,6 +1226,14 @@ onBeforeUnmount(() => {
font-size: 0.65rem; font-size: 0.65rem;
min-width: 30px; min-width: 30px;
} }
.recommendation-scroll-content {
max-height: 250px !important;
}
.recommendation-emoji {
font-size: 1.2rem;
}
} }
/* 다크 테마 지원 */ /* 다크 테마 지원 */
@ -1005,4 +1254,17 @@ onBeforeUnmount(() => {
background: #1E293B !important; background: #1E293B !important;
border-color: #334155; border-color: #334155;
} }
.v-theme--dark .current-info-box {
border-color: rgba(255, 255, 255, 0.1);
}
.v-theme--dark .insight-box {
background-color: rgba(255, 255, 255, 0.05) !important;
border-color: rgba(255, 255, 255, 0.1);
}
.v-theme--dark .recommendation-actions {
border-color: rgba(255, 255, 255, 0.1);
}
</style> </style>