fix(pop-dashboard): 아이템/모드 레이아웃 수정 및 게이지 설정 버그 수정
설정 패널 버그 수정 (PopDashboardConfig): - gaugeConfig 스프레드 순서 수정: min/max/target 값이 기존값에 덮어씌워지는 문제 해결 - 스프레드를 먼저 적용 후 변경 필드를 뒤에 배치하여 올바르게 반영 아이템 레이아웃 개선: - KpiCard/StatCard: items-center justify-center 추가로 셀 내 중앙 정렬 - GaugeItem: SVG를 flex-1 영역에서 반응형 렌더링 (h-full w-auto) - GaugeItem: preserveAspectRatio로 비율 유지, 라벨/목표값 shrink-0 모드 레이아웃 개선: - ArrowsMode: 아이템이 전체 영역 사용, 화살표/인디케이터를 overlay로 변경 - ArrowsMode: 화살표 크기 축소 (h-11 -> h-8), backdrop-blur 추가 - AutoSlideMode: 슬라이드 컨테이너를 absolute inset-0으로 전체 영역 활용 - AutoSlideMode: 인디케이터를 하단 overlay로 변경 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7a71fc6ca7
commit
bd7bf69a99
|
|
@ -1307,9 +1307,9 @@ function ItemEditor({
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...item,
|
...item,
|
||||||
gaugeConfig: {
|
gaugeConfig: {
|
||||||
|
...item.gaugeConfig,
|
||||||
min: parseInt(e.target.value) || 0,
|
min: parseInt(e.target.value) || 0,
|
||||||
max: item.gaugeConfig?.max ?? 100,
|
max: item.gaugeConfig?.max ?? 100,
|
||||||
...item.gaugeConfig,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1325,9 +1325,9 @@ function ItemEditor({
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...item,
|
...item,
|
||||||
gaugeConfig: {
|
gaugeConfig: {
|
||||||
|
...item.gaugeConfig,
|
||||||
min: item.gaugeConfig?.min ?? 0,
|
min: item.gaugeConfig?.min ?? 0,
|
||||||
max: parseInt(e.target.value) || 100,
|
max: parseInt(e.target.value) || 100,
|
||||||
...item.gaugeConfig,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -1343,9 +1343,9 @@ function ItemEditor({
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...item,
|
...item,
|
||||||
gaugeConfig: {
|
gaugeConfig: {
|
||||||
|
...item.gaugeConfig,
|
||||||
min: item.gaugeConfig?.min ?? 0,
|
min: item.gaugeConfig?.min ?? 0,
|
||||||
max: item.gaugeConfig?.max ?? 100,
|
max: item.gaugeConfig?.max ?? 100,
|
||||||
...item.gaugeConfig,
|
|
||||||
target: parseInt(e.target.value) || undefined,
|
target: parseInt(e.target.value) || undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -69,17 +69,21 @@ export function GaugeItemComponent({
|
||||||
const largeArcFlag = percentage > 50 ? 1 : 0;
|
const largeArcFlag = percentage > 50 ? 1 : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="@container flex h-full w-full flex-col items-center justify-center p-3">
|
<div className="@container flex h-full w-full flex-col items-center justify-center p-2">
|
||||||
{/* 라벨 */}
|
{/* 라벨 */}
|
||||||
{visibility.showLabel && (
|
{visibility.showLabel && (
|
||||||
<p className="mb-1 text-xs text-muted-foreground @[250px]:text-sm">
|
<p className="shrink-0 truncate text-xs text-muted-foreground @[250px]:text-sm">
|
||||||
{item.label}
|
{item.label}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 게이지 SVG */}
|
{/* 게이지 SVG - 높이/너비 모두 반응형 */}
|
||||||
<div className="relative w-full max-w-[200px]">
|
<div className="flex min-h-0 flex-1 items-center justify-center w-full">
|
||||||
<svg viewBox="0 0 200 110" className="w-full">
|
<svg
|
||||||
|
viewBox="0 0 200 110"
|
||||||
|
className="h-full w-auto max-w-full"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
>
|
||||||
{/* 배경 반원 (회색) */}
|
{/* 배경 반원 (회색) */}
|
||||||
<path
|
<path
|
||||||
d={`M ${cx - radius} ${cy} A ${radius} ${radius} 0 0 1 ${cx + radius} ${cy}`}
|
d={`M ${cx - radius} ${cy} A ${radius} ${radius} 0 0 1 ${cx + radius} ${cy}`}
|
||||||
|
|
@ -128,7 +132,7 @@ export function GaugeItemComponent({
|
||||||
|
|
||||||
{/* 목표값 */}
|
{/* 목표값 */}
|
||||||
{visibility.showTarget && (
|
{visibility.showTarget && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="shrink-0 text-xs text-muted-foreground">
|
||||||
목표: {abbreviateNumber(target)}
|
목표: {abbreviateNumber(target)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export function KpiCardComponent({
|
||||||
const valueColor = getColorForValue(displayValue, kpiConfig?.colorRanges);
|
const valueColor = getColorForValue(displayValue, kpiConfig?.colorRanges);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="@container flex h-full w-full flex-col justify-center p-3">
|
<div className="@container flex h-full w-full flex-col items-center justify-center p-3">
|
||||||
{/* 라벨 */}
|
{/* 라벨 */}
|
||||||
{visibility.showLabel && (
|
{visibility.showLabel && (
|
||||||
<p className="text-xs text-muted-foreground @[250px]:text-sm">
|
<p className="text-xs text-muted-foreground @[250px]:text-sm">
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export function StatCardComponent({ item, categoryData }: StatCardProps) {
|
||||||
const total = Object.values(categoryData).reduce((sum, v) => sum + v, 0);
|
const total = Object.values(categoryData).reduce((sum, v) => sum + v, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="@container flex h-full w-full flex-col p-3">
|
<div className="@container flex h-full w-full flex-col items-center justify-center p-3">
|
||||||
{/* 라벨 */}
|
{/* 라벨 */}
|
||||||
{visibility.showLabel && (
|
{visibility.showLabel && (
|
||||||
<p className="mb-1 text-xs text-muted-foreground @[250px]:text-sm">
|
<p className="mb-1 text-xs text-muted-foreground @[250px]:text-sm">
|
||||||
|
|
|
||||||
|
|
@ -47,42 +47,37 @@ export function ArrowsModeComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col">
|
<div className="relative h-full w-full">
|
||||||
{/* 콘텐츠 + 화살표 */}
|
{/* 아이템 (전체 영역 사용) */}
|
||||||
<div className="relative flex min-h-0 flex-1 items-center">
|
<div className="h-full w-full">
|
||||||
{/* 왼쪽 화살표 */}
|
{renderItem(currentIndex)}
|
||||||
{itemCount > 1 && (
|
</div>
|
||||||
|
|
||||||
|
{/* 좌우 화살표 (콘텐츠 위에 겹침) */}
|
||||||
|
{itemCount > 1 && (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={goToPrev}
|
onClick={goToPrev}
|
||||||
className="absolute left-0 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-background/80 shadow-sm transition-colors hover:bg-accent active:scale-95"
|
className="absolute left-1 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-background/70 shadow-sm backdrop-blur-sm transition-all hover:bg-background/90 active:scale-95"
|
||||||
aria-label="이전"
|
aria-label="이전"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-5 w-5" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 아이템 */}
|
|
||||||
<div className="h-full w-full px-12">
|
|
||||||
{renderItem(currentIndex)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 오른쪽 화살표 */}
|
|
||||||
{itemCount > 1 && (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={goToNext}
|
onClick={goToNext}
|
||||||
className="absolute right-0 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-background/80 shadow-sm transition-colors hover:bg-accent active:scale-95"
|
className="absolute right-1 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-full bg-background/70 shadow-sm backdrop-blur-sm transition-all hover:bg-background/90 active:scale-95"
|
||||||
aria-label="다음"
|
aria-label="다음"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-5 w-5" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
</>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* 페이지 인디케이터 */}
|
{/* 페이지 인디케이터 (콘텐츠 하단에 겹침) */}
|
||||||
{showIndicator && itemCount > 1 && (
|
{showIndicator && itemCount > 1 && (
|
||||||
<div className="flex items-center justify-center gap-1.5 py-1">
|
<div className="absolute bottom-1 left-0 right-0 z-10 flex items-center justify-center gap-1.5">
|
||||||
{Array.from({ length: itemCount }).map((_, i) => (
|
{Array.from({ length: itemCount }).map((_, i) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -90,39 +90,39 @@ export function AutoSlideModeComponent({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex h-full w-full flex-col"
|
className="relative h-full w-full"
|
||||||
onClick={handlePause}
|
onClick={handlePause}
|
||||||
onTouchStart={handlePause}
|
onTouchStart={handlePause}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
{/* 콘텐츠 (슬라이드 애니메이션) */}
|
{/* 콘텐츠 (슬라이드 애니메이션) */}
|
||||||
<div className="relative min-h-0 flex-1 overflow-hidden">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 transition-transform duration-500 ease-in-out"
|
className="flex h-full transition-transform duration-500 ease-in-out"
|
||||||
style={{ transform: `translateX(-${currentIndex * 100}%)` }}
|
style={{
|
||||||
|
width: `${itemCount * 100}%`,
|
||||||
|
transform: `translateX(-${currentIndex * (100 / itemCount)}%)`,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{Array.from({ length: itemCount }).map((_, i) => (
|
||||||
className="flex h-full"
|
<div
|
||||||
style={{ width: `${itemCount * 100}%` }}
|
key={i}
|
||||||
>
|
className="h-full"
|
||||||
{Array.from({ length: itemCount }).map((_, i) => (
|
style={{ width: `${100 / itemCount}%` }}
|
||||||
<div
|
>
|
||||||
key={i}
|
{renderItem(i)}
|
||||||
className="h-full"
|
</div>
|
||||||
style={{ width: `${100 / itemCount}%` }}
|
))}
|
||||||
>
|
|
||||||
{renderItem(i)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 인디케이터 + 일시정지 표시 */}
|
{/* 인디케이터 (콘텐츠 하단에 겹침) */}
|
||||||
{showIndicator && itemCount > 1 && (
|
{showIndicator && itemCount > 1 && (
|
||||||
<div className="flex items-center justify-center gap-1.5 py-1">
|
<div className="absolute bottom-1 left-0 right-0 z-10 flex items-center justify-center gap-1.5">
|
||||||
{isPaused && (
|
{isPaused && (
|
||||||
<span className="mr-2 text-[10px] text-muted-foreground">일시정지</span>
|
<span className="mr-2 text-[10px] text-muted-foreground/70">
|
||||||
|
일시정지
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{Array.from({ length: itemCount }).map((_, i) => (
|
{Array.from({ length: itemCount }).map((_, i) => (
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue