샤드시옌으로 쫙 수정

This commit is contained in:
leeheejin
2025-10-29 17:53:03 +09:00
parent 2517261db9
commit 437e0c331c
87 changed files with 1493 additions and 1491 deletions

View File

@@ -26,124 +26,124 @@ import {
// 위젯 동적 임포트
const WeatherWidget = dynamic(() => import("@/components/dashboard/widgets/WeatherWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const ExchangeWidget = dynamic(() => import("@/components/dashboard/widgets/ExchangeWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const CalculatorWidget = dynamic(() => import("@/components/dashboard/widgets/CalculatorWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const VehicleStatusWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleStatusWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const VehicleListWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleListWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const VehicleMapOnlyWidget = dynamic(() => import("@/components/dashboard/widgets/VehicleMapOnlyWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 범용 지도 위젯 (차량, 창고, 고객 등 모든 위치 위젯 통합)
const MapSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/MapSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 🧪 테스트용 지도 위젯 (REST API 지원)
const MapTestWidget = dynamic(() => import("@/components/dashboard/widgets/MapTestWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 🧪 테스트용 지도 위젯 V2 (다중 데이터 소스)
const MapTestWidgetV2 = dynamic(() => import("@/components/dashboard/widgets/MapTestWidgetV2"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 🧪 테스트용 차트 위젯 (다중 데이터 소스)
const ChartTestWidget = dynamic(() => import("@/components/dashboard/widgets/ChartTestWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const ListTestWidget = dynamic(
() => import("@/components/dashboard/widgets/ListTestWidget").then((mod) => ({ default: mod.ListTestWidget })),
{
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
},
);
const CustomMetricTestWidget = dynamic(() => import("@/components/dashboard/widgets/CustomMetricTestWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const RiskAlertTestWidget = dynamic(() => import("@/components/dashboard/widgets/RiskAlertTestWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 범용 상태 요약 위젯 (차량, 배송 등 모든 상태 위젯 통합)
const StatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/StatusSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 범용 목록 위젯 (차량, 기사, 제품 등 모든 목록 위젯 통합) - 다른 분 작업 중, 임시 주석
/* const ListSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/ListSummaryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
}); */
// 개별 위젯들 (주석 처리 - StatusSummaryWidget으로 통합됨)
// const DeliveryStatusSummaryWidget = dynamic(() => import("@/components/dashboard/widgets/DeliveryStatusSummaryWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
// });
// const DeliveryTodayStatsWidget = dynamic(() => import("@/components/dashboard/widgets/DeliveryTodayStatsWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
// });
// const CargoListWidget = dynamic(() => import("@/components/dashboard/widgets/CargoListWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
// });
// const CustomerIssuesWidget = dynamic(() => import("@/components/dashboard/widgets/CustomerIssuesWidget"), {
// ssr: false,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500">로딩 중...</div>,
// loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground">로딩 중...</div>,
// });
const RiskAlertWidget = dynamic(() => import("@/components/dashboard/widgets/RiskAlertWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const TaskWidget = dynamic(() => import("@/components/dashboard/widgets/TaskWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const BookingAlertWidget = dynamic(() => import("@/components/dashboard/widgets/BookingAlertWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
const DocumentWidget = dynamic(() => import("@/components/dashboard/widgets/DocumentWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 시계 위젯 임포트
@@ -160,25 +160,25 @@ import { Button } from "@/components/ui/button";
// 야드 관리 3D 위젯
const YardManagement3DWidget = dynamic(() => import("./widgets/YardManagement3DWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 작업 이력 위젯
const WorkHistoryWidget = dynamic(() => import("@/components/dashboard/widgets/WorkHistoryWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 커스텀 통계 카드 위젯
const CustomStatsWidget = dynamic(() => import("@/components/dashboard/widgets/CustomStatsWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
// 사용자 커스텀 카드 위젯
const CustomMetricWidget = dynamic(() => import("@/components/dashboard/widgets/CustomMetricWidget"), {
ssr: false,
loading: () => <div className="flex h-full items-center justify-center text-sm text-gray-500"> ...</div>,
loading: () => <div className="flex h-full items-center justify-center text-sm text-muted-foreground"> ...</div>,
});
interface CanvasElementProps {
@@ -712,33 +712,33 @@ export function CanvasElement({
if (element.type === "chart") {
switch (element.subtype) {
case "bar":
return "bg-gradient-to-br from-indigo-400 to-purple-600";
return "bg-gradient-to-br from-primary to-purple-500";
case "pie":
return "bg-gradient-to-br from-pink-400 to-red-500";
return "bg-gradient-to-br from-destructive to-destructive/80";
case "line":
return "bg-gradient-to-br from-blue-400 to-cyan-400";
return "bg-gradient-to-br from-primary to-primary/80";
default:
return "bg-gray-200";
return "bg-muted";
}
} else if (element.type === "widget") {
switch (element.subtype) {
case "exchange":
return "bg-gradient-to-br from-pink-400 to-yellow-400";
return "bg-gradient-to-br from-warning to-warning/80";
case "weather":
return "bg-gradient-to-br from-cyan-400 to-indigo-800";
return "bg-gradient-to-br from-primary to-primary/80";
case "clock":
return "bg-gradient-to-br from-teal-400 to-cyan-600";
return "bg-gradient-to-br from-primary to-primary/80";
case "calendar":
return "bg-gradient-to-br from-indigo-400 to-purple-600";
return "bg-gradient-to-br from-primary to-purple-500";
case "driver-management":
return "bg-gradient-to-br from-blue-400 to-indigo-600";
return "bg-gradient-to-br from-primary to-primary";
case "list":
return "bg-gradient-to-br from-cyan-400 to-blue-600";
return "bg-gradient-to-br from-primary to-primary/80";
default:
return "bg-gray-200";
return "bg-muted";
}
}
return "bg-gray-200";
return "bg-muted";
};
// 드래그/리사이즈 중일 때는 임시 위치/크기 사용, 아니면 실제 값 사용
@@ -758,7 +758,7 @@ export function CanvasElement({
<div
ref={elementRef}
data-element-id={element.id}
className={`absolute min-h-[120px] min-w-[120px] cursor-move overflow-hidden rounded-lg border-2 bg-white shadow-lg ${isSelected ? "border-blue-500 shadow-blue-200" : "border-gray-400"} ${isDragging || isResizing ? "transition-none" : "transition-all duration-150"} `}
className={`absolute min-h-[120px] min-w-[120px] cursor-move overflow-hidden rounded-lg border-2 bg-background shadow-lg ${isSelected ? "border-primary ring-2 ring-primary/20" : "border-border"} ${isDragging || isResizing ? "transition-none" : "transition-all duration-150"} `}
style={{
left: displayPosition.x,
top: displayPosition.y,
@@ -809,7 +809,7 @@ export function CanvasElement({
)}
{/* 제목 */}
{!element.type || element.type !== "chart" ? (
<span className="text-xs font-bold text-gray-800">{element.customTitle || element.title}</span>
<span className="text-xs font-bold text-foreground">{element.customTitle || element.title}</span>
) : null}
</div>
<div className="flex gap-1">
@@ -817,7 +817,7 @@ export function CanvasElement({
<Button
variant="ghost"
size="icon"
className="element-close hover:bg-destructive h-5 w-5 text-gray-400 hover:text-white"
className="element-close hover:bg-destructive h-5 w-5 text-muted-foreground hover:text-white"
onClick={handleRemove}
onMouseDown={(e) => e.stopPropagation()}
title="삭제"
@@ -831,9 +831,9 @@ export function CanvasElement({
<div className="relative h-[calc(100%-32px)] px-2 pb-2">
{element.type === "chart" ? (
// 차트 렌더링
<div className="h-full w-full bg-white">
<div className="h-full w-full bg-background">
{isLoadingData ? (
<div className="flex h-full w-full items-center justify-center text-gray-500">
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="border-primary mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-t-transparent" />
<div className="text-sm"> ...</div>
@@ -926,7 +926,7 @@ export function CanvasElement({
) : element.type === "widget" && element.subtype === "status-summary" ? (
// 커스텀 상태 카드 - 범용 위젯
<div className="widget-interactive-area h-full w-full">
<StatusSummaryWidget element={element} title="상태 요약" icon="📊" bgGradient="from-slate-50 to-blue-50" />
<StatusSummaryWidget element={element} title="상태 요약" icon="📊" bgGradient="from-background to-primary/10" />
</div>
) : /* element.type === "widget" && element.subtype === "list-summary" ? (
// 커스텀 목록 카드 - 범용 위젯 (다른 분 작업 중 - 임시 주석)
@@ -940,7 +940,7 @@ export function CanvasElement({
element={element}
title="배송/화물 현황"
icon="📦"
bgGradient="from-slate-50 to-blue-50"
bgGradient="from-background to-primary/10"
/>
</div>
) : element.type === "widget" && element.subtype === "delivery-status-summary" ? (
@@ -950,7 +950,7 @@ export function CanvasElement({
element={element}
title="배송 상태 요약"
icon="📊"
bgGradient="from-slate-50 to-blue-50"
bgGradient="from-background to-primary/10"
statusConfig={{
: { label: "배송중", color: "blue" },
: { label: "완료", color: "green" },
@@ -966,7 +966,7 @@ export function CanvasElement({
element={element}
title="오늘 처리 현황"
icon="📈"
bgGradient="from-slate-50 to-green-50"
bgGradient="from-background to-success/10"
/>
</div>
) : element.type === "widget" && element.subtype === "cargo-list" ? (
@@ -976,7 +976,7 @@ export function CanvasElement({
element={element}
title="화물 목록"
icon="📦"
bgGradient="from-slate-50 to-orange-50"
bgGradient="from-background to-warning/10"
/>
</div>
) : element.type === "widget" && element.subtype === "customer-issues" ? (
@@ -986,7 +986,7 @@ export function CanvasElement({
element={element}
title="고객 클레임/이슈"
icon="⚠️"
bgGradient="from-slate-50 to-red-50"
bgGradient="from-background to-destructive/10"
/>
</div>
) : element.type === "widget" && element.subtype === "risk-alert" ? (
@@ -1111,7 +1111,7 @@ function ResizeHandle({ position, onMouseDown }: ResizeHandleProps) {
return (
<div
className={`resize-handle absolute h-3 w-3 border border-white bg-green-500 ${getPositionClass()} `}
className={`resize-handle absolute h-3 w-3 border border-white bg-success ${getPositionClass()} `}
onMouseDown={(e) => onMouseDown(e, position)}
/>
);

View File

@@ -117,17 +117,17 @@ export function ChartConfigPanel({
:
<div className="mt-1 flex flex-wrap gap-1">
{complexColumns.map((col) => (
<Badge key={col} variant="outline" className="bg-red-50">
<Badge key={col} variant="outline" className="bg-destructive/10">
{col} ({columnTypes[col]})
</Badge>
))}
</div>
</div>
<div className="mt-2 text-xs text-gray-600">
<div className="mt-2 text-xs text-foreground">
<strong> :</strong> JSON Path를 .
<br />
: <code className="rounded bg-gray-100 px-1">main</code> {" "}
<code className="rounded bg-gray-100 px-1">data.items</code>
: <code className="rounded bg-muted px-1">main</code> {" "}
<code className="rounded bg-muted px-1">data.items</code>
</div>
</AlertDescription>
</Alert>
@@ -135,7 +135,7 @@ export function ChartConfigPanel({
{/* 차트 제목 */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700"> </Label>
<Label className="text-xs font-medium text-foreground"> </Label>
<Input
type="text"
value={currentConfig.title || ""}
@@ -149,9 +149,9 @@ export function ChartConfigPanel({
{/* X축 설정 */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700">
<Label className="text-xs font-medium text-foreground">
X축 ()
<span className="ml-1 text-red-500">*</span>
<span className="ml-1 text-destructive">*</span>
</Label>
<Select value={currentConfig.xAxis || undefined} onValueChange={(value) => updateConfig({ xAxis: value })}>
<SelectTrigger className="h-8 text-xs">
@@ -170,39 +170,39 @@ export function ChartConfigPanel({
return (
<SelectItem key={col} value={col} className="text-xs">
{col}
{previewText && <span className="ml-1.5 text-[10px] text-gray-500">(: {previewText})</span>}
{previewText && <span className="ml-1.5 text-[10px] text-muted-foreground">(: {previewText})</span>}
</SelectItem>
);
})}
</SelectContent>
</Select>
{simpleColumns.length === 0 && (
<p className="text-[11px] text-red-500"> . JSON Path를 .</p>
<p className="text-[11px] text-destructive"> . JSON Path를 .</p>
)}
</div>
{/* Y축 설정 (다중 선택 가능) */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700">
<Label className="text-xs font-medium text-foreground">
Y축 () -
{!isPieChart && !isApiSource && <span className="ml-1 text-red-500">*</span>}
{!isPieChart && !isApiSource && <span className="ml-1 text-destructive">*</span>}
{(isPieChart || isApiSource) && (
<span className="ml-1.5 text-[11px] text-gray-500">( - + )</span>
<span className="ml-1.5 text-[11px] text-muted-foreground">( - + )</span>
)}
</Label>
<div className="max-h-48 overflow-y-auto rounded border border-gray-200 bg-gray-50 p-2">
<div className="max-h-48 overflow-y-auto rounded border border-border bg-muted p-2">
<div className="space-y-1.5">
{/* 숫자 타입 우선 표시 */}
{numericColumns.length > 0 && (
<>
<div className="mb-1.5 text-[11px] font-medium text-green-700"> ()</div>
<div className="mb-1.5 text-[11px] font-medium text-success"> ()</div>
{numericColumns.map((col) => {
const isSelected = Array.isArray(currentConfig.yAxis)
? currentConfig.yAxis.includes(col)
: currentConfig.yAxis === col;
return (
<div key={col} className="flex items-center gap-1.5 rounded border-green-500 bg-green-50 p-1.5">
<div key={col} className="flex items-center gap-1.5 rounded border-success bg-success/10 p-1.5">
<Checkbox
checked={isSelected}
onCheckedChange={(checked) => {
@@ -229,7 +229,7 @@ export function ChartConfigPanel({
<Label className="flex-1 cursor-pointer text-xs font-normal">
<span className="font-medium">{col}</span>
{sampleData[col] !== undefined && (
<span className="ml-1.5 text-[10px] text-gray-600">(: {sampleData[col]})</span>
<span className="ml-1.5 text-[10px] text-foreground">(: {sampleData[col]})</span>
)}
</Label>
</div>
@@ -242,7 +242,7 @@ export function ChartConfigPanel({
{simpleColumns.filter((col) => !numericColumns.includes(col)).length > 0 && (
<>
{numericColumns.length > 0 && <div className="my-1.5 border-t"></div>}
<div className="mb-1.5 text-[11px] font-medium text-gray-600"> </div>
<div className="mb-1.5 text-[11px] font-medium text-foreground"> </div>
{simpleColumns
.filter((col) => !numericColumns.includes(col))
.map((col) => {
@@ -251,7 +251,7 @@ export function ChartConfigPanel({
: currentConfig.yAxis === col;
return (
<div key={col} className="flex items-center gap-1.5 rounded p-1.5 hover:bg-gray-50">
<div key={col} className="flex items-center gap-1.5 rounded p-1.5 hover:bg-muted">
<Checkbox
checked={isSelected}
onCheckedChange={(checked) => {
@@ -278,7 +278,7 @@ export function ChartConfigPanel({
<Label className="flex-1 cursor-pointer text-xs font-normal">
{col}
{sampleData[col] !== undefined && (
<span className="ml-1.5 text-[10px] text-gray-500">
<span className="ml-1.5 text-[10px] text-muted-foreground">
(: {String(sampleData[col]).substring(0, 30)})
</span>
)}
@@ -291,9 +291,9 @@ export function ChartConfigPanel({
</div>
</div>
{simpleColumns.length === 0 && (
<p className="text-[11px] text-red-500"> . JSON Path를 .</p>
<p className="text-[11px] text-destructive"> . JSON Path를 .</p>
)}
<p className="text-[11px] text-gray-500">
<p className="text-[11px] text-muted-foreground">
: 여러 (: 갤럭시 vs )
</p>
</div>
@@ -302,9 +302,9 @@ export function ChartConfigPanel({
{/* 집계 함수 */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700">
<Label className="text-xs font-medium text-foreground">
<span className="ml-1.5 text-[11px] text-gray-500">( )</span>
<span className="ml-1.5 text-[11px] text-muted-foreground">( )</span>
</Label>
<Select
value={currentConfig.aggregation || "none"}
@@ -338,16 +338,16 @@ export function ChartConfigPanel({
</SelectItem>
</SelectContent>
</Select>
<p className="text-[11px] text-gray-500">
<p className="text-[11px] text-muted-foreground">
. (: 부서별 , )
</p>
</div>
{/* 그룹핑 필드 (선택사항) */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700">
<Label className="text-xs font-medium text-foreground">
()
<span className="ml-1.5 text-[11px] text-gray-500">( )</span>
<span className="ml-1.5 text-[11px] text-muted-foreground">( )</span>
</Label>
<Select
value={currentConfig.groupBy || undefined}
@@ -373,7 +373,7 @@ export function ChartConfigPanel({
{/* 차트 색상 */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700"> </Label>
<Label className="text-xs font-medium text-foreground"> </Label>
<div className="grid grid-cols-4 gap-2">
{[
["#3B82F6", "#EF4444", "#10B981", "#F59E0B"], // 기본
@@ -387,8 +387,8 @@ export function ChartConfigPanel({
onClick={() => updateConfig({ colors: colorSet })}
className={`flex h-8 rounded border-2 transition-colors ${
JSON.stringify(currentConfig.colors) === JSON.stringify(colorSet)
? "border-gray-800"
: "border-gray-300 hover:border-gray-400"
? "border-foreground"
: "border-border hover:border-border/80"
}`}
>
{colorSet.map((color, idx) => (

View File

@@ -466,7 +466,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
return (
<div
ref={ref}
className={`dashboard-canvas relative w-full ${isDragOver ? "bg-blue-50/50" : ""} `}
className={`dashboard-canvas relative w-full ${isDragOver ? "bg-primary/5" : ""} `}
style={{
backgroundColor,
height: `${canvasHeight}px`,
@@ -512,7 +512,7 @@ export const DashboardCanvas = forwardRef<HTMLDivElement, DashboardCanvasProps>(
)}
{/* 배치된 요소들 렌더링 */}
{elements.length === 0 && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center text-gray-400">
<div className="pointer-events-none absolute inset-0 flex items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="text-sm"> </div>
</div>

View File

@@ -582,11 +582,11 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
// 로딩 중이면 로딩 화면 표시
if (isLoading) {
return (
<div className="flex h-full items-center justify-center bg-gray-50">
<div className="flex h-full items-center justify-center bg-muted">
<div className="text-center">
<div className="border-primary mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-t-transparent" />
<div className="text-lg font-medium text-gray-700"> ...</div>
<div className="mt-1 text-sm text-gray-500"> </div>
<div className="text-lg font-medium text-foreground"> ...</div>
<div className="mt-1 text-sm text-muted-foreground"> </div>
</div>
</div>
);
@@ -594,7 +594,7 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
return (
<DashboardProvider>
<div className="flex h-full flex-col bg-gray-50">
<div className="flex h-full flex-col bg-muted">
{/* 상단 메뉴바 */}
<DashboardTopMenu
onSaveLayout={saveLayout}
@@ -610,7 +610,7 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
{/* 캔버스 영역 - 해상도에 따른 크기, 중앙 정렬 */}
{/* overflow-auto 제거 - 외부 페이지 스크롤 사용 */}
<div className="dashboard-canvas-container flex flex-1 items-start justify-center bg-gray-100 p-8">
<div className="dashboard-canvas-container flex flex-1 items-start justify-center bg-muted p-8">
<div
className="relative"
style={{
@@ -679,8 +679,8 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<CheckCircle2 className="h-6 w-6 text-green-600" />
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-success/10">
<CheckCircle2 className="h-6 w-6 text-success" />
</div>
<DialogTitle className="text-center"> </DialogTitle>
<DialogDescription className="text-center"> .</DialogDescription>
@@ -711,7 +711,7 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleClearConfirm} className="bg-red-600 hover:bg-red-700">
<AlertDialogAction onClick={handleClearConfirm} className="bg-destructive hover:bg-destructive/90">
</AlertDialogAction>
</AlertDialogFooter>

View File

@@ -177,7 +177,7 @@ export function DashboardSaveModal({
{/* 대시보드 이름 */}
<div className="space-y-2">
<Label htmlFor="title">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="title"
@@ -235,7 +235,7 @@ export function DashboardSaveModal({
{/* 메뉴 할당 옵션 */}
{assignToMenu && (
<div className="ml-6 space-y-4 border-l-2 border-gray-200 pl-4">
<div className="ml-6 space-y-4 border-l-2 border-border pl-4">
{/* 메뉴 타입 선택 */}
<div className="space-y-2">
<Label> </Label>
@@ -260,8 +260,8 @@ export function DashboardSaveModal({
<Label> </Label>
{loadingMenus ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
<span className="ml-2 text-sm text-gray-500"> ...</span>
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
<span className="ml-2 text-sm text-muted-foreground"> ...</span>
</div>
) : (
<div className="space-y-2">
@@ -273,7 +273,7 @@ export function DashboardSaveModal({
<SelectGroup>
<SelectLabel>{menuType === "admin" ? "관리자 메뉴" : "사용자 메뉴"}</SelectLabel>
{flatMenus.length === 0 ? (
<div className="px-2 py-3 text-sm text-gray-500"> .</div>
<div className="px-2 py-3 text-sm text-muted-foreground"> .</div>
) : (
flatMenus.map((menu) => (
<SelectItem key={menu.uniqueKey} value={menu.id}>
@@ -285,7 +285,7 @@ export function DashboardSaveModal({
</SelectContent>
</Select>
{selectedMenuId && (
<div className="rounded-md bg-gray-50 p-2 text-sm text-gray-700">
<div className="rounded-md bg-muted p-2 text-sm text-foreground">
:{" "}
<span className="font-medium">{flatMenus.find((m) => m.id === selectedMenuId)?.label}</span>
</div>
@@ -293,7 +293,7 @@ export function DashboardSaveModal({
</div>
)}
{assignToMenu && selectedMenuId && (
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
URL이 .
{menuType === "admin" && " (관리자 모드 파라미터 포함)"}
</p>

View File

@@ -16,13 +16,13 @@ interface DashboardToolbarProps {
export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackgroundColor, onCanvasBackgroundColorChange }: DashboardToolbarProps) {
const [showColorPicker, setShowColorPicker] = useState(false);
return (
<div className="absolute top-5 left-5 bg-white p-3 rounded-lg shadow-lg z-50 flex gap-3">
<div className="absolute top-5 left-5 bg-background p-3 rounded-lg shadow-lg z-50 flex gap-3">
<button
onClick={onClearCanvas}
className="
px-4 py-2 border border-gray-300 bg-white rounded-md
text-sm font-medium text-gray-700
hover:bg-gray-50 hover:border-gray-400
px-4 py-2 border border-border bg-background rounded-md
text-sm font-medium text-foreground
hover:bg-muted hover:border-border/80
transition-colors duration-200
"
>
@@ -32,9 +32,9 @@ export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackground
<button
onClick={onSaveLayout}
className="
px-4 py-2 border border-gray-300 bg-white rounded-md
text-sm font-medium text-gray-700
hover:bg-gray-50 hover:border-gray-400
px-4 py-2 border border-border bg-background rounded-md
text-sm font-medium text-foreground
hover:bg-muted hover:border-border/80
transition-colors duration-200
"
>
@@ -46,36 +46,36 @@ export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackground
<button
onClick={() => setShowColorPicker(!showColorPicker)}
className="
px-4 py-2 border border-gray-300 bg-white rounded-md
text-sm font-medium text-gray-700
hover:bg-gray-50 hover:border-gray-400
px-4 py-2 border border-border bg-background rounded-md
text-sm font-medium text-foreground
hover:bg-muted hover:border-border/80
transition-colors duration-200
flex items-center gap-2
"
>
🎨
<div
className="w-4 h-4 rounded border border-gray-300"
className="w-4 h-4 rounded border border-border"
style={{ backgroundColor: canvasBackgroundColor }}
/>
</button>
{/* 색상 선택 패널 */}
{showColorPicker && (
<div className="absolute top-full left-0 mt-2 bg-white p-4 rounded-lg shadow-xl z-50 border border-gray-200 w-[280px]">
<div className="absolute top-full left-0 mt-2 bg-background p-4 rounded-lg shadow-xl z-50 border border-border w-[280px]">
<div className="flex items-center gap-3 mb-3">
<input
type="color"
value={canvasBackgroundColor}
onChange={(e) => onCanvasBackgroundColorChange(e.target.value)}
className="h-10 w-16 border border-gray-300 rounded cursor-pointer"
className="h-10 w-16 border border-border rounded cursor-pointer"
/>
<input
type="text"
value={canvasBackgroundColor}
onChange={(e) => onCanvasBackgroundColorChange(e.target.value)}
placeholder="#ffffff"
className="flex-1 px-2 py-1 text-sm border border-gray-300 rounded"
className="flex-1 px-2 py-1 text-sm border border-border rounded"
/>
</div>
@@ -89,7 +89,7 @@ export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackground
<button
key={color}
onClick={() => onCanvasBackgroundColorChange(color)}
className={`h-8 rounded border-2 ${canvasBackgroundColor === color ? 'border-blue-500 ring-2 ring-blue-200' : 'border-gray-300'}`}
className={`h-8 rounded border-2 ${canvasBackgroundColor === color ? 'border-primary ring-2 ring-primary/20' : 'border-border'}`}
style={{ backgroundColor: color }}
title={color}
/>
@@ -98,7 +98,7 @@ export function DashboardToolbar({ onClearCanvas, onSaveLayout, canvasBackground
<button
onClick={() => setShowColorPicker(false)}
className="w-full px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded hover:bg-gray-50"
className="w-full px-3 py-1.5 text-sm text-foreground border border-border rounded hover:bg-muted"
>
</button>

View File

@@ -265,13 +265,13 @@ export function DashboardTopMenu({
};
return (
<div className="flex h-16 items-center justify-between border-b bg-white px-6 shadow-sm">
<div className="flex h-16 items-center justify-between border-b bg-background px-6 shadow-sm">
{/* 좌측: 대시보드 제목 */}
<div className="flex items-center gap-4">
{dashboardTitle && (
<div className="flex items-center gap-2">
<span className="text-lg font-semibold text-gray-900">{dashboardTitle}</span>
<span className="rounded bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700"> </span>
<span className="text-lg font-semibold text-foreground">{dashboardTitle}</span>
<span className="rounded bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary"> </span>
</div>
)}
</div>
@@ -287,7 +287,7 @@ export function DashboardTopMenu({
/>
)}
<div className="h-6 w-px bg-gray-300" />
<div className="h-6 w-px bg-border" />
{/* 배경색 선택 */}
{onBackgroundColorChange && (
@@ -295,7 +295,7 @@ export function DashboardTopMenu({
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="gap-2">
<Palette className="h-4 w-4" />
<div className="h-4 w-4 rounded border border-gray-300" style={{ backgroundColor }} />
<div className="h-4 w-4 rounded border border-border" style={{ backgroundColor }} />
</Button>
</PopoverTrigger>
<PopoverContent className="z-[99999] w-64">
@@ -349,7 +349,7 @@ export function DashboardTopMenu({
</Popover>
)}
<div className="h-6 w-px bg-gray-300" />
<div className="h-6 w-px bg-border" />
{/* 차트 선택 */}
<Select value={chartValue} onValueChange={handleChartSelect}>
@@ -417,7 +417,7 @@ export function DashboardTopMenu({
{/* 우측: 액션 버튼 */}
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={onClearCanvas} className="gap-2 text-red-600 hover:text-red-700">
<Button variant="outline" size="sm" onClick={onClearCanvas} className="gap-2 text-destructive hover:text-destructive">
<Trash2 className="h-4 w-4" />
</Button>

View File

@@ -49,9 +49,9 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
<Card className="p-4">
<div className="flex cursor-pointer items-center justify-between" onClick={() => setIsExpanded(!isExpanded)}>
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-blue-600" />
<Label className="cursor-pointer text-sm font-medium text-gray-700"> ()</Label>
{dateFilter.enabled && <span className="rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-700"></span>}
<Calendar className="h-4 w-4 text-primary" />
<Label className="cursor-pointer text-sm font-medium text-foreground"> ()</Label>
{dateFilter.enabled && <span className="rounded bg-primary/10 px-2 py-0.5 text-xs text-primary"></span>}
</div>
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</div>
@@ -81,7 +81,7 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
<>
{/* 날짜 컬럼 선택 */}
<div>
<Label className="mb-2 text-sm font-medium text-gray-700"> </Label>
<Label className="mb-2 text-sm font-medium text-foreground"> </Label>
<Select
value={dateFilter.dateColumn || ""}
onValueChange={(value) =>
@@ -104,12 +104,12 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
))}
</SelectContent>
</Select>
<p className="mt-1 text-xs text-gray-500"> : {dateColumns.join(", ")}</p>
<p className="mt-1 text-xs text-muted-foreground"> : {dateColumns.join(", ")}</p>
</div>
{/* 빠른 선택 */}
<div>
<Label className="mb-2 text-sm font-medium text-gray-700"> </Label>
<Label className="mb-2 text-sm font-medium text-foreground"> </Label>
<div className="flex flex-wrap gap-2">
<Button
type="button"
@@ -149,7 +149,7 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
{/* 직접 입력 */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="mb-2 text-sm font-medium text-gray-700"></Label>
<Label className="mb-2 text-sm font-medium text-foreground"></Label>
<Input
type="date"
value={dateFilter.startDate || ""}
@@ -165,7 +165,7 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
/>
</div>
<div>
<Label className="mb-2 text-sm font-medium text-gray-700"></Label>
<Label className="mb-2 text-sm font-medium text-foreground"></Label>
<Input
type="date"
value={dateFilter.endDate || ""}
@@ -184,7 +184,7 @@ export function DateFilterPanel({ config, dateColumns, onChange }: DateFilterPan
{/* 필터 정보 */}
{dateFilter.startDate && dateFilter.endDate && (
<div className="rounded-md bg-blue-50 p-3 text-sm text-blue-800">
<div className="rounded-md bg-primary/10 p-3 text-sm text-primary">
<strong> :</strong> {dateFilter.dateColumn} {dateFilter.startDate}{" "}
{dateFilter.endDate} .
</div>

View File

@@ -247,7 +247,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div
className={`flex flex-col rounded-xl border bg-white shadow-2xl ${
className={`flex flex-col rounded-xl border bg-background shadow-2xl ${
currentStep === 1 && !isSimpleWidget ? "h-auto max-h-[70vh] w-full max-w-3xl" : "h-[85vh] w-full max-w-5xl"
}`}
>
@@ -255,7 +255,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
<div className="border-b p-6">
<div className="flex items-center justify-between">
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-900">{element.title} </h2>
<h2 className="text-xl font-semibold text-foreground">{element.title} </h2>
</div>
<Button variant="ghost" size="icon" onClick={onClose} className="h-8 w-8">
<X className="h-5 w-5" />
@@ -264,7 +264,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
{/* 커스텀 제목 입력 */}
<div className="mt-4">
<label className="mb-1 block text-sm font-medium text-gray-700"> ()</label>
<label className="mb-1 block text-sm font-medium text-foreground"> ()</label>
<input
type="text"
value={customTitle}
@@ -274,9 +274,9 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
e.stopPropagation();
}}
placeholder="예: 정비 일정 목록, 창고 위치 현황 등 (비워두면 자동 생성)"
className="focus:border-primary focus:ring-primary w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-1 focus:outline-none"
className="focus:border-primary focus:ring-primary w-full rounded-md border border-border px-3 py-2 text-sm focus:ring-1 focus:outline-none"
/>
<p className="mt-1 text-xs text-gray-500">
<p className="mt-1 text-xs text-muted-foreground">
(: "maintenance_schedules 목록")
</p>
</div>
@@ -288,9 +288,9 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
id="showHeader"
checked={showHeader}
onChange={(e) => setShowHeader(e.target.checked)}
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300"
className="text-primary focus:ring-primary h-4 w-4 rounded border-border"
/>
<label htmlFor="showHeader" className="text-sm font-medium text-gray-700">
<label htmlFor="showHeader" className="text-sm font-medium text-foreground">
( + )
</label>
</div>
@@ -298,9 +298,9 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
{/* 진행 상황 표시 - 간단한 위젯과 헤더 전용 위젯은 표시 안 함 */}
{!isSimpleWidget && !isHeaderOnlyWidget && (
<div className="border-b bg-gray-50 px-6 py-4">
<div className="border-b bg-muted px-6 py-4">
<div className="flex items-center justify-between">
<div className="text-sm font-medium text-gray-700">
<div className="text-sm font-medium text-foreground">
{currentStep} / 2: {currentStep === 1 ? "데이터 소스 선택" : "데이터 설정 및 차트 설정"}
</div>
</div>
@@ -356,9 +356,9 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
onConfigChange={handleChartConfigChange}
/>
) : (
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted p-8 text-center">
<div>
<div className="mt-1 text-xs text-gray-500"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
)
@@ -373,9 +373,9 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
query={dataSource.query}
/>
) : (
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted p-8 text-center">
<div>
<div className="mt-1 text-xs text-gray-500"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
)}
@@ -387,7 +387,7 @@ export function ElementConfigModal({ element, isOpen, onClose, onSave, onPreview
)}
{/* 모달 푸터 */}
<div className="flex items-center justify-between border-t bg-gray-50 p-6">
<div className="flex items-center justify-between border-t bg-muted p-6">
<div>{queryResult && <Badge variant="default">{queryResult.rows.length} </Badge>}</div>
<div className="flex gap-3">

View File

@@ -291,31 +291,31 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
return (
<div
className={cn(
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-72 flex-col bg-gray-50 transition-transform duration-300 ease-in-out",
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-72 flex-col bg-muted transition-transform duration-300 ease-in-out",
isOpen ? "translate-x-0" : "translate-x-[-100%]",
)}
>
{/* 헤더 */}
<div className="flex items-center justify-between bg-white px-3 py-2 shadow-sm">
<div className="flex items-center justify-between bg-background px-3 py-2 shadow-sm">
<div className="flex items-center gap-2">
<div className="bg-primary/10 flex h-6 w-6 items-center justify-center rounded">
<span className="text-primary text-xs font-bold"></span>
</div>
<span className="text-xs font-semibold text-gray-900">{element.title}</span>
<span className="text-xs font-semibold text-foreground">{element.title}</span>
</div>
<button
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-gray-100"
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-muted"
>
<X className="h-3.5 w-3.5 text-gray-500" />
<X className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
{/* 본문: 스크롤 가능 영역 */}
<div className="flex-1 overflow-y-auto p-3">
{/* 기본 설정 카드 */}
<div className="mb-3 rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="mb-3 rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="space-y-2">
{/* 커스텀 제목 입력 */}
<div>
@@ -325,20 +325,20 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
onChange={(e) => setCustomTitle(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
placeholder="위젯 제목"
className="focus:border-primary focus:ring-primary/20 h-8 w-full rounded border border-gray-200 bg-gray-50 px-2 text-xs placeholder:text-gray-400 focus:bg-white focus:ring-1 focus:outline-none"
className="focus:border-primary focus:ring-primary/20 h-8 w-full rounded border border-border bg-muted px-2 text-xs placeholder:text-muted-foreground focus:bg-background focus:ring-1 focus:outline-none"
/>
</div>
{/* 헤더 표시 옵션 */}
<label className="flex cursor-pointer items-center gap-2 rounded border border-gray-200 bg-gray-50 px-2 py-1.5 transition-colors hover:border-gray-300">
<label className="flex cursor-pointer items-center gap-2 rounded border border-border bg-muted px-2 py-1.5 transition-colors hover:border-border">
<input
type="checkbox"
id="showHeader"
checked={showHeader}
onChange={(e) => setShowHeader(e.target.checked)}
className="text-primary focus:ring-primary h-3 w-3 rounded border-gray-300"
className="text-primary focus:ring-primary h-3 w-3 rounded border-border"
/>
<span className="text-xs text-gray-700"> </span>
<span className="text-xs text-foreground"> </span>
</label>
</div>
</div>
@@ -346,7 +346,7 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
{/* 다중 데이터 소스 위젯 */}
{isMultiDataSourceWidget && (
<>
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="rounded-lg bg-background p-3 shadow-sm">
<MultiDataSourceConfig
dataSources={dataSources}
onChange={setDataSources}
@@ -372,11 +372,11 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
{/* 지도 위젯: 타일맵 URL 설정 */}
{element.subtype === "map-summary-v2" && (
<div className="rounded-lg bg-white shadow-sm">
<div className="rounded-lg bg-background shadow-sm">
<details className="group">
<summary className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50">
<summary className="flex cursor-pointer items-center justify-between p-3 hover:bg-muted">
<div>
<div className="text-xs font-semibold tracking-wide text-gray-500 uppercase">
<div className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">
()
</div>
<div className="text-muted-foreground mt-0.5 text-[10px]"> VWorld </div>
@@ -403,11 +403,11 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
{/* 차트 위젯: 차트 설정 */}
{element.subtype === "chart" && (
<div className="rounded-lg bg-white shadow-sm">
<div className="rounded-lg bg-background shadow-sm">
<details className="group" open>
<summary className="flex cursor-pointer items-center justify-between p-3 hover:bg-gray-50">
<summary className="flex cursor-pointer items-center justify-between p-3 hover:bg-muted">
<div>
<div className="text-xs font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="text-xs font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="text-muted-foreground mt-0.5 text-[10px]">
{testResults.size > 0
? `${testResults.size}개 데이터 소스 • X축, Y축, 차트 타입 설정`
@@ -439,24 +439,24 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
{/* 헤더 전용 위젯이 아닐 때만 데이터 소스 표시 */}
{!isHeaderOnlyWidget && !isMultiDataSourceWidget && (
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<Tabs
defaultValue={dataSource.type}
onValueChange={(value) => handleDataSourceTypeChange(value as "database" | "api")}
className="w-full"
>
<TabsList className="grid h-7 w-full grid-cols-2 bg-gray-100 p-0.5">
<TabsList className="grid h-7 w-full grid-cols-2 bg-muted p-0.5">
<TabsTrigger
value="database"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
className="h-6 rounded text-[11px] data-[state=active]:bg-background data-[state=active]:shadow-sm"
>
</TabsTrigger>
<TabsTrigger
value="api"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
className="h-6 rounded text-[11px] data-[state=active]:bg-background data-[state=active]:shadow-sm"
>
REST API
</TabsTrigger>
@@ -552,9 +552,9 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
{/* 데이터 로드 상태 */}
{queryResult && (
<div className="mt-2 flex items-center gap-1.5 rounded bg-green-50 px-2 py-1">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span className="text-[10px] font-medium text-green-700">
<div className="mt-2 flex items-center gap-1.5 rounded bg-success/10 px-2 py-1">
<div className="h-1.5 w-1.5 rounded-full bg-success" />
<span className="text-[10px] font-medium text-success">
{queryResult.rows.length}
</span>
</div>
@@ -564,10 +564,10 @@ export function ElementConfigSidebar({ element, isOpen, onClose, onApply }: Elem
</div>
{/* 푸터: 적용 버튼 */}
<div className="flex gap-2 bg-white p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<div className="flex gap-2 bg-background p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<button
onClick={onClose}
className="flex-1 rounded bg-gray-100 py-2 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200"
className="flex-1 rounded bg-muted py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted"
>
</button>

View File

@@ -123,9 +123,9 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
<div className="space-y-3">
{/* 타일맵 URL 설정 (외부 커넥션 또는 직접 입력) */}
<div className="space-y-2">
<Label className="text-xs font-medium text-gray-700">
<Label className="text-xs font-medium text-foreground">
( )
<span className="text-red-500 ml-1">*</span>
<span className="text-destructive ml-1">*</span>
</Label>
{/* 외부 커넥션 선택 */}
@@ -140,7 +140,7 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
}
}
}}
className="w-full px-2 py-1.5 border border-gray-300 rounded-md text-xs h-8 bg-white"
className="w-full px-2 py-1.5 border border-border rounded-md text-xs h-8 bg-background"
>
<option value=""> </option>
{connections.map((conn) => (
@@ -167,9 +167,9 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 타일맵 소스 목록 */}
{/* <div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
타일맵 소스 (REST API)
<span className="text-red-500 ml-1">*</span>
<span className="text-destructive ml-1">*</span>
</label>
<Button
type="button"
@@ -184,14 +184,14 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
</div>
{tileMapSources.map((source, index) => (
<div key={source.id} className="space-y-2 rounded-lg border border-gray-200 bg-gray-50 p-3">
<div key={source.id} className="space-y-2 rounded-lg border border-border bg-muted p-3">
<div className="space-y-1">
<label className="block text-xs font-medium text-gray-600">
<label className="block text-xs font-medium text-foreground">
외부 커넥션 선택 (선택사항)
</label>
<select
onChange={(e) => loadFromConnection(source.id, e.target.value)}
className="w-full px-2 py-1.5 border border-gray-300 rounded-md text-xs h-8 bg-white"
className="w-full px-2 py-1.5 border border-border rounded-md text-xs h-8 bg-background"
>
<option value="">직접 입력 또는 커넥션 선택</option>
{connections.map((conn) => (
@@ -217,7 +217,7 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
variant="ghost"
size="icon"
onClick={() => removeTileMapSource(source.id)}
className="h-8 w-8 text-gray-500 hover:text-red-600"
className="h-8 w-8 text-muted-foreground hover:text-destructive"
>
<X className="h-4 w-4" />
</Button>
@@ -233,7 +233,7 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 지도 제목 */}
{/* <div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">지도 제목</label>
<label className="block text-xs font-medium text-foreground">지도 제목</label>
<Input
type="text"
value={currentConfig.title || ''}
@@ -245,7 +245,7 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 구분선 */}
{/* <div className="border-t pt-3">
<h5 className="text-xs font-semibold text-gray-700 mb-2">📍 마커 데이터 설정 (선택사항)</h5>
<h5 className="text-xs font-semibold text-foreground mb-2">📍 마커 데이터 설정 (선택사항)</h5>
<p className="text-xs text-muted-foreground mb-3">
데이터 소스 탭에서 API 또는 데이터베이스를 연결하면 마커를 표시할 수 있습니다.
</p>
@@ -253,8 +253,8 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 쿼리 결과가 없을 때 */}
{/* {!queryResult && (
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-lg">
<div className="text-yellow-800 text-xs">
<div className="p-3 bg-warning/10 border border-warning rounded-lg">
<div className="text-warning text-xs">
💡 데이터 소스를 연결하고 쿼리를 실행하면 마커 설정이 가능합니다.
</div>
</div>
@@ -265,13 +265,13 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
<>
{/* 위도 컬럼 설정 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
(Latitude)
</label>
<select
value={currentConfig.latitudeColumn || ''}
onChange={(e) => updateConfig({ latitudeColumn: e.target.value })}
className="w-full px-2 py-1.5 border border-gray-300 rounded-lg text-xs"
className="w-full px-2 py-1.5 border border-border rounded-lg text-xs"
>
<option value=""></option>
{availableColumns.map((col) => (
@@ -284,13 +284,13 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 경도 컬럼 설정 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
(Longitude)
</label>
<select
value={currentConfig.longitudeColumn || ''}
onChange={(e) => updateConfig({ longitudeColumn: e.target.value })}
className="w-full px-2 py-1.5 border border-gray-300 rounded-lg text-xs"
className="w-full px-2 py-1.5 border border-border rounded-lg text-xs"
>
<option value=""></option>
{availableColumns.map((col) => (
@@ -303,13 +303,13 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 라벨 컬럼 (선택사항) */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
( )
</label>
<select
value={currentConfig.labelColumn || ''}
onChange={(e) => updateConfig({ labelColumn: e.target.value })}
className="w-full px-2 py-1.5 border border-gray-300 rounded-lg text-xs"
className="w-full px-2 py-1.5 border border-border rounded-lg text-xs"
>
<option value=""> ()</option>
{availableColumns.map((col) => (
@@ -322,13 +322,13 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 상태 컬럼 (선택사항) */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
( )
</label>
<select
value={currentConfig.statusColumn || ''}
onChange={(e) => updateConfig({ statusColumn: e.target.value })}
className="w-full px-2 py-1.5 border border-gray-300 rounded-lg text-xs"
className="w-full px-2 py-1.5 border border-border rounded-lg text-xs"
>
<option value=""> ()</option>
{availableColumns.map((col) => (
@@ -343,8 +343,8 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 기상특보 데이터 안내 */}
{queryResult && isWeatherAlertData && (
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
<div className="text-blue-800 text-xs">
<div className="p-3 bg-primary/10 border border-primary rounded-lg">
<div className="text-primary text-xs">
🚨 . (reg_ko) .
</div>
</div>
@@ -355,38 +355,38 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 날씨 정보 표시 옵션 */}
<div className="space-y-1.5">
<label className="flex items-center gap-2 text-xs font-medium text-gray-700 cursor-pointer">
<label className="flex items-center gap-2 text-xs font-medium text-foreground cursor-pointer">
<input
type="checkbox"
checked={currentConfig.showWeather || false}
onChange={(e) => updateConfig({ showWeather: e.target.checked })}
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-primary"
className="h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
/>
<span> </span>
</label>
<p className="text-xs text-gray-500 ml-6">
<p className="text-xs text-muted-foreground ml-6">
</p>
</div>
<div className="space-y-1.5">
<label className="flex items-center gap-2 text-xs font-medium text-gray-700 cursor-pointer">
<label className="flex items-center gap-2 text-xs font-medium text-foreground cursor-pointer">
<input
type="checkbox"
checked={currentConfig.showWeatherAlerts || false}
onChange={(e) => updateConfig({ showWeatherAlerts: e.target.checked })}
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-primary"
className="h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary"
/>
<span> </span>
</label>
<p className="text-xs text-gray-500 ml-6">
<p className="text-xs text-muted-foreground ml-6">
(/)
</p>
</div>
{/* 설정 미리보기 */}
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-xs font-medium text-gray-700 mb-2">📋 </div>
<div className="p-3 bg-muted rounded-lg">
<div className="text-xs font-medium text-foreground mb-2">📋 </div>
<div className="text-xs text-muted-foreground space-y-1">
<div><strong>:</strong> {currentConfig.tileMapUrl ? '✅ 설정됨' : '❌ 미설정'}</div>
<div><strong>:</strong> {currentConfig.latitudeColumn || '미설정'}</div>
@@ -403,8 +403,8 @@ export function MapTestConfigPanel({ config, queryResult, onConfigChange }: MapT
{/* 필수 필드 확인 */}
{/* {!currentConfig.tileMapUrl && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
<div className="text-red-800 text-xs">
<div className="p-3 bg-destructive/10 border border-destructive rounded-lg">
<div className="text-destructive text-xs">
⚠️ 타일맵 URL을 입력해야 지도가 표시됩니다.
</div>
</div>

View File

@@ -177,7 +177,7 @@ export const MenuAssignmentModal: React.FC<MenuAssignmentModalProps> = ({
<Label> </Label>
{loading ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : (
<Select value={selectedMenuId} onValueChange={setSelectedMenuId}>

View File

@@ -302,8 +302,8 @@ export function MultiChartConfigPanel({
{/* 안내 메시지 */}
{dataSourceConfigs.length > 0 && (
<div className="rounded-lg bg-blue-50 p-3">
<p className="text-xs text-blue-900">
<div className="rounded-lg bg-primary/10 p-3">
<p className="text-xs text-primary">
{mergeMode ? (
<>
🔗 {dataSourceConfigs.length} / .

View File

@@ -168,8 +168,8 @@ ORDER BY 하위부서수 DESC`,
{/* 쿼리 에디터 헤더 */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<Database className="h-3.5 w-3.5 text-blue-600" />
<h4 className="text-xs font-semibold text-gray-800">SQL </h4>
<Database className="h-3.5 w-3.5 text-primary" />
<h4 className="text-xs font-semibold text-foreground">SQL </h4>
</div>
<Button onClick={executeQuery} disabled={isExecuting || !query.trim()} size="sm" className="h-7 text-xs">
{isExecuting ? (
@@ -188,7 +188,7 @@ ORDER BY 하위부서수 DESC`,
{/* 샘플 쿼리 아코디언 */}
<Collapsible open={sampleQueryOpen} onOpenChange={setSampleQueryOpen}>
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded border border-gray-200 bg-gray-50 px-2 py-1.5 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-100">
<CollapsibleTrigger className="flex w-full items-center gap-1.5 rounded border border-border bg-muted px-2 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-muted">
{sampleQueryOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
</CollapsibleTrigger>
@@ -196,33 +196,33 @@ ORDER BY 하위부서수 DESC`,
<div className="flex flex-wrap gap-1.5">
<button
onClick={() => insertSampleQuery("users")}
className="flex items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
className="flex items-center gap-1 rounded border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted"
>
<Code className="h-3 w-3" />
</button>
<button
onClick={() => insertSampleQuery("dept")}
className="flex items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
className="flex items-center gap-1 rounded border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted"
>
<Code className="h-3 w-3" />
</button>
<button
onClick={() => insertSampleQuery("usersByDate")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
className="rounded border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted"
>
</button>
<button
onClick={() => insertSampleQuery("usersByPosition")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
className="rounded border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted"
>
</button>
<button
onClick={() => insertSampleQuery("deptHierarchy")}
className="rounded border border-gray-200 bg-white px-2 py-1 text-[11px] transition-colors hover:bg-gray-50"
className="rounded border border-border bg-background px-2 py-1 text-[11px] transition-colors hover:bg-muted"
>
</button>
@@ -300,15 +300,15 @@ ORDER BY 하위부서수 DESC`,
{/* 쿼리 결과 미리보기 */}
{queryResult && (
<Card>
<div className="border-b border-gray-200 bg-gray-50 px-2 py-1.5">
<div className="border-b border-border bg-muted px-2 py-1.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium text-gray-700"> </span>
<span className="text-xs font-medium text-foreground"> </span>
<Badge variant="secondary" className="h-4 text-[10px]">
{queryResult.rows.length}
</Badge>
</div>
<span className="text-[10px] text-gray-500"> : {queryResult.executionTime}ms</span>
<span className="text-[10px] text-muted-foreground"> : {queryResult.executionTime}ms</span>
</div>
</div>
@@ -339,13 +339,13 @@ ORDER BY 하위부서수 DESC`,
</Table>
{queryResult.rows.length > 10 && (
<div className="mt-2 text-center text-[10px] text-gray-500">
<div className="mt-2 text-center text-[10px] text-muted-foreground">
... {queryResult.rows.length - 10} ( 10 )
</div>
)}
</div>
) : (
<div className="py-6 text-center text-xs text-gray-500"> .</div>
<div className="py-6 text-center text-xs text-muted-foreground"> .</div>
)}
</div>
</Card>

View File

@@ -122,9 +122,9 @@ export function ResolutionSelector({ value, onChange, currentScreenResolution }:
return (
<div className="flex items-center gap-2">
<Monitor className="h-4 w-4 text-gray-500" />
<Monitor className="h-4 w-4 text-muted-foreground" />
<Select value={value} onValueChange={(v) => onChange(v as Resolution)}>
<SelectTrigger className={`w-[180px] ${isTooLarge ? "border-orange-500" : ""}`}>
<SelectTrigger className={`w-[180px] ${isTooLarge ? "border-warning" : ""}`}>
<SelectValue />
</SelectTrigger>
<SelectContent className="z-[99999]">
@@ -133,31 +133,31 @@ export function ResolutionSelector({ value, onChange, currentScreenResolution }:
<SelectItem value="hd">
<div className="flex items-center gap-2">
<span>HD</span>
<span className="text-xs text-gray-500">1280x720</span>
<span className="text-xs text-muted-foreground">1280x720</span>
</div>
</SelectItem>
<SelectItem value="fhd">
<div className="flex items-center gap-2">
<span>Full HD</span>
<span className="text-xs text-gray-500">1920x1080</span>
<span className="text-xs text-muted-foreground">1920x1080</span>
</div>
</SelectItem>
<SelectItem value="qhd">
<div className="flex items-center gap-2">
<span>QHD</span>
<span className="text-xs text-gray-500">2560x1440</span>
<span className="text-xs text-muted-foreground">2560x1440</span>
</div>
</SelectItem>
<SelectItem value="uhd">
<div className="flex items-center gap-2">
<span>4K UHD</span>
<span className="text-xs text-gray-500">3840x2160</span>
<span className="text-xs text-muted-foreground">3840x2160</span>
</div>
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
{isTooLarge && <span className="text-xs text-orange-600"> </span>}
{isTooLarge && <span className="text-xs text-warning"> </span>}
</div>
);
}

View File

@@ -88,12 +88,12 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
return (
<div className="space-y-3">
<h4 className="text-xs font-semibold text-gray-800">🗺 </h4>
<h4 className="text-xs font-semibold text-foreground">🗺 </h4>
{/* 쿼리 결과가 없을 때 */}
{!queryResult && (
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-3">
<div className="text-xs text-yellow-800">
<div className="rounded-lg border border-warning bg-warning/10 p-3">
<div className="text-xs text-warning">
💡 SQL .
</div>
</div>
@@ -104,26 +104,26 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
<>
{/* 지도 제목 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700"> </label>
<label className="block text-xs font-medium text-foreground"> </label>
<input
type="text"
value={currentConfig.title || ""}
onChange={(e) => updateConfig({ title: e.target.value })}
placeholder="차량 위치 지도"
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="w-full rounded-lg border border-border px-2 py-1.5 text-xs"
/>
</div>
{/* 위도 컬럼 설정 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
(Latitude)
<span className="ml-1 text-red-500">*</span>
<span className="ml-1 text-destructive">*</span>
</label>
<select
value={currentConfig.latitudeColumn || ""}
onChange={(e) => updateConfig({ latitudeColumn: e.target.value })}
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="w-full rounded-lg border border-border px-2 py-1.5 text-xs"
>
<option value=""></option>
{availableColumns.map((col) => (
@@ -136,14 +136,14 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 경도 컬럼 설정 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
(Longitude)
<span className="ml-1 text-red-500">*</span>
<span className="ml-1 text-destructive">*</span>
</label>
<select
value={currentConfig.longitudeColumn || ""}
onChange={(e) => updateConfig({ longitudeColumn: e.target.value })}
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="w-full rounded-lg border border-border px-2 py-1.5 text-xs"
>
<option value=""></option>
{availableColumns.map((col) => (
@@ -156,11 +156,11 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 라벨 컬럼 (선택사항) */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700"> ( )</label>
<label className="block text-xs font-medium text-foreground"> ( )</label>
<select
value={currentConfig.labelColumn || ""}
onChange={(e) => updateConfig({ labelColumn: e.target.value })}
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="w-full rounded-lg border border-border px-2 py-1.5 text-xs"
>
<option value=""> ()</option>
{availableColumns.map((col) => (
@@ -173,19 +173,19 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 마커 색상 설정 */}
<div className="space-y-2 border-t pt-3">
<h5 className="text-xs font-semibold text-gray-800">🎨 </h5>
<h5 className="text-xs font-semibold text-foreground">🎨 </h5>
{/* 색상 모드 선택 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700"> </label>
<label className="block text-xs font-medium text-foreground"> </label>
<div className="flex gap-2">
<button
type="button"
onClick={() => handleMarkerColorModeChange("single")}
className={`flex-1 rounded-lg border px-3 py-2 text-xs transition-colors ${
(currentConfig.markerColorMode || "single") === "single"
? "border-blue-300 bg-blue-50 font-medium text-blue-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
? "border-primary bg-primary/10 font-medium text-primary"
: "border-border bg-background text-foreground hover:bg-muted"
}`}
>
@@ -195,8 +195,8 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
onClick={() => handleMarkerColorModeChange("conditional")}
className={`flex-1 rounded-lg border px-3 py-2 text-xs transition-colors ${
currentConfig.markerColorMode === "conditional"
? "border-blue-300 bg-blue-50 font-medium text-blue-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50"
? "border-primary bg-primary/10 font-medium text-primary"
: "border-border bg-background text-foreground hover:bg-muted"
}`}
>
@@ -206,40 +206,40 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 단일 색상 모드 */}
{(currentConfig.markerColorMode || "single") === "single" && (
<div className="space-y-1.5 rounded-lg bg-gray-50 p-3">
<label className="block text-xs font-medium text-gray-700"> </label>
<div className="space-y-1.5 rounded-lg bg-muted p-3">
<label className="block text-xs font-medium text-foreground"> </label>
<div className="flex items-center gap-2">
<input
type="color"
value={currentConfig.markerDefaultColor || "#3b82f6"}
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
className="h-8 w-12 cursor-pointer rounded border border-border"
/>
<input
type="text"
value={currentConfig.markerDefaultColor || "#3b82f6"}
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
placeholder="#3b82f6"
className="flex-1 rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="flex-1 rounded-lg border border-border px-2 py-1.5 text-xs"
/>
</div>
<p className="text-xs text-gray-500"> </p>
<p className="text-xs text-muted-foreground"> </p>
</div>
)}
{/* 조건부 색상 모드 */}
{currentConfig.markerColorMode === "conditional" && (
<div className="space-y-2 rounded-lg bg-gray-50 p-3">
<div className="space-y-2 rounded-lg bg-muted p-3">
{/* 색상 조건 컬럼 선택 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700">
<label className="block text-xs font-medium text-foreground">
<span className="ml-1 text-red-500">*</span>
<span className="ml-1 text-destructive">*</span>
</label>
<select
value={currentConfig.markerColorColumn || ""}
onChange={(e) => updateConfig({ markerColorColumn: e.target.value })}
className="w-full rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="w-full rounded-lg border border-border px-2 py-1.5 text-xs"
>
<option value=""></option>
{availableColumns.map((col) => (
@@ -248,38 +248,38 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
</option>
))}
</select>
<p className="text-xs text-gray-500"> </p>
<p className="text-xs text-muted-foreground"> </p>
</div>
{/* 기본 색상 */}
<div className="space-y-1.5">
<label className="block text-xs font-medium text-gray-700"> </label>
<label className="block text-xs font-medium text-foreground"> </label>
<div className="flex items-center gap-2">
<input
type="color"
value={currentConfig.markerDefaultColor || "#6b7280"}
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
className="h-8 w-12 cursor-pointer rounded border border-border"
/>
<input
type="text"
value={currentConfig.markerDefaultColor || "#6b7280"}
onChange={(e) => updateConfig({ markerDefaultColor: e.target.value })}
placeholder="#6b7280"
className="flex-1 rounded-lg border border-gray-300 px-2 py-1.5 text-xs"
className="flex-1 rounded-lg border border-border px-2 py-1.5 text-xs"
/>
</div>
<p className="text-xs text-gray-500"> </p>
<p className="text-xs text-muted-foreground"> </p>
</div>
{/* 색상 규칙 목록 */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-xs font-medium text-gray-700"> </label>
<label className="block text-xs font-medium text-foreground"> </label>
<button
type="button"
onClick={addColorRule}
className="flex items-center gap-1 rounded-lg bg-blue-500 px-2 py-1 text-xs text-white transition-colors hover:bg-blue-600"
className="flex items-center gap-1 rounded-lg bg-primary px-2 py-1 text-xs text-white transition-colors hover:bg-primary/90"
>
<Plus className="h-3 w-3" />
@@ -288,20 +288,20 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 규칙 리스트 */}
{(currentConfig.markerColorRules || []).length === 0 ? (
<div className="rounded-lg border border-gray-200 bg-white p-3 text-center">
<p className="text-xs text-gray-500"> </p>
<div className="rounded-lg border border-border bg-background p-3 text-center">
<p className="text-xs text-muted-foreground"> </p>
</div>
) : (
<div className="space-y-2">
{(currentConfig.markerColorRules || []).map((rule) => (
<div key={rule.id} className="space-y-2 rounded-lg border border-gray-200 bg-white p-2">
<div key={rule.id} className="space-y-2 rounded-lg border border-border bg-background p-2">
{/* 규칙 헤더 */}
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-gray-700"></span>
<span className="text-xs font-medium text-foreground"></span>
<button
type="button"
onClick={() => deleteColorRule(rule.id)}
className="text-red-500 transition-colors hover:text-red-700"
className="text-destructive transition-colors hover:text-destructive"
>
<Trash2 className="h-3 w-3" />
</button>
@@ -309,45 +309,45 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 조건 값 */}
<div className="space-y-1">
<label className="block text-xs font-medium text-gray-600"> ()</label>
<label className="block text-xs font-medium text-foreground"> ()</label>
<input
type="text"
value={rule.value}
onChange={(e) => updateColorRule(rule.id, { value: e.target.value })}
placeholder="예: active, inactive"
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
className="w-full rounded border border-border px-2 py-1 text-xs"
/>
</div>
{/* 색상 */}
<div className="space-y-1">
<label className="block text-xs font-medium text-gray-600"></label>
<label className="block text-xs font-medium text-foreground"></label>
<div className="flex items-center gap-2">
<input
type="color"
value={rule.color}
onChange={(e) => updateColorRule(rule.id, { color: e.target.value })}
className="h-8 w-12 cursor-pointer rounded border border-gray-300"
className="h-8 w-12 cursor-pointer rounded border border-border"
/>
<input
type="text"
value={rule.color}
onChange={(e) => updateColorRule(rule.id, { color: e.target.value })}
placeholder="#3b82f6"
className="flex-1 rounded border border-gray-300 px-2 py-1 text-xs"
className="flex-1 rounded border border-border px-2 py-1 text-xs"
/>
</div>
</div>
{/* 라벨 (선택사항) */}
<div className="space-y-1">
<label className="block text-xs font-medium text-gray-600"> ()</label>
<label className="block text-xs font-medium text-foreground"> ()</label>
<input
type="text"
value={rule.label || ""}
onChange={(e) => updateColorRule(rule.id, { label: e.target.value })}
placeholder="예: 활성, 비활성"
className="w-full rounded border border-gray-300 px-2 py-1 text-xs"
className="w-full rounded border border-border px-2 py-1 text-xs"
/>
</div>
</div>
@@ -361,36 +361,36 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 날씨 정보 표시 옵션 */}
<div className="space-y-1.5">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-700">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-foreground">
<input
type="checkbox"
checked={currentConfig.showWeather || false}
onChange={(e) => updateConfig({ showWeather: e.target.checked })}
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300 focus:ring-2"
className="text-primary focus:ring-primary h-4 w-4 rounded border-border focus:ring-2"
/>
<span> </span>
</label>
<p className="ml-6 text-xs text-gray-500"> </p>
<p className="ml-6 text-xs text-muted-foreground"> </p>
</div>
<div className="space-y-1.5">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-gray-700">
<label className="flex cursor-pointer items-center gap-2 text-xs font-medium text-foreground">
<input
type="checkbox"
checked={currentConfig.showWeatherAlerts || false}
onChange={(e) => updateConfig({ showWeatherAlerts: e.target.checked })}
className="text-primary focus:ring-primary h-4 w-4 rounded border-gray-300 focus:ring-2"
className="text-primary focus:ring-primary h-4 w-4 rounded border-border focus:ring-2"
/>
<span> </span>
</label>
<p className="ml-6 text-xs text-gray-500">
<p className="ml-6 text-xs text-muted-foreground">
(/)
</p>
</div>
{/* 설정 미리보기 */}
<div className="rounded-lg bg-gray-50 p-3">
<div className="mb-2 text-xs font-medium text-gray-700">📋 </div>
<div className="rounded-lg bg-muted p-3">
<div className="mb-2 text-xs font-medium text-foreground">📋 </div>
<div className="text-muted-foreground space-y-1 text-xs">
<div>
<strong>:</strong> {currentConfig.latitudeColumn || "미설정"}
@@ -428,8 +428,8 @@ export function VehicleMapConfigPanel({ config, queryResult, onConfigChange }: V
{/* 필수 필드 확인 */}
{(!currentConfig.latitudeColumn || !currentConfig.longitudeColumn) && (
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
<div className="text-xs text-red-800">
<div className="rounded-lg border border-destructive bg-destructive/10 p-3">
<div className="text-xs text-destructive">
.
</div>
</div>

View File

@@ -27,13 +27,13 @@ export function Chart({ chartType, data, config, width, height }: ChartProps) {
if (!data || !data.labels.length || !data.datasets.length) {
return (
<div
className="flex items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50"
className="flex items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted"
style={{ width, height }}
>
<div className="text-center">
<div className="mb-2 text-4xl">📊</div>
<div className="text-sm font-medium text-gray-600"> </div>
<div className="mt-1 text-xs text-gray-500"> </div>
<div className="text-sm font-medium text-foreground"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
);
@@ -68,13 +68,13 @@ export function Chart({ chartType, data, config, width, height }: ChartProps) {
default:
return (
<div
className="flex items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50"
className="flex items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted"
style={{ width, height }}
>
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600"> </div>
<div className="mt-1 text-xs text-gray-500">{chartType}</div>
<div className="text-sm font-medium text-foreground"> </div>
<div className="mt-1 text-xs text-muted-foreground">{chartType}</div>
</div>
</div>
);

View File

@@ -203,9 +203,9 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende
// 로딩 중
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center text-gray-500">
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" />
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<div className="text-sm"> ...</div>
</div>
</div>
@@ -215,7 +215,7 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende
// 에러
if (error) {
return (
<div className="flex h-full w-full items-center justify-center text-red-500">
<div className="flex h-full w-full items-center justify-center text-destructive">
<div className="text-center">
<div className="mb-2 text-2xl"></div>
<div className="text-sm font-medium"> </div>
@@ -232,7 +232,7 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende
if (!chartData || !element.chartConfig?.xAxis || (needsYAxis && !element.chartConfig?.yAxis)) {
return (
<div className="flex h-full w-full items-center justify-center text-gray-500">
<div className="flex h-full w-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="text-sm"> </div>
</div>
@@ -264,7 +264,7 @@ export function ChartRenderer({ element, data, width, height = 200 }: ChartRende
});
return (
<div ref={containerRef} className="flex h-full w-full items-center justify-center bg-white p-0.5">
<div ref={containerRef} className="flex h-full w-full items-center justify-center bg-background p-0.5">
<div className="flex items-center justify-center">
<Chart
chartType={element.subtype}

View File

@@ -47,7 +47,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }:
return (
<div className="w-full h-full p-2">
{title && (
<div className="text-center text-sm font-semibold text-gray-700 mb-2">
<div className="text-center text-sm font-semibold text-foreground mb-2">
{title}
</div>
)}

View File

@@ -42,7 +42,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2
return (
<div className="w-full h-full p-2">
{title && (
<div className="text-center text-sm font-semibold text-gray-700 mb-2">
<div className="text-center text-sm font-semibold text-foreground mb-2">
{title}
</div>
)}

View File

@@ -374,7 +374,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
<div className="space-y-4">
{/* 외부 커넥션 선택 - 항상 표시 */}
<div className="space-y-2">
<Label className="text-xs font-medium text-gray-700"> ()</Label>
<Label className="text-xs font-medium text-foreground"> ()</Label>
<Select value={selectedConnectionId} onValueChange={handleConnectionSelect}>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="저장된 커넥션 선택" />
@@ -387,22 +387,22 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
apiConnections.map((conn) => (
<SelectItem key={conn.id} value={String(conn.id)} className="text-xs">
{conn.connection_name}
{conn.description && <span className="ml-1.5 text-[10px] text-gray-500">({conn.description})</span>}
{conn.description && <span className="ml-1.5 text-[10px] text-muted-foreground">({conn.description})</span>}
</SelectItem>
))
) : (
<SelectItem value="no-connections" disabled className="text-xs text-gray-500">
<SelectItem value="no-connections" disabled className="text-xs text-muted-foreground">
</SelectItem>
)}
</SelectContent>
</Select>
<p className="text-[11px] text-gray-500"> REST API </p>
<p className="text-[11px] text-muted-foreground"> REST API </p>
</div>
{/* API URL */}
<div className="space-y-1.5">
<Label className="text-xs font-medium text-gray-700">API URL *</Label>
<Label className="text-xs font-medium text-foreground">API URL *</Label>
<Input
type="url"
placeholder="https://api.example.com/data 또는 /api/typ01/url/wrn_now_data.php"
@@ -410,7 +410,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
onChange={(e) => onChange({ endpoint: e.target.value })}
className="h-8 text-xs"
/>
<p className="text-[11px] text-gray-500">
<p className="text-[11px] text-muted-foreground">
URL base_url ( base_url )
</p>
</div>
@@ -418,7 +418,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
{/* 쿼리 파라미터 */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium text-gray-700">URL </Label>
<Label className="text-xs font-medium text-foreground">URL </Label>
<Button variant="outline" size="sm" onClick={addQueryParam} className="h-6 text-[11px]">
<Plus className="mr-1 h-3 w-3" />
@@ -445,7 +445,7 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
/>
<button
onClick={() => removeQueryParam(param.id)}
className="flex h-7 w-7 items-center justify-center rounded hover:bg-gray-100"
className="flex h-7 w-7 items-center justify-center rounded hover:bg-muted"
>
<X className="h-3 w-3" />
</button>
@@ -453,17 +453,17 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
))}
</div>
) : (
<p className="py-2 text-center text-[11px] text-gray-500"> </p>
<p className="py-2 text-center text-[11px] text-muted-foreground"> </p>
);
})()}
<p className="text-[11px] text-gray-500">: category=electronics, limit=10</p>
<p className="text-[11px] text-muted-foreground">: category=electronics, limit=10</p>
</div>
{/* 헤더 */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium text-gray-700"> </Label>
<Label className="text-xs font-medium text-foreground"> </Label>
<Button variant="outline" size="sm" onClick={addHeader}>
<Plus className="mr-1 h-3 w-3" />
@@ -524,20 +524,20 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
))}
</div>
) : (
<p className="py-2 text-center text-sm text-gray-500"> </p>
<p className="py-2 text-center text-sm text-muted-foreground"> </p>
);
})()}
</div>
{/* JSON Path */}
<div className="space-y-2">
<Label className="text-xs font-medium text-gray-700">JSON Path ()</Label>
<Label className="text-xs font-medium text-foreground">JSON Path ()</Label>
<Input
placeholder="data.results"
value={dataSource.jsonPath || ""}
onChange={(e) => onChange({ jsonPath: e.target.value })}
/>
<p className="text-[11px] text-gray-500">
<p className="text-[11px] text-muted-foreground">
JSON (: data.results, items, response.data)
<br />
@@ -563,12 +563,12 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
{/* 테스트 오류 */}
{testError && (
<div className="rounded bg-red-50 px-2 py-2">
<div className="rounded bg-destructive/10 px-2 py-2">
<div className="flex items-start gap-2">
<AlertCircle className="mt-0.5 h-5 w-5 flex-shrink-0 text-red-600" />
<AlertCircle className="mt-0.5 h-5 w-5 flex-shrink-0 text-destructive" />
<div>
<div className="text-sm font-medium text-red-800">API </div>
<div className="mt-1 text-sm text-red-700">{testError}</div>
<div className="text-sm font-medium text-destructive">API </div>
<div className="mt-1 text-sm text-destructive">{testError}</div>
</div>
</div>
</div>
@@ -576,9 +576,9 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
{/* 테스트 결과 */}
{testResult && (
<div className="rounded bg-green-50 px-2 py-2">
<div className="mb-2 text-sm font-medium text-green-800">API </div>
<div className="space-y-1 text-xs text-green-700">
<div className="rounded bg-success/10 px-2 py-2">
<div className="mb-2 text-sm font-medium text-success">API </div>
<div className="space-y-1 text-xs text-success">
<div> {testResult.rows.length} </div>
<div>: {testResult.columns.join(", ")}</div>
</div>

View File

@@ -19,8 +19,8 @@ export function DataSourceSelector({ dataSource, onTypeChange }: DataSourceSelec
return (
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold text-gray-800">1단계: 데이터 </h3>
<p className="mt-1 text-sm text-gray-600"> </p>
<h3 className="text-lg font-semibold text-foreground">1단계: 데이터 </h3>
<p className="mt-1 text-sm text-foreground"> </p>
</div>
<div className="grid grid-cols-2 gap-4">
@@ -28,20 +28,20 @@ export function DataSourceSelector({ dataSource, onTypeChange }: DataSourceSelec
<Card
className={`cursor-pointer p-6 transition-all ${
dataSource.type === "database"
? "border-2 border-blue-500 bg-blue-50"
: "border-2 border-gray-200 hover:border-gray-300"
? "border-2 border-primary bg-primary/10"
: "border-2 border-border hover:border-border"
}`}
onClick={() => onTypeChange("database")}
>
<div className="flex flex-col items-center space-y-3 text-center">
<div className={`rounded-full p-4 ${dataSource.type === "database" ? "bg-blue-100" : "bg-gray-100"}`}>
<Database className={`h-8 w-8 ${dataSource.type === "database" ? "text-blue-600" : "text-gray-600"}`} />
<div className={`rounded-full p-4 ${dataSource.type === "database" ? "bg-primary/10" : "bg-muted"}`}>
<Database className={`h-8 w-8 ${dataSource.type === "database" ? "text-primary" : "text-foreground"}`} />
</div>
<div>
<h4 className="font-semibold text-gray-900"></h4>
<p className="mt-1 text-sm text-gray-600">SQL </p>
<h4 className="font-semibold text-foreground"></h4>
<p className="mt-1 text-sm text-foreground">SQL </p>
</div>
<div className="space-y-1 text-xs text-gray-500">
<div className="space-y-1 text-xs text-muted-foreground">
<div> DB DB</div>
<div> SELECT </div>
<div> </div>
@@ -53,20 +53,20 @@ export function DataSourceSelector({ dataSource, onTypeChange }: DataSourceSelec
<Card
className={`cursor-pointer p-6 transition-all ${
dataSource.type === "api"
? "border-2 border-green-500 bg-green-50"
: "border-2 border-gray-200 hover:border-gray-300"
? "border-2 border-success bg-success/10"
: "border-2 border-border hover:border-border"
}`}
onClick={() => onTypeChange("api")}
>
<div className="flex flex-col items-center space-y-3 text-center">
<div className={`rounded-full p-4 ${dataSource.type === "api" ? "bg-green-100" : "bg-gray-100"}`}>
<Globe className={`h-8 w-8 ${dataSource.type === "api" ? "text-green-600" : "text-gray-600"}`} />
<div className={`rounded-full p-4 ${dataSource.type === "api" ? "bg-success/10" : "bg-muted"}`}>
<Globe className={`h-8 w-8 ${dataSource.type === "api" ? "text-success" : "text-foreground"}`} />
</div>
<div>
<h4 className="font-semibold text-gray-900">REST API</h4>
<p className="mt-1 text-sm text-gray-600"> API에서 </p>
<h4 className="font-semibold text-foreground">REST API</h4>
<p className="mt-1 text-sm text-foreground"> API에서 </p>
</div>
<div className="space-y-1 text-xs text-gray-500">
<div className="space-y-1 text-xs text-muted-foreground">
<div> GET </div>
<div> JSON </div>
<div> </div>
@@ -77,10 +77,10 @@ export function DataSourceSelector({ dataSource, onTypeChange }: DataSourceSelec
{/* 선택된 타입 표시 */}
{dataSource.type && (
<div className="rounded-lg border border-gray-200 bg-gray-50 p-3">
<div className="rounded-lg border border-border bg-muted p-3">
<div className="flex items-center gap-2 text-sm">
<span className="font-medium text-gray-700">:</span>
<span className="text-gray-900">{dataSource.type === "database" ? "🗄️ 데이터베이스" : "🌐 REST API"}</span>
<span className="font-medium text-foreground">:</span>
<span className="text-foreground">{dataSource.type === "database" ? "🗄️ 데이터베이스" : "🌐 REST API"}</span>
</div>
</div>
)}

View File

@@ -52,7 +52,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
<div className="space-y-3">
{/* 현재 DB vs 외부 DB 선택 */}
<div>
<Label className="mb-2 block text-xs font-medium text-gray-700"> </Label>
<Label className="mb-2 block text-xs font-medium text-foreground"> </Label>
<div className="flex gap-2">
<button
onClick={() => {
@@ -61,7 +61,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
className={`flex flex-1 items-center gap-1.5 rounded border px-2 py-1.5 text-xs transition-colors ${
dataSource.connectionType === "current"
? "bg-primary border-primary text-white"
: "border-gray-200 bg-white hover:bg-gray-50"
: "border-border bg-background hover:bg-muted"
}`}
>
<Database className="h-3 w-3" />
@@ -75,7 +75,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
className={`flex flex-1 items-center gap-1.5 rounded border px-2 py-1.5 text-xs transition-colors ${
dataSource.connectionType === "external"
? "bg-primary border-primary text-white"
: "border-gray-200 bg-white hover:bg-gray-50"
: "border-border bg-background hover:bg-muted"
}`}
>
<Server className="h-3 w-3" />
@@ -88,12 +88,12 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
{dataSource.connectionType === "external" && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs font-medium text-gray-700"> </Label>
<Label className="text-xs font-medium text-foreground"> </Label>
<button
onClick={() => {
router.push("/admin/external-connections");
}}
className="flex items-center gap-1 text-[11px] text-blue-600 transition-colors hover:text-blue-700"
className="flex items-center gap-1 text-[11px] text-primary transition-colors hover:text-primary"
>
<ExternalLink className="h-3 w-3" />
@@ -102,17 +102,17 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
{loading && (
<div className="flex items-center justify-center py-3">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-blue-600" />
<span className="ml-2 text-xs text-gray-600"> ...</span>
<div className="h-4 w-4 animate-spin rounded-full border-2 border-border border-t-blue-600" />
<span className="ml-2 text-xs text-foreground"> ...</span>
</div>
)}
{error && (
<div className="rounded bg-red-50 px-2 py-1.5">
<div className="text-xs text-red-800">{error}</div>
<div className="rounded bg-destructive/10 px-2 py-1.5">
<div className="text-xs text-destructive">{error}</div>
<button
onClick={loadExternalConnections}
className="mt-1 text-[11px] text-red-600 underline hover:no-underline"
className="mt-1 text-[11px] text-destructive underline hover:no-underline"
>
</button>
@@ -120,13 +120,13 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
)}
{!loading && !error && connections.length === 0 && (
<div className="rounded bg-yellow-50 px-2 py-2 text-center">
<div className="mb-1 text-xs text-yellow-800"> </div>
<div className="rounded bg-warning/10 px-2 py-2 text-center">
<div className="mb-1 text-xs text-warning"> </div>
<button
onClick={() => {
router.push("/admin/external-connections");
}}
className="text-[11px] text-yellow-700 underline hover:no-underline"
className="text-[11px] text-warning underline hover:no-underline"
>
</button>
@@ -149,7 +149,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
<SelectItem key={conn.id} value={String(conn.id)} className="text-xs">
<div className="flex items-center gap-1.5">
<span className="font-medium">{conn.connection_name}</span>
<span className="text-[10px] text-gray-500">({conn.db_type.toUpperCase()})</span>
<span className="text-[10px] text-muted-foreground">({conn.db_type.toUpperCase()})</span>
</div>
</SelectItem>
))}
@@ -157,7 +157,7 @@ export function DatabaseConfig({ dataSource, onChange }: DatabaseConfigProps) {
</Select>
{selectedConnection && (
<div className="space-y-0.5 rounded bg-gray-50 px-2 py-1.5 text-[11px] text-gray-600">
<div className="space-y-0.5 rounded bg-muted px-2 py-1.5 text-[11px] text-foreground">
<div>
<span className="font-medium">:</span> {selectedConnection.connection_name}
</div>

View File

@@ -630,8 +630,8 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
<div
className={`flex items-center gap-2 rounded-md p-2 text-xs ${
testResult.success
? "bg-green-50 text-green-700"
: "bg-red-50 text-red-700"
? "bg-success/10 text-success"
: "bg-destructive/10 text-destructive"
}`}
>
{testResult.success ? (
@@ -710,12 +710,12 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
}[type];
const typeColor = {
number: "text-blue-600 bg-blue-50",
string: "text-gray-600 bg-gray-50",
date: "text-purple-600 bg-purple-50",
boolean: "text-green-600 bg-green-50",
object: "text-orange-600 bg-orange-50",
unknown: "text-gray-400 bg-gray-50"
number: "text-primary bg-primary/10",
string: "text-muted-foreground bg-muted",
date: "text-purple-500 bg-purple-500/10",
boolean: "text-success bg-success/10",
object: "text-warning bg-warning/10",
unknown: "text-muted-foreground/50 bg-muted"
}[type];
return (
@@ -746,7 +746,7 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
h-4 w-4 rounded border-2 flex items-center justify-center transition-colors
${isSelected
? "border-primary bg-primary"
: "border-gray-300 bg-background"
: "border-border bg-background"
}
`}>
{isSelected && (

View File

@@ -324,10 +324,10 @@ export default function MultiDataSourceConfig({
{(item.status || item.level) && (
<div className={`rounded px-2 py-0.5 text-[10px] font-medium ${
(item.status || item.level)?.includes('경보') || (item.status || item.level)?.includes('위험')
? 'bg-red-100 text-red-700'
? 'bg-destructive/10 text-destructive'
: (item.status || item.level)?.includes('주의')
? 'bg-orange-100 text-orange-700'
: 'bg-blue-100 text-blue-700'
? 'bg-warning/10 text-warning'
: 'bg-primary/10 text-primary'
}`}>
{item.status || item.level}
</div>

View File

@@ -406,8 +406,8 @@ ORDER BY 하위부서수 DESC`,
<div
className={`flex items-center gap-2 rounded-md p-2 text-xs ${
testResult.success
? "bg-green-50 text-green-700"
: "bg-red-50 text-red-700"
? "bg-success/10 text-success"
: "bg-destructive/10 text-destructive"
}`}
>
{testResult.success ? (
@@ -491,12 +491,12 @@ ORDER BY 하위부서수 DESC`,
}[type];
const typeColor = {
number: "text-blue-600 bg-blue-50",
string: "text-gray-600 bg-gray-50",
date: "text-purple-600 bg-purple-50",
boolean: "text-green-600 bg-green-50",
object: "text-orange-600 bg-orange-50",
unknown: "text-gray-400 bg-gray-50"
number: "text-primary bg-primary/10",
string: "text-foreground bg-muted",
date: "text-purple-500 bg-purple-500/10",
boolean: "text-success bg-success/10",
object: "text-warning bg-warning/10",
unknown: "text-muted-foreground bg-muted"
}[type];
return (
@@ -527,7 +527,7 @@ ORDER BY 하위부서수 DESC`,
h-4 w-4 rounded border-2 flex items-center justify-center transition-colors
${isSelected
? "border-primary bg-primary"
: "border-gray-300 bg-background"
: "border-border bg-background"
}
`}>
{isSelected && (

View File

@@ -91,19 +91,19 @@ export function CalendarSettings({ config, onSave, onClose }: CalendarSettingsPr
{
value: "light",
label: "Light",
gradient: "bg-gradient-to-br from-white to-gray-100",
text: "text-gray-900",
gradient: "bg-gradient-to-br from-background to-muted",
text: "text-foreground",
},
{
value: "dark",
label: "Dark",
gradient: "bg-gradient-to-br from-gray-800 to-gray-900",
gradient: "bg-gradient-to-br from-foreground to-foreground",
text: "text-white",
},
{
value: "custom",
label: "사용자",
gradient: "bg-gradient-to-br from-blue-400 to-purple-600",
gradient: "bg-gradient-to-br from-primary to-purple-500",
text: "text-white",
},
].map((theme) => (

View File

@@ -81,7 +81,7 @@ export function CalendarWidget({ element, onConfigUpdate }: CalendarWidgetProps)
return (
<div className="relative flex h-full w-full flex-col">
{/* 헤더 - 네비게이션 */}
<div className="flex items-center justify-between border-b border-gray-200 p-2">
<div className="flex items-center justify-between border-b border-border p-2">
{/* 이전 월 버튼 */}
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={handlePrevMonth}>
<ChevronLeft className="h-4 w-4" />
@@ -123,7 +123,7 @@ export function CalendarWidget({ element, onConfigUpdate }: CalendarWidgetProps)
<div className="absolute bottom-2 right-2">
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 bg-white/80 hover:bg-white">
<Button variant="ghost" size="icon" className="h-8 w-8 bg-background/80 hover:bg-background">
<Settings className="h-4 w-4" />
</Button>
</PopoverTrigger>

View File

@@ -97,19 +97,19 @@ export function ClockSettings({ config, onSave, onClose }: ClockSettingsProps) {
{
value: "light",
label: "Light",
gradient: "bg-gradient-to-br from-white to-gray-100",
text: "text-gray-900",
gradient: "bg-gradient-to-br from-background to-muted",
text: "text-foreground",
},
{
value: "dark",
label: "Dark",
gradient: "bg-gradient-to-br from-gray-800 to-gray-900",
gradient: "bg-gradient-to-br from-foreground to-foreground",
text: "text-white",
},
{
value: "custom",
label: "사용자",
gradient: "bg-gradient-to-br from-blue-400 to-purple-600",
gradient: "bg-gradient-to-br from-primary to-purple-500",
text: "text-white",
},
].map((theme) => (

View File

@@ -116,7 +116,7 @@ export function ClockWidget({ element, onConfigUpdate }: ClockWidgetProps) {
<div className="absolute top-2 right-2">
<Popover open={settingsOpen} onOpenChange={setSettingsOpen}>
<PopoverTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 bg-white/80 hover:bg-white">
<Button variant="ghost" size="icon" className="h-8 w-8 bg-background/80 hover:bg-background">
<Settings className="h-4 w-4" />
</Button>
</PopoverTrigger>

View File

@@ -112,22 +112,22 @@ function getThemeClasses(theme: string, customColor?: string) {
const themes = {
light: {
container: "bg-white text-gray-900",
date: "text-gray-600",
time: "text-gray-900",
timezone: "text-gray-500",
container: "bg-background text-foreground",
date: "text-foreground",
time: "text-foreground",
timezone: "text-muted-foreground",
},
dark: {
container: "bg-gray-900 text-white",
date: "text-gray-300",
date: "text-muted-foreground",
time: "text-white",
timezone: "text-gray-400",
timezone: "text-muted-foreground",
},
custom: {
container: "bg-gradient-to-br from-blue-400 to-purple-600 text-white",
date: "text-blue-100",
container: "bg-gradient-to-br from-primary to-purple-500 text-white",
date: "text-primary/70",
time: "text-white",
timezone: "text-blue-200",
timezone: "text-primary/80",
},
};

View File

@@ -25,25 +25,25 @@ export function DriverListView({ drivers, config, isCompact = false }: DriverLis
return (
<div className="flex h-full flex-col items-center justify-center space-y-3 p-4">
<div className="text-center">
<div className="text-3xl font-bold text-gray-900">{drivers.length}</div>
<div className="text-sm text-gray-600"> </div>
<div className="text-3xl font-bold text-foreground">{drivers.length}</div>
<div className="text-sm text-foreground"> </div>
</div>
<div className="grid w-full grid-cols-2 gap-2 text-center text-xs">
<div className="rounded-lg bg-green-100 p-2">
<div className="font-semibold text-green-800">{stats.driving}</div>
<div className="text-green-600"></div>
<div className="rounded-lg bg-success/10 p-2">
<div className="font-semibold text-success">{stats.driving}</div>
<div className="text-success"></div>
</div>
<div className="rounded-lg bg-gray-100 p-2">
<div className="font-semibold text-gray-800">{stats.standby}</div>
<div className="text-gray-600"></div>
<div className="rounded-lg bg-muted p-2">
<div className="font-semibold text-foreground">{stats.standby}</div>
<div className="text-foreground"></div>
</div>
<div className="rounded-lg bg-orange-100 p-2">
<div className="font-semibold text-orange-800">{stats.resting}</div>
<div className="text-orange-600"></div>
<div className="rounded-lg bg-warning/10 p-2">
<div className="font-semibold text-warning">{stats.resting}</div>
<div className="text-warning"></div>
</div>
<div className="rounded-lg bg-red-100 p-2">
<div className="font-semibold text-red-800">{stats.maintenance}</div>
<div className="text-red-600"></div>
<div className="rounded-lg bg-destructive/10 p-2">
<div className="font-semibold text-destructive">{stats.maintenance}</div>
<div className="text-destructive"></div>
</div>
</div>
</div>
@@ -53,54 +53,54 @@ export function DriverListView({ drivers, config, isCompact = false }: DriverLis
// 빈 데이터 처리
if (drivers.length === 0) {
return (
<div className="flex h-full items-center justify-center text-sm text-gray-500"> </div>
<div className="flex h-full items-center justify-center text-sm text-muted-foreground"> </div>
);
}
return (
<div className="h-full w-full overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="sticky top-0 z-10 bg-gray-50">
<thead className="sticky top-0 z-10 bg-muted">
<tr>
{visibleColumns.includes("status") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.status}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.status}</th>
)}
{visibleColumns.includes("name") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.name}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.name}</th>
)}
{visibleColumns.includes("vehicleNumber") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.vehicleNumber}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.vehicleNumber}</th>
)}
{visibleColumns.includes("vehicleType") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.vehicleType}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.vehicleType}</th>
)}
{visibleColumns.includes("departure") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.departure}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.departure}</th>
)}
{visibleColumns.includes("destination") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.destination}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.destination}</th>
)}
{visibleColumns.includes("departureTime") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.departureTime}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.departureTime}</th>
)}
{visibleColumns.includes("estimatedArrival") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">
{COLUMN_LABELS.estimatedArrival}
</th>
)}
{visibleColumns.includes("phone") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.phone}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.phone}</th>
)}
{visibleColumns.includes("progress") && (
<th className="px-3 py-2 text-left text-xs font-semibold text-gray-700">{COLUMN_LABELS.progress}</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-foreground">{COLUMN_LABELS.progress}</th>
)}
</tr>
</thead>
<tbody className="divide-y divide-gray-200 bg-white">
<tbody className="divide-y divide-gray-200 bg-background">
{drivers.map((driver) => {
const statusColors = getStatusColor(driver.status);
return (
<tr key={driver.id} className="transition-colors hover:bg-gray-50">
<tr key={driver.id} className="transition-colors hover:bg-muted">
{visibleColumns.includes("status") && (
<td className="px-3 py-2">
<span
@@ -111,42 +111,42 @@ export function DriverListView({ drivers, config, isCompact = false }: DriverLis
</td>
)}
{visibleColumns.includes("name") && (
<td className="px-3 py-2 text-sm font-medium text-gray-900">{driver.name}</td>
<td className="px-3 py-2 text-sm font-medium text-foreground">{driver.name}</td>
)}
{visibleColumns.includes("vehicleNumber") && (
<td className="px-3 py-2 text-sm text-gray-700">{driver.vehicleNumber}</td>
<td className="px-3 py-2 text-sm text-foreground">{driver.vehicleNumber}</td>
)}
{visibleColumns.includes("vehicleType") && (
<td className="px-3 py-2 text-sm text-gray-600">{driver.vehicleType}</td>
<td className="px-3 py-2 text-sm text-foreground">{driver.vehicleType}</td>
)}
{visibleColumns.includes("departure") && (
<td className="px-3 py-2 text-sm text-gray-700">
{driver.departure || <span className="text-gray-400">-</span>}
<td className="px-3 py-2 text-sm text-foreground">
{driver.departure || <span className="text-muted-foreground">-</span>}
</td>
)}
{visibleColumns.includes("destination") && (
<td className="px-3 py-2 text-sm text-gray-700">
{driver.destination || <span className="text-gray-400">-</span>}
<td className="px-3 py-2 text-sm text-foreground">
{driver.destination || <span className="text-muted-foreground">-</span>}
</td>
)}
{visibleColumns.includes("departureTime") && (
<td className="px-3 py-2 text-sm text-gray-600">{formatTime(driver.departureTime)}</td>
<td className="px-3 py-2 text-sm text-foreground">{formatTime(driver.departureTime)}</td>
)}
{visibleColumns.includes("estimatedArrival") && (
<td className="px-3 py-2 text-sm text-gray-600">{formatTime(driver.estimatedArrival)}</td>
<td className="px-3 py-2 text-sm text-foreground">{formatTime(driver.estimatedArrival)}</td>
)}
{visibleColumns.includes("phone") && (
<td className="px-3 py-2 text-sm text-gray-600">{driver.phone}</td>
<td className="px-3 py-2 text-sm text-foreground">{driver.phone}</td>
)}
{visibleColumns.includes("progress") && (
<td className="px-3 py-2">
{driver.progress !== undefined ? (
<div className="flex items-center space-x-2">
<Progress value={driver.progress} className="h-2 w-16" />
<span className="text-xs text-gray-600">{driver.progress}%</span>
<span className="text-xs text-foreground">{driver.progress}%</span>
</div>
) : (
<span className="text-gray-400">-</span>
<span className="text-muted-foreground">-</span>
)}
</td>
)}

View File

@@ -110,7 +110,7 @@ export function DriverManagementSettings({ config, onSave, onClose }: DriverMana
<Card
key={key}
className={`cursor-pointer border p-3 transition-colors ${
localConfig.visibleColumns.includes(key) ? "border-primary bg-primary/5" : "hover:bg-gray-50"
localConfig.visibleColumns.includes(key) ? "border-primary bg-primary/5" : "hover:bg-muted"
}`}
onClick={() => toggleColumn(key)}
>
@@ -128,7 +128,7 @@ export function DriverManagementSettings({ config, onSave, onClose }: DriverMana
</div>
{/* 푸터 - 고정 */}
<div className="flex flex-shrink-0 justify-end gap-3 border-t border-gray-200 bg-gray-50 p-4">
<div className="flex flex-shrink-0 justify-end gap-3 border-t border-border bg-muted p-4">
<Button variant="outline" onClick={onClose}>
</Button>

View File

@@ -70,14 +70,14 @@ export function DriverManagementWidget({ element, onConfigUpdate }: DriverManage
const isCompact = element.size.width < 400 || element.size.height < 300;
return (
<div className="relative flex h-full w-full flex-col bg-white">
<div className="relative flex h-full w-full flex-col bg-background">
{/* 헤더 - 컴팩트 모드가 아닐 때만 표시 */}
{!isCompact && (
<div className="flex-shrink-0 border-b border-gray-200 bg-gray-50 px-3 py-2">
<div className="flex-shrink-0 border-b border-border bg-muted px-3 py-2">
<div className="flex items-center justify-between gap-2">
{/* 검색 */}
<div className="relative max-w-xs flex-1">
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-gray-400" />
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
<Input
type="text"
placeholder="기사명, 차량번호 검색"
@@ -132,20 +132,20 @@ export function DriverManagementWidget({ element, onConfigUpdate }: DriverManage
</div>
{/* 통계 정보 */}
<div className="mt-2 flex items-center gap-3 text-xs text-gray-600">
<div className="mt-2 flex items-center gap-3 text-xs text-foreground">
<span>
<span className="font-semibold text-gray-900">{filteredDrivers.length}</span>
<span className="font-semibold text-foreground">{filteredDrivers.length}</span>
</span>
<span className="text-gray-400">|</span>
<span className="text-muted-foreground">|</span>
<span>
{" "}
<span className="font-semibold text-green-600">
<span className="font-semibold text-success">
{filteredDrivers.filter((d) => d.status === "driving").length}
</span>
</span>
<span className="text-gray-400">|</span>
<span className="text-xs text-gray-500"> : {lastRefresh.toLocaleTimeString("ko-KR")}</span>
<span className="text-muted-foreground">|</span>
<span className="text-xs text-muted-foreground"> : {lastRefresh.toLocaleTimeString("ko-KR")}</span>
</div>
</div>
)}

View File

@@ -168,8 +168,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
return (
<div className="flex h-full w-full items-center justify-center">
<div className="text-center">
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" />
<div className="text-sm text-gray-600"> ...</div>
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<div className="text-sm text-foreground"> ...</div>
</div>
</div>
);
@@ -181,8 +181,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full items-center justify-center">
<div className="text-center">
<div className="mb-2 text-2xl"></div>
<div className="text-sm font-medium text-red-600"> </div>
<div className="mt-1 text-xs text-gray-500">{error}</div>
<div className="text-sm font-medium text-destructive"> </div>
<div className="mt-1 text-xs text-muted-foreground">{error}</div>
</div>
</div>
);
@@ -194,8 +194,8 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full flex-col items-center justify-center gap-4 p-4">
<div className="text-center">
<div className="mb-2 text-4xl">📋</div>
<div className="text-sm font-medium text-gray-700"> </div>
<div className="mt-1 text-xs text-gray-500"> </div>
<div className="text-sm font-medium text-foreground"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
);
@@ -222,7 +222,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<div className="flex h-full w-full flex-col p-4">
{/* 제목 - 항상 표시 */}
<div className="mb-4">
<h3 className="text-sm font-semibold text-gray-700">{element.customTitle || element.title}</h3>
<h3 className="text-sm font-semibold text-foreground">{element.customTitle || element.title}</h3>
</div>
{/* 테이블 뷰 */}
@@ -251,7 +251,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
<TableRow>
<TableCell
colSpan={displayColumns.filter((col) => col.visible).length}
className="text-center text-gray-500"
className="text-center text-muted-foreground"
>
</TableCell>
@@ -281,7 +281,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
{config.viewMode === "card" && (
<div className="flex-1 overflow-auto">
{paginatedRows.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-500"> </div>
<div className="flex h-full items-center justify-center text-muted-foreground"> </div>
) : (
<div
className={`grid gap-4 ${config.compactMode ? "text-xs" : "text-sm"}`}
@@ -296,9 +296,9 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
.filter((col) => col.visible)
.map((col) => (
<div key={col.id}>
<div className="text-xs font-medium text-gray-500">{col.label || col.name}</div>
<div className="text-xs font-medium text-muted-foreground">{col.label || col.name}</div>
<div
className={`font-medium text-gray-900 ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : ""}`}
className={`font-medium text-foreground ${col.align === "center" ? "text-center" : col.align === "right" ? "text-right" : ""}`}
>
{String(row[col.dataKey || col.field] ?? "")}
</div>
@@ -315,7 +315,7 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
{/* 페이지네이션 */}
{config.enablePagination && totalPages > 1 && (
<div className="mt-4 flex items-center justify-between text-sm">
<div className="text-gray-600">
<div className="text-foreground">
{startIdx + 1}-{Math.min(endIdx, data.rows.length)} / {data.rows.length}
</div>
<div className="flex gap-2">
@@ -328,9 +328,9 @@ export function ListWidget({ element, onConfigUpdate }: ListWidgetProps) {
</Button>
<div className="flex items-center gap-1 px-2">
<span className="text-gray-700">{currentPage}</span>
<span className="text-gray-400">/</span>
<span className="text-gray-500">{totalPages}</span>
<span className="text-foreground">{currentPage}</span>
<span className="text-muted-foreground">/</span>
<span className="text-muted-foreground">{totalPages}</span>
</div>
<Button
variant="outline"

View File

@@ -142,15 +142,15 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div className="flex max-h-[90vh] w-[90vw] max-w-6xl flex-col rounded-xl border bg-white shadow-2xl">
<div className="flex max-h-[90vh] w-[90vw] max-w-6xl flex-col rounded-xl border bg-background shadow-2xl">
{/* 헤더 */}
<div className="space-y-4 border-b px-6 py-4">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold">📋 </h2>
<p className="mt-1 text-sm text-gray-600"> </p>
<p className="mt-1 text-sm text-foreground"> </p>
</div>
<button onClick={onClose} className="rounded-lg p-2 transition-colors hover:bg-gray-100">
<button onClick={onClose} className="rounded-lg p-2 transition-colors hover:bg-muted">
<X className="h-5 w-5" />
</button>
</div>
@@ -173,34 +173,34 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List
</div>
{/* 참고: 리스트 위젯은 제목이 항상 표시됩니다 */}
<div className="rounded bg-blue-50 p-2 text-xs text-blue-700">💡 </div>
<div className="rounded bg-primary/10 p-2 text-xs text-primary">💡 </div>
</div>
{/* 진행 상태 표시 */}
<div className="border-b bg-gray-50 px-6 py-4">
<div className="border-b bg-muted px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className={`flex items-center gap-2 ${currentStep >= 1 ? "text-blue-600" : "text-gray-400"}`}>
<div className={`flex items-center gap-2 ${currentStep >= 1 ? "text-primary" : "text-muted-foreground"}`}>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 1 ? "bg-blue-600 text-white" : "bg-gray-300"}`}
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 1 ? "bg-primary text-white" : "bg-muted"}`}
>
1
</div>
<span className="text-sm font-medium"> </span>
</div>
<div className="h-0.5 w-12 bg-gray-300" />
<div className={`flex items-center gap-2 ${currentStep >= 2 ? "text-blue-600" : "text-gray-400"}`}>
<div className="h-0.5 w-12 bg-muted" />
<div className={`flex items-center gap-2 ${currentStep >= 2 ? "text-primary" : "text-muted-foreground"}`}>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 2 ? "bg-blue-600 text-white" : "bg-gray-300"}`}
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 2 ? "bg-primary text-white" : "bg-muted"}`}
>
2
</div>
<span className="text-sm font-medium"> </span>
</div>
<div className="h-0.5 w-12 bg-gray-300" />
<div className={`flex items-center gap-2 ${currentStep >= 3 ? "text-blue-600" : "text-gray-400"}`}>
<div className="h-0.5 w-12 bg-muted" />
<div className={`flex items-center gap-2 ${currentStep >= 3 ? "text-primary" : "text-muted-foreground"}`}>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 3 ? "bg-blue-600 text-white" : "bg-gray-300"}`}
className={`flex h-8 w-8 items-center justify-center rounded-full ${currentStep >= 3 ? "bg-primary text-white" : "bg-muted"}`}
>
3
</div>
@@ -240,21 +240,21 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List
{/* 오른쪽: 데이터 미리보기 */}
<div>
{queryResult && queryResult.rows.length > 0 ? (
<div className="rounded-lg border bg-gray-50 p-4">
<h3 className="mb-3 font-semibold text-gray-800">📋 </h3>
<div className="overflow-x-auto rounded bg-white p-3">
<div className="rounded-lg border bg-muted p-4">
<h3 className="mb-3 font-semibold text-foreground">📋 </h3>
<div className="overflow-x-auto rounded bg-background p-3">
<Badge variant="secondary" className="mb-2">
{queryResult.totalRows}
</Badge>
<pre className="text-xs text-gray-700">
<pre className="text-xs text-foreground">
{JSON.stringify(queryResult.rows.slice(0, 3), null, 2)}
</pre>
</div>
</div>
) : (
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center">
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted p-8 text-center">
<div>
<div className="mt-1 text-xs text-gray-500"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
)}
@@ -288,10 +288,10 @@ export function ListWidgetConfigModal({ isOpen, element, onClose, onSave }: List
</div>
{/* 푸터 */}
<div className="flex items-center justify-between border-t bg-gray-50 p-6">
<div className="flex items-center justify-between border-t bg-muted p-6">
<div>
{queryResult && (
<Badge variant="default" className="bg-green-600">
<Badge variant="default" className="bg-success">
📊 {queryResult.rows.length}
</Badge>
)}

View File

@@ -132,31 +132,31 @@ export function ListWidgetConfigSidebar({ element, isOpen, onClose, onApply }: L
return (
<div
className={cn(
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-gray-50 transition-transform duration-300 ease-in-out",
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-muted transition-transform duration-300 ease-in-out",
isOpen ? "translate-x-0" : "translate-x-[-100%]",
)}
>
{/* 헤더 */}
<div className="flex items-center justify-between bg-white px-3 py-2 shadow-sm">
<div className="flex items-center justify-between bg-background px-3 py-2 shadow-sm">
<div className="flex items-center gap-2">
<div className="bg-primary/10 flex h-6 w-6 items-center justify-center rounded">
<span className="text-primary text-xs font-bold">📋</span>
</div>
<span className="text-xs font-semibold text-gray-900"> </span>
<span className="text-xs font-semibold text-foreground"> </span>
</div>
<button
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-gray-100"
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-muted"
>
<X className="h-3.5 w-3.5 text-gray-500" />
<X className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
{/* 본문: 스크롤 가능 영역 */}
<div className="flex-1 overflow-y-auto p-3">
{/* 기본 설정 */}
<div className="mb-3 rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="mb-3 rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="space-y-2">
<div>
<input
@@ -165,31 +165,31 @@ export function ListWidgetConfigSidebar({ element, isOpen, onClose, onApply }: L
onChange={(e) => setTitle(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
placeholder="리스트 이름"
className="focus:border-primary focus:ring-primary/20 h-8 w-full rounded border border-gray-200 bg-gray-50 px-2 text-xs placeholder:text-gray-400 focus:bg-white focus:ring-1 focus:outline-none"
className="focus:border-primary focus:ring-primary/20 h-8 w-full rounded border border-border bg-muted px-2 text-xs placeholder:text-muted-foreground focus:bg-background focus:ring-1 focus:outline-none"
/>
</div>
</div>
</div>
{/* 데이터 소스 */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<Tabs
defaultValue={dataSource.type}
onValueChange={(value) => handleDataSourceTypeChange(value as "database" | "api")}
className="w-full"
>
<TabsList className="grid h-7 w-full grid-cols-2 bg-gray-100 p-0.5">
<TabsList className="grid h-7 w-full grid-cols-2 bg-muted p-0.5">
<TabsTrigger
value="database"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
className="h-6 rounded text-[11px] data-[state=active]:bg-background data-[state=active]:shadow-sm"
>
</TabsTrigger>
<TabsTrigger
value="api"
className="h-6 rounded text-[11px] data-[state=active]:bg-white data-[state=active]:shadow-sm"
className="h-6 rounded text-[11px] data-[state=active]:bg-background data-[state=active]:shadow-sm"
>
REST API
</TabsTrigger>
@@ -211,17 +211,17 @@ export function ListWidgetConfigSidebar({ element, isOpen, onClose, onApply }: L
{/* 데이터 로드 상태 */}
{queryResult && (
<div className="mt-2 flex items-center gap-1.5 rounded bg-green-50 px-2 py-1">
<div className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span className="text-[10px] font-medium text-green-700">{queryResult.rows.length} </span>
<div className="mt-2 flex items-center gap-1.5 rounded bg-success/10 px-2 py-1">
<div className="h-1.5 w-1.5 rounded-full bg-success" />
<span className="text-[10px] font-medium text-success">{queryResult.rows.length} </span>
</div>
)}
</div>
{/* 컬럼 설정 - 쿼리 실행 후에만 표시 */}
{queryResult && (
<div className="mt-3 rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="mt-3 rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<UnifiedColumnEditor
queryResult={queryResult}
config={listConfig}
@@ -232,18 +232,18 @@ export function ListWidgetConfigSidebar({ element, isOpen, onClose, onApply }: L
{/* 테이블 옵션 - 컬럼이 있을 때만 표시 */}
{listConfig.columns.length > 0 && (
<div className="mt-3 rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="mt-3 rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<ListTableOptions config={listConfig} onConfigChange={handleListConfigChange} />
</div>
)}
</div>
{/* 푸터: 적용 버튼 */}
<div className="flex gap-2 bg-white p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<div className="flex gap-2 bg-background p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<button
onClick={onClose}
className="flex-1 rounded bg-gray-100 py-2 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200"
className="flex-1 rounded bg-muted py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted"
>
</button>

View File

@@ -67,11 +67,11 @@ export function MonthView({ days, config, isCompact = false, selectedDate, onDat
const sizeClass = isCompact ? "text-xs" : "text-sm";
const cursorClass = day.isCurrentMonth ? "cursor-pointer" : "cursor-default";
let colorClass = "text-gray-700";
let colorClass = "text-foreground";
// 현재 월이 아닌 날짜
if (!day.isCurrentMonth) {
colorClass = "text-gray-300";
colorClass = "text-muted-foreground";
}
// 선택된 날짜
else if (isSelected(day)) {
@@ -87,7 +87,7 @@ export function MonthView({ days, config, isCompact = false, selectedDate, onDat
}
// 주말
else if (config.highlightWeekends && day.isWeekend) {
colorClass = "text-red-600";
colorClass = "text-destructive";
}
let bgClass = "";
@@ -96,7 +96,7 @@ export function MonthView({ days, config, isCompact = false, selectedDate, onDat
} else if (config.highlightToday && day.isToday) {
bgClass = "";
} else {
bgClass = "hover:bg-gray-100";
bgClass = "hover:bg-muted";
}
return `${baseClass} ${sizeClass} ${colorClass} ${bgClass} ${cursorClass}`;
@@ -112,7 +112,7 @@ export function MonthView({ days, config, isCompact = false, selectedDate, onDat
return (
<div
key={name}
className={`text-center text-xs font-semibold ${isWeekend && config.highlightWeekends ? "text-red-600" : "text-gray-600"}`}
className={`text-center text-xs font-semibold ${isWeekend && config.highlightWeekends ? "text-destructive" : "text-foreground"}`}
>
{name}
</div>

View File

@@ -233,41 +233,41 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/50">
<div className="relative flex h-[90vh] w-[90vw] max-w-6xl flex-col rounded-lg bg-white shadow-xl">
<div className="relative flex h-[90vh] w-[90vw] max-w-6xl flex-col rounded-lg bg-background shadow-xl">
{/* 헤더 */}
<div className="flex items-center justify-between border-b border-gray-200 px-6 py-4">
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<div>
<h2 className="text-xl font-bold text-gray-800"> </h2>
<p className="mt-1 text-sm text-gray-500">
<h2 className="text-xl font-bold text-foreground"> </h2>
<p className="mt-1 text-sm text-muted-foreground">
</p>
</div>
<button
onClick={onClose}
className="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700"
className="rounded-lg p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<X className="h-5 w-5" />
</button>
</div>
{/* 진행 상태 */}
<div className="border-b border-gray-200 bg-gray-50 px-6 py-3">
<div className="border-b border-border bg-muted px-6 py-3">
<div className="flex items-center gap-4">
<div className={`flex items-center gap-2 ${currentStep === 1 ? "text-primary" : "text-gray-400"}`}>
<div className={`flex items-center gap-2 ${currentStep === 1 ? "text-primary" : "text-muted-foreground"}`}>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full font-semibold ${
currentStep === 1 ? "bg-primary text-white" : "bg-gray-200"
currentStep === 1 ? "bg-primary text-white" : "bg-muted"
}`}
>
1
</div>
<span className="font-medium"> </span>
</div>
<ChevronRight className="h-4 w-4 text-gray-400" />
<div className={`flex items-center gap-2 ${currentStep === 2 ? "text-primary" : "text-gray-400"}`}>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<div className={`flex items-center gap-2 ${currentStep === 2 ? "text-primary" : "text-muted-foreground"}`}>
<div
className={`flex h-8 w-8 items-center justify-center rounded-full font-semibold ${
currentStep === 2 ? "bg-primary text-white" : "bg-gray-200"
currentStep === 2 ? "bg-primary text-white" : "bg-muted"
}`}
>
2
@@ -312,47 +312,47 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
{currentStep === 2 && (
<div className="space-y-6">
<div>
<div className="mb-4 rounded-lg bg-blue-50 p-4">
<h3 className="mb-2 font-semibold text-blue-900">💡 </h3>
<p className="mb-2 text-sm text-blue-700">
<div className="mb-4 rounded-lg bg-primary/10 p-4">
<h3 className="mb-2 font-semibold text-primary">💡 </h3>
<p className="mb-2 text-sm text-primary">
:
</p>
<ul className="space-y-1 text-sm text-blue-600">
<ul className="space-y-1 text-sm text-primary">
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">id</code> - ID ( )
<code className="rounded bg-primary/10 px-1 py-0.5">id</code> - ID ( )
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">title</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">task</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">name</code> - ()
<code className="rounded bg-primary/10 px-1 py-0.5">title</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">task</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">name</code> - ()
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">description</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">desc</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">content</code> -
<code className="rounded bg-primary/10 px-1 py-0.5">description</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">desc</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">content</code> -
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">priority</code> - (urgent, high,
<code className="rounded bg-primary/10 px-1 py-0.5">priority</code> - (urgent, high,
normal, low)
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">status</code> - (pending, in_progress,
<code className="rounded bg-primary/10 px-1 py-0.5">status</code> - (pending, in_progress,
completed)
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">assigned_to</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">assignedTo</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">user</code> -
<code className="rounded bg-primary/10 px-1 py-0.5">assigned_to</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">assignedTo</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">user</code> -
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">due_date</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">dueDate</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">deadline</code> -
<code className="rounded bg-primary/10 px-1 py-0.5">due_date</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">dueDate</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">deadline</code> -
</li>
<li>
<code className="rounded bg-blue-100 px-1 py-0.5">is_urgent</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">isUrgent</code>,{" "}
<code className="rounded bg-blue-100 px-1 py-0.5">urgent</code> -
<code className="rounded bg-primary/10 px-1 py-0.5">is_urgent</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">isUrgent</code>,{" "}
<code className="rounded bg-primary/10 px-1 py-0.5">urgent</code> -
</li>
</ul>
</div>
@@ -365,26 +365,26 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
</div>
{/* 디버그: 항상 표시되는 테스트 메시지 */}
<div className="mt-4 rounded-lg bg-yellow-50 border-2 border-yellow-500 p-4">
<p className="text-sm font-bold text-yellow-900">
<div className="mt-4 rounded-lg bg-warning/10 border-2 border-warning p-4">
<p className="text-sm font-bold text-warning">
🔍 디버그: queryResult = {queryResult ? "있음" : "없음"}
</p>
{queryResult && (
<p className="text-xs text-yellow-700 mt-1">
<p className="text-xs text-warning mt-1">
rows: {queryResult.rows?.length}, error: {queryResult.error || "없음"}
</p>
)}
</div>
{queryResult && !queryResult.error && queryResult.rows && queryResult.rows.length > 0 && (
<div className="mt-4 rounded-lg bg-green-50 border-2 border-green-500 p-4">
<h3 className="mb-2 font-semibold text-green-900"> !</h3>
<p className="text-sm text-green-700">
<div className="mt-4 rounded-lg bg-success/10 border-2 border-success p-4">
<h3 className="mb-2 font-semibold text-success"> !</h3>
<p className="text-sm text-success">
<strong>{queryResult.rows.length}</strong> .
</p>
<div className="mt-3 rounded bg-white p-3">
<p className="mb-2 text-xs font-semibold text-gray-600"> :</p>
<pre className="overflow-x-auto text-xs text-gray-700">
<div className="mt-3 rounded bg-background p-3">
<p className="mb-2 text-xs font-semibold text-foreground"> :</p>
<pre className="overflow-x-auto text-xs text-foreground">
{JSON.stringify(queryResult.rows[0], null, 2)}
</pre>
</div>
@@ -392,10 +392,10 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
)}
{/* 데이터베이스 연동 쿼리 (선택사항) */}
<div className="mt-6 space-y-4 rounded-lg border-2 border-purple-200 bg-purple-50 p-4">
<div className="mt-6 space-y-4 rounded-lg border-2 border-purple-500 bg-purple-500/10 p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-purple-900">🔗 ()</h3>
<h3 className="font-semibold text-purple-700">🔗 ()</h3>
<p className="text-sm text-purple-700">
//
</p>
@@ -405,9 +405,9 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
type="checkbox"
checked={enableDbSync}
onChange={(e) => setEnableDbSync(e.target.checked)}
className="h-4 w-4 rounded border-purple-300"
className="h-4 w-4 rounded border-purple-500/50"
/>
<span className="text-sm font-medium text-purple-900"></span>
<span className="text-sm font-medium text-purple-700"></span>
</label>
</div>
@@ -419,8 +419,8 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
onClick={() => setDbSyncMode("simple")}
className={`flex-1 rounded px-4 py-2 text-sm font-medium transition-colors ${
dbSyncMode === "simple"
? "bg-purple-600 text-white"
: "bg-white text-purple-600 hover:bg-purple-100"
? "bg-purple-500 text-white"
: "bg-background text-purple-500 hover:bg-purple-500/10"
}`}
>
@@ -429,8 +429,8 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
onClick={() => setDbSyncMode("advanced")}
className={`flex-1 rounded px-4 py-2 text-sm font-medium transition-colors ${
dbSyncMode === "advanced"
? "bg-purple-600 text-white"
: "bg-white text-purple-600 hover:bg-purple-100"
? "bg-purple-500 text-white"
: "bg-background text-purple-500 hover:bg-purple-500/10"
}`}
>
@@ -439,14 +439,14 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
{/* 간편 모드 */}
{dbSyncMode === "simple" && (
<div className="space-y-4 rounded-lg border border-purple-300 bg-white p-4">
<div className="space-y-4 rounded-lg border border-purple-500/50 bg-background p-4">
<p className="text-sm text-purple-700">
INSERT/UPDATE/DELETE .
</p>
{/* 테이블명 */}
<div>
<Label className="text-sm font-semibold text-purple-900"> *</Label>
<Label className="text-sm font-semibold text-purple-700"> *</Label>
<Input
value={tableName}
onChange={(e) => setTableName(e.target.value)}
@@ -457,10 +457,10 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
{/* 컬럼 매핑 */}
<div>
<Label className="text-sm font-semibold text-purple-900"> </Label>
<Label className="text-sm font-semibold text-purple-700"> </Label>
<div className="mt-2 grid grid-cols-2 gap-3">
<div>
<label className="text-xs text-gray-600">ID </label>
<label className="text-xs text-foreground">ID </label>
<Input
value={columnMapping.id}
onChange={(e) => setColumnMapping({ ...columnMapping, id: e.target.value })}
@@ -469,7 +469,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.title}
onChange={(e) => setColumnMapping({ ...columnMapping, title: e.target.value })}
@@ -478,7 +478,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.description}
onChange={(e) => setColumnMapping({ ...columnMapping, description: e.target.value })}
@@ -487,7 +487,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.priority}
onChange={(e) => setColumnMapping({ ...columnMapping, priority: e.target.value })}
@@ -496,7 +496,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.status}
onChange={(e) => setColumnMapping({ ...columnMapping, status: e.target.value })}
@@ -505,7 +505,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.assignedTo}
onChange={(e) => setColumnMapping({ ...columnMapping, assignedTo: e.target.value })}
@@ -514,7 +514,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.dueDate}
onChange={(e) => setColumnMapping({ ...columnMapping, dueDate: e.target.value })}
@@ -523,7 +523,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
/>
</div>
<div>
<label className="text-xs text-gray-600"> </label>
<label className="text-xs text-foreground"> </label>
<Input
value={columnMapping.isUrgent}
onChange={(e) => setColumnMapping({ ...columnMapping, isUrgent: e.target.value })}
@@ -545,8 +545,8 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
{/* INSERT 쿼리 */}
<div>
<Label className="text-sm font-semibold text-purple-900">INSERT ()</Label>
<p className="mb-2 text-xs text-purple-600">
<Label className="text-sm font-semibold text-purple-700">INSERT ()</Label>
<p className="mb-2 text-xs text-purple-500">
변수: ${"{title}"}, ${"{description}"}, ${"{priority}"}, ${"{status}"}, ${"{assignedTo}"}, ${"{dueDate}"}, ${"{isUrgent}"}
</p>
<textarea
@@ -562,14 +562,14 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
Object.assign(element, updates);
}}
placeholder="예: INSERT INTO tasks (title, description, status) VALUES ('${title}', '${description}', '${status}')"
className="h-20 w-full rounded border border-purple-300 bg-white px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
className="h-20 w-full rounded border border-purple-500/50 bg-background px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
/>
</div>
{/* UPDATE 쿼리 */}
<div>
<Label className="text-sm font-semibold text-purple-900">UPDATE ( )</Label>
<p className="mb-2 text-xs text-purple-600">
<Label className="text-sm font-semibold text-purple-700">UPDATE ( )</Label>
<p className="mb-2 text-xs text-purple-500">
변수: ${"{id}"}, ${"{status}"}
</p>
<textarea
@@ -585,14 +585,14 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
Object.assign(element, updates);
}}
placeholder="예: UPDATE tasks SET status = '${status}' WHERE id = ${id}"
className="h-20 w-full rounded border border-purple-300 bg-white px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
className="h-20 w-full rounded border border-purple-500/50 bg-background px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
/>
</div>
{/* DELETE 쿼리 */}
<div>
<Label className="text-sm font-semibold text-purple-900">DELETE ()</Label>
<p className="mb-2 text-xs text-purple-600">
<Label className="text-sm font-semibold text-purple-700">DELETE ()</Label>
<p className="mb-2 text-xs text-purple-500">
변수: ${"{id}"}
</p>
<textarea
@@ -608,7 +608,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
Object.assign(element, updates);
}}
placeholder="예: DELETE FROM tasks WHERE id = ${id}"
className="h-20 w-full rounded border border-purple-300 bg-white px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
className="h-20 w-full rounded border border-purple-500/50 bg-background px-3 py-2 text-sm font-mono focus:border-purple-500 focus:outline-none"
/>
</div>
</div>
@@ -621,7 +621,7 @@ export function TodoWidgetConfigModal({ isOpen, element, onClose, onSave }: Todo
</div>
{/* 하단 버튼 */}
<div className="flex items-center justify-between border-t border-gray-200 px-6 py-4">
<div className="flex items-center justify-between border-t border-border px-6 py-4">
<div>
{currentStep > 1 && (
<Button onClick={handlePrev} variant="outline">

View File

@@ -137,11 +137,11 @@ export default function YardManagement3DWidget({
// 편집 모드: 레이아웃 선택 UI
if (isEditMode) {
return (
<div className="widget-interactive-area flex h-full w-full flex-col bg-white">
<div className="widget-interactive-area flex h-full w-full flex-col bg-background">
<div className="flex items-center justify-between border-b p-4">
<div>
<h3 className="text-sm font-semibold text-gray-700"> </h3>
<p className="mt-1 text-xs text-gray-500">
<h3 className="text-sm font-semibold text-foreground"> </h3>
<p className="mt-1 text-xs text-muted-foreground">
{config?.layoutName ? `선택됨: ${config.layoutName}` : "표시할 야드 레이아웃을 선택하세요"}
</p>
</div>
@@ -153,14 +153,14 @@ export default function YardManagement3DWidget({
<div className="flex-1 overflow-auto p-4">
{isLoading ? (
<div className="flex h-full items-center justify-center">
<div className="text-sm text-gray-500"> ...</div>
<div className="text-sm text-muted-foreground"> ...</div>
</div>
) : layouts.length === 0 ? (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="mb-2 text-4xl">🏗</div>
<div className="text-sm text-gray-600"> </div>
<div className="mt-1 text-xs text-gray-400"> </div>
<div className="text-sm text-foreground"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
</div>
</div>
) : (
@@ -169,17 +169,17 @@ export default function YardManagement3DWidget({
<div
key={layout.id}
className={`rounded-lg border p-3 transition-all ${
config?.layoutId === layout.id ? "border-blue-500 bg-blue-50" : "border-gray-200 bg-white"
config?.layoutId === layout.id ? "border-primary bg-primary/10" : "border-border bg-background"
}`}
>
<div className="flex items-start justify-between gap-3">
<button onClick={() => handleSelectLayout(layout)} className="flex-1 text-left hover:opacity-80">
<div className="flex items-center gap-2">
<span className="font-medium text-gray-900">{layout.name}</span>
{config?.layoutId === layout.id && <Check className="h-4 w-4 text-blue-600" />}
<span className="font-medium text-foreground">{layout.name}</span>
{config?.layoutId === layout.id && <Check className="h-4 w-4 text-primary" />}
</div>
{layout.description && <p className="mt-1 text-xs text-gray-500">{layout.description}</p>}
<div className="mt-2 text-xs text-gray-400"> : {layout.placement_count}</div>
{layout.description && <p className="mt-1 text-xs text-muted-foreground">{layout.description}</p>}
<div className="mt-2 text-xs text-muted-foreground"> : {layout.placement_count}</div>
</button>
<div className="flex gap-1">
<Button
@@ -195,7 +195,7 @@ export default function YardManagement3DWidget({
<Button
variant="outline"
size="sm"
className="text-red-600 hover:bg-red-50"
className="text-destructive hover:bg-destructive/10"
onClick={(e) => {
e.stopPropagation();
setDeleteLayoutId(layout.id);
@@ -230,18 +230,18 @@ export default function YardManagement3DWidget({
<DialogTitle> </DialogTitle>
</DialogHeader>
<div className="space-y-4">
<p className="text-sm text-gray-600">
<p className="text-sm text-foreground">
?
<br />
.
<br />
<span className="font-semibold text-red-600"> .</span>
<span className="font-semibold text-destructive"> .</span>
</p>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setDeleteLayoutId(null)}>
</Button>
<Button onClick={handleDeleteLayout} className="bg-red-600 hover:bg-red-700">
<Button onClick={handleDeleteLayout} className="bg-destructive hover:bg-destructive/90">
</Button>
</div>
@@ -256,12 +256,12 @@ export default function YardManagement3DWidget({
if (!config?.layoutId) {
console.warn("⚠️ 야드관리 위젯: layoutId가 설정되지 않음", { config, isEditMode });
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="flex h-full w-full items-center justify-center bg-muted">
<div className="text-center">
<div className="mb-2 text-4xl">🏗</div>
<div className="text-sm font-medium text-gray-600"> </div>
<div className="mt-1 text-xs text-gray-400"> </div>
<div className="mt-2 text-xs text-red-500">
<div className="text-sm font-medium text-foreground"> </div>
<div className="mt-1 text-xs text-muted-foreground"> </div>
<div className="mt-2 text-xs text-destructive">
디버그: config={JSON.stringify(config)}
</div>
</div>

View File

@@ -51,7 +51,7 @@ export function YardWidgetConfigModal({ element, isOpen, onClose, onSave }: Yard
onChange={(e) => setCustomTitle(e.target.value)}
placeholder="제목을 입력하세요 (비워두면 기본 제목 사용)"
/>
<p className="text-xs text-gray-500"> 제목: 야드 3D</p>
<p className="text-xs text-muted-foreground"> 제목: 야드 3D</p>
</div>
{/* 헤더 표시 여부 */}

View File

@@ -38,23 +38,23 @@ export function YardWidgetConfigSidebar({ element, isOpen, onClose, onApply }: Y
return (
<div
className={cn(
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-gray-50 transition-transform duration-300 ease-in-out",
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-muted transition-transform duration-300 ease-in-out",
isOpen ? "translate-x-0" : "translate-x-[-100%]",
)}
>
{/* 헤더 */}
<div className="flex items-center justify-between bg-white px-3 py-2 shadow-sm">
<div className="flex items-center justify-between bg-background px-3 py-2 shadow-sm">
<div className="flex items-center gap-2">
<div className="bg-primary/10 flex h-6 w-6 items-center justify-center rounded">
<span className="text-primary text-xs font-bold">🏗</span>
</div>
<span className="text-xs font-semibold text-gray-900"> </span>
<span className="text-xs font-semibold text-foreground"> </span>
</div>
<button
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-gray-100"
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-muted"
>
<X className="h-3.5 w-3.5 text-gray-500" />
<X className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
@@ -62,8 +62,8 @@ export function YardWidgetConfigSidebar({ element, isOpen, onClose, onApply }: Y
<div className="flex-1 overflow-y-auto p-3">
<div className="space-y-3">
{/* 위젯 제목 */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<Input
value={customTitle}
onChange={(e) => setCustomTitle(e.target.value)}
@@ -71,12 +71,12 @@ export function YardWidgetConfigSidebar({ element, isOpen, onClose, onApply }: Y
className="h-8 text-xs"
style={{ fontSize: "12px" }}
/>
<p className="mt-1 text-[10px] text-gray-500"> 제목: 야드 3D</p>
<p className="mt-1 text-[10px] text-muted-foreground"> 제목: 야드 3D</p>
</div>
{/* 헤더 표시 */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<RadioGroup
value={showHeader ? "show" : "hide"}
onValueChange={(value) => setShowHeader(value === "show")}
@@ -100,10 +100,10 @@ export function YardWidgetConfigSidebar({ element, isOpen, onClose, onApply }: Y
</div>
{/* 푸터 */}
<div className="flex gap-2 bg-white p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<div className="flex gap-2 bg-background p-2 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
<button
onClick={onClose}
className="flex-1 rounded bg-gray-100 py-2 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-200"
className="flex-1 rounded bg-muted py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted"
>
</button>

View File

@@ -175,23 +175,23 @@ export default function CustomMetricConfigSidebar({
return (
<div
className={cn(
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-gray-50 transition-transform duration-300 ease-in-out",
"fixed top-14 left-0 z-[100] flex h-[calc(100vh-3.5rem)] w-80 flex-col bg-muted transition-transform duration-300 ease-in-out",
isOpen ? "translate-x-0" : "translate-x-[-100%]",
)}
>
{/* 헤더 */}
<div className="flex items-center justify-between bg-white px-3 py-2 shadow-sm">
<div className="flex items-center justify-between bg-background px-3 py-2 shadow-sm">
<div className="flex items-center gap-2">
<div className="bg-primary/10 flex h-6 w-6 items-center justify-center rounded">
<span className="text-primary text-xs font-bold">📊</span>
</div>
<span className="text-xs font-semibold text-gray-900"> </span>
<span className="text-xs font-semibold text-foreground"> </span>
</div>
<button
onClick={onClose}
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-gray-100"
className="flex h-6 w-6 items-center justify-center rounded transition-colors hover:bg-muted"
>
<X className="h-3.5 w-3.5 text-gray-500" />
<X className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
@@ -199,12 +199,12 @@ export default function CustomMetricConfigSidebar({
<div className="flex-1 overflow-y-auto p-3">
<div className="space-y-3">
{/* 헤더 설정 */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="space-y-2">
{/* 제목 입력 */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"></label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"></label>
<Input
value={customTitle}
onChange={(e) => setCustomTitle(e.target.value)}
@@ -216,15 +216,15 @@ export default function CustomMetricConfigSidebar({
{/* 헤더 표시 여부 */}
<div className="flex items-center justify-between">
<label className="text-[9px] font-medium text-gray-500"> </label>
<label className="text-[9px] font-medium text-muted-foreground"> </label>
<button
onClick={() => setShowHeader(!showHeader)}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
showHeader ? "bg-primary" : "bg-gray-300"
showHeader ? "bg-primary" : "bg-muted"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
className={`inline-block h-4 w-4 transform rounded-full bg-background transition-transform ${
showHeader ? "translate-x-5" : "translate-x-0.5"
}`}
/>
@@ -234,15 +234,15 @@ export default function CustomMetricConfigSidebar({
</div>
{/* 데이터 소스 타입 선택 */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => handleDataSourceTypeChange("database")}
className={`flex h-16 items-center justify-center rounded border transition-all ${
dataSourceType === "database"
? "border-primary bg-primary/5 text-primary"
: "border-gray-200 bg-gray-50 text-gray-600 hover:border-gray-300"
: "border-border bg-muted text-foreground hover:border-border"
}`}
>
<span className="text-sm font-medium"></span>
@@ -252,7 +252,7 @@ export default function CustomMetricConfigSidebar({
className={`flex h-16 items-center justify-center rounded border transition-all ${
dataSourceType === "api"
? "border-primary bg-primary/5 text-primary"
: "border-gray-200 bg-gray-50 text-gray-600 hover:border-gray-300"
: "border-border bg-muted text-foreground hover:border-border"
}`}
>
<span className="text-sm font-medium">REST API</span>
@@ -275,9 +275,9 @@ export default function CustomMetricConfigSidebar({
)}
{/* 일반 지표 설정 (항상 표시) */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-3 flex items-center justify-between">
<div className="text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
{queryColumns.length > 0 && (
<Button size="sm" variant="outline" className="h-7 gap-1 text-xs" onClick={addMetric}>
<Plus className="h-3 w-3" />
@@ -287,11 +287,11 @@ export default function CustomMetricConfigSidebar({
</div>
{queryColumns.length === 0 ? (
<p className="text-xs text-gray-500"> </p>
<p className="text-xs text-muted-foreground"> </p>
) : (
<div className="space-y-2">
{metrics.length === 0 ? (
<p className="text-xs text-gray-500"> </p>
<p className="text-xs text-muted-foreground"> </p>
) : (
metrics.map((metric, index) => (
<div
@@ -299,7 +299,7 @@ export default function CustomMetricConfigSidebar({
onDragOver={(e) => handleDragOver(e, index)}
onDrop={(e) => handleDrop(e, index)}
className={cn(
"rounded-md border bg-white p-2 transition-all",
"rounded-md border bg-background p-2 transition-all",
draggedIndex === index && "opacity-50",
dragOverIndex === index && draggedIndex !== index && "border-primary border-2",
)}
@@ -312,21 +312,21 @@ export default function CustomMetricConfigSidebar({
onDragEnd={handleDragEnd}
className="cursor-grab active:cursor-grabbing"
>
<GripVertical className="h-4 w-4 shrink-0 text-gray-400" />
<GripVertical className="h-4 w-4 shrink-0 text-muted-foreground" />
</div>
<div className="grid min-w-0 flex-1 grid-cols-[1fr,auto,auto] items-center gap-2">
<span className="truncate text-xs font-medium text-gray-900">
<span className="truncate text-xs font-medium text-foreground">
{metric.label || "새 지표"}
</span>
<span className="text-[10px] text-gray-500">{metric.aggregation.toUpperCase()}</span>
<span className="text-[10px] text-muted-foreground">{metric.aggregation.toUpperCase()}</span>
<button
onClick={() => setExpandedMetric(expandedMetric === metric.id ? null : metric.id)}
className="flex items-center justify-center rounded p-0.5 hover:bg-gray-100"
className="flex items-center justify-center rounded p-0.5 hover:bg-muted"
>
{expandedMetric === metric.id ? (
<ChevronUp className="h-3.5 w-3.5 text-gray-500" />
<ChevronUp className="h-3.5 w-3.5 text-muted-foreground" />
) : (
<ChevronDown className="h-3.5 w-3.5 text-gray-500" />
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
)}
</button>
</div>
@@ -334,12 +334,12 @@ export default function CustomMetricConfigSidebar({
{/* 설정 영역 */}
{expandedMetric === metric.id && (
<div className="mt-2 space-y-1.5 border-t border-gray-200 pt-2">
<div className="mt-2 space-y-1.5 border-t border-border pt-2">
{/* 2열 그리드 레이아웃 */}
<div className="grid grid-cols-2 gap-1.5">
{/* 컬럼 */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"></label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"></label>
<Select
value={metric.field}
onValueChange={(value) => updateMetric(metric.id, "field", value)}
@@ -359,7 +359,7 @@ export default function CustomMetricConfigSidebar({
{/* 집계 함수 */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"></label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"></label>
<Select
value={metric.aggregation}
onValueChange={(value: any) => updateMetric(metric.id, "aggregation", value)}
@@ -379,7 +379,7 @@ export default function CustomMetricConfigSidebar({
{/* 단위 */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"></label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"></label>
<Input
value={metric.unit}
onChange={(e) => updateMetric(metric.id, "unit", e.target.value)}
@@ -390,7 +390,7 @@ export default function CustomMetricConfigSidebar({
{/* 소수점 */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"></label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"></label>
<Select
value={String(metric.decimals)}
onValueChange={(value) => updateMetric(metric.id, "decimals", parseInt(value))}
@@ -411,7 +411,7 @@ export default function CustomMetricConfigSidebar({
{/* 표시 이름 (전체 너비) */}
<div>
<label className="mb-0.5 block text-[9px] font-medium text-gray-500"> </label>
<label className="mb-0.5 block text-[9px] font-medium text-muted-foreground"> </label>
<Input
value={metric.label}
onChange={(e) => updateMetric(metric.id, "label", e.target.value)}
@@ -421,7 +421,7 @@ export default function CustomMetricConfigSidebar({
</div>
{/* 삭제 버튼 */}
<div className="border-t border-gray-200 pt-1.5">
<div className="border-t border-border pt-1.5">
<Button
size="sm"
variant="ghost"
@@ -442,13 +442,13 @@ export default function CustomMetricConfigSidebar({
</div>
{/* 그룹별 카드 생성 모드 (항상 표시) */}
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase"> </div>
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase"> </div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<label className="text-xs font-medium text-gray-900"> </label>
<p className="mt-0.5 text-[9px] text-gray-500">
<label className="text-xs font-medium text-foreground"> </label>
<p className="mt-0.5 text-[9px] text-muted-foreground">
</p>
</div>
@@ -461,18 +461,18 @@ export default function CustomMetricConfigSidebar({
}
}}
className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors ${
groupByMode ? "bg-primary" : "bg-gray-300"
groupByMode ? "bg-primary" : "bg-muted"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
className={`inline-block h-4 w-4 transform rounded-full bg-background transition-transform ${
groupByMode ? "translate-x-5" : "translate-x-0.5"
}`}
/>
</button>
</div>
{groupByMode && (
<div className="rounded-md bg-blue-50 p-2 text-[9px] text-blue-700">
<div className="rounded-md bg-primary/10 p-2 text-[9px] text-primary">
<p className="font-medium">💡 </p>
<ul className="mt-1 space-y-0.5 pl-3 text-[8px]">
<li> 컬럼: 카드 </li>
@@ -487,8 +487,8 @@ export default function CustomMetricConfigSidebar({
{/* 그룹별 카드 전용 쿼리 (활성화 시에만 표시) */}
{groupByMode && groupByDataSource && (
<div className="rounded-lg bg-white p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-gray-500 uppercase">
<div className="rounded-lg bg-background p-3 shadow-sm">
<div className="mb-2 text-[10px] font-semibold tracking-wide text-muted-foreground uppercase">
</div>
<DatabaseConfig dataSource={groupByDataSource} onChange={handleGroupByDataSourceUpdate} />
@@ -503,7 +503,7 @@ export default function CustomMetricConfigSidebar({
</div>
{/* 푸터 */}
<div className="flex gap-2 border-t bg-white p-3 shadow-sm">
<div className="flex gap-2 border-t bg-background p-3 shadow-sm">
<Button variant="outline" className="h-8 flex-1 text-xs" onClick={onClose}>
</Button>

View File

@@ -7,38 +7,38 @@ export function getStatusColor(status: DriverInfo["status"]) {
switch (status) {
case "driving":
return {
bg: "bg-green-100",
text: "text-green-800",
border: "border-green-300",
badge: "bg-green-500",
bg: "bg-success/10",
text: "text-success",
border: "border-success",
badge: "bg-success",
};
case "standby":
return {
bg: "bg-gray-100",
text: "text-gray-800",
border: "border-gray-300",
badge: "bg-gray-500",
bg: "bg-muted",
text: "text-foreground",
border: "border-border",
badge: "bg-muted0",
};
case "resting":
return {
bg: "bg-orange-100",
text: "text-orange-800",
border: "border-orange-300",
badge: "bg-orange-500",
bg: "bg-warning/10",
text: "text-warning",
border: "border-warning",
badge: "bg-warning",
};
case "maintenance":
return {
bg: "bg-red-100",
text: "text-red-800",
border: "border-red-300",
badge: "bg-red-500",
bg: "bg-destructive/10",
text: "text-destructive",
border: "border-destructive",
badge: "bg-destructive",
};
default:
return {
bg: "bg-gray-100",
text: "text-gray-800",
border: "border-gray-300",
badge: "bg-gray-500",
bg: "bg-muted",
text: "text-foreground",
border: "border-border",
badge: "bg-muted0",
};
}
}

View File

@@ -123,7 +123,7 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
className={`group relative rounded-md border transition-all ${
isSelected
? "border-primary/40 bg-primary/5 shadow-sm"
: "border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"
: "border-border bg-background hover:border-border hover:shadow-sm"
} ${isDraggable ? "cursor-grab active:cursor-grabbing" : ""} ${
draggedIndex === columnIndex ? "scale-95 opacity-50" : ""
}`}
@@ -137,20 +137,20 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
/>
<GripVertical
className={`h-3.5 w-3.5 shrink-0 transition-colors ${
isDraggable ? "group-hover:text-primary text-gray-400" : "text-gray-300"
isDraggable ? "group-hover:text-primary text-muted-foreground" : "text-muted-foreground"
}`}
/>
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-1.5">
<span className="truncate text-[11px] font-medium text-gray-900">{field}</span>
{previewText && <span className="shrink-0 text-[9px] text-gray-400">: {previewText}</span>}
<span className="truncate text-[11px] font-medium text-foreground">{field}</span>
{previewText && <span className="shrink-0 text-[9px] text-muted-foreground">: {previewText}</span>}
</div>
</div>
</div>
{/* 설정 영역 */}
{isSelected && selectedCol && (
<div className="border-t border-gray-100 bg-gray-50/50 px-2.5 py-1.5">
<div className="border-t border-border bg-muted/50 px-2.5 py-1.5">
<div className="grid grid-cols-2 gap-1.5">
{/* 표시 이름 */}
<div className="min-w-0">
@@ -158,7 +158,7 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
value={selectedCol.label}
onChange={(e) => handleLabelChange(field, e.target.value)}
placeholder="표시 이름"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] placeholder:text-gray-400 focus:ring-1"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] placeholder:text-muted-foreground focus:ring-1"
style={{ fontSize: "10px" }}
/>
</div>
@@ -170,7 +170,7 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
onValueChange={(value: "left" | "center" | "right") => handleAlignChange(field, value)}
>
<SelectTrigger
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
style={{ fontSize: "10px", height: "24px", minHeight: "24px" }}
>
<SelectValue />
@@ -210,7 +210,7 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
return (
<div
key={field}
className="group rounded-md border border-gray-200 bg-white transition-all hover:border-gray-300 hover:shadow-sm"
className="group rounded-md border border-border bg-background transition-all hover:border-border hover:shadow-sm"
>
<div className="flex items-center gap-2 px-2.5 py-2">
<Checkbox
@@ -218,11 +218,11 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
onCheckedChange={() => handleToggle(field)}
className="h-3.5 w-3.5 shrink-0"
/>
<GripVertical className="h-3.5 w-3.5 shrink-0 text-gray-300" />
<GripVertical className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-1.5">
<span className="truncate text-[11px] font-medium text-gray-600">{field}</span>
{previewText && <span className="shrink-0 text-[9px] text-gray-400">: {previewText}</span>}
<span className="truncate text-[11px] font-medium text-foreground">{field}</span>
{previewText && <span className="shrink-0 text-[9px] text-muted-foreground">: {previewText}</span>}
</div>
</div>
</div>
@@ -232,9 +232,9 @@ export function ColumnSelector({ queryResult, config, onConfigChange }: ColumnSe
</div>
{selectedColumns.length === 0 && (
<div className="mt-3 flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50/50 px-3 py-2">
<span className="text-amber-600"></span>
<span className="text-[10px] text-amber-700"> 1 </span>
<div className="mt-3 flex items-center gap-2 rounded-md border border-warning bg-warning/10 px-3 py-2">
<span className="text-warning"></span>
<span className="text-[10px] text-warning"> 1 </span>
</div>
)}
</div>

View File

@@ -22,7 +22,7 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
<div className="space-y-3">
{/* 뷰 모드 */}
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"> </Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"> </Label>
<RadioGroup
value={config.viewMode}
onValueChange={(value: "table" | "card") => onConfigChange({ viewMode: value })}
@@ -46,7 +46,7 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
{/* 카드 뷰 컬럼 수 */}
{config.viewMode === "card" && (
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"> </Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"> </Label>
<Input
type="number"
min="1"
@@ -55,13 +55,13 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
onChange={(e) => onConfigChange({ cardColumns: parseInt(e.target.value) || 3 })}
className="h-6 w-full px-1.5 text-[11px]"
/>
<p className="mt-0.5 text-[9px] text-gray-500"> (1-6)</p>
<p className="mt-0.5 text-[9px] text-muted-foreground"> (1-6)</p>
</div>
)}
{/* 페이지 크기 */}
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"> </Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"> </Label>
<Select
value={String(config.pageSize)}
onValueChange={(value) => onConfigChange({ pageSize: parseInt(value) })}
@@ -91,7 +91,7 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
{/* 기능 활성화 */}
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"></Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"></Label>
<RadioGroup
value={config.enablePagination ? "enabled" : "disabled"}
onValueChange={(value) => onConfigChange({ enablePagination: value === "enabled" })}
@@ -115,7 +115,7 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
{/* 헤더 표시 */}
{config.viewMode === "table" && (
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"> </Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"> </Label>
<RadioGroup
value={config.showHeader ? "show" : "hide"}
onValueChange={(value) => onConfigChange({ showHeader: value === "show" })}
@@ -140,7 +140,7 @@ export function ListTableOptions({ config, onConfigChange }: ListTableOptionsPro
{/* 줄무늬 행 */}
{config.viewMode === "table" && (
<div>
<Label className="mb-1 block text-[9px] font-medium text-gray-600"> </Label>
<Label className="mb-1 block text-[9px] font-medium text-foreground"> </Label>
<RadioGroup
value={config.stripedRows ? "enabled" : "disabled"}
onValueChange={(value) => onConfigChange({ stripedRows: value === "enabled" })}

View File

@@ -77,7 +77,7 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
<div>
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<p className="text-[10px] text-gray-500"> </p>
<p className="text-[10px] text-muted-foreground"> </p>
<button
onClick={handleAddColumn}
className="bg-primary hover:bg-primary/90 flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium text-white transition-colors"
@@ -102,24 +102,24 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
handleDragEnd();
e.currentTarget.style.cursor = "grab";
}}
className={`group relative rounded-md border border-gray-200 bg-white shadow-sm transition-all hover:border-gray-300 hover:shadow-sm ${
className={`group relative rounded-md border border-border bg-background shadow-sm transition-all hover:border-border hover:shadow-sm ${
draggedIndex === index ? "scale-95 opacity-50" : ""
} cursor-grab active:cursor-grabbing`}
>
{/* 헤더 */}
<div className="flex items-center gap-2 px-2.5 py-2">
<GripVertical className="group-hover:text-primary h-3.5 w-3.5 shrink-0 text-gray-400 transition-colors" />
<span className="text-[11px] font-medium text-gray-900"> {index + 1}</span>
<GripVertical className="group-hover:text-primary h-3.5 w-3.5 shrink-0 text-muted-foreground transition-colors" />
<span className="text-[11px] font-medium text-foreground"> {index + 1}</span>
<button
onClick={() => handleRemove(col.id)}
className="ml-auto flex h-5 w-5 items-center justify-center rounded text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600"
className="ml-auto flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 className="h-3 w-3" />
</button>
</div>
{/* 설정 영역 */}
<div className="border-t border-gray-100 bg-gray-50/50 px-2.5 py-1.5">
<div className="border-t border-border bg-muted/50 px-2.5 py-1.5">
<div className="flex flex-col gap-1.5">
{/* 표시 이름 */}
<div className="min-w-0">
@@ -127,7 +127,7 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
value={col.label}
onChange={(e) => handleUpdate(col.id, { label: e.target.value })}
placeholder="표시 이름"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] placeholder:text-gray-400 focus:ring-1"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] placeholder:text-muted-foreground focus:ring-1"
style={{ fontSize: "10px" }}
/>
</div>
@@ -138,7 +138,7 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
value={col.field}
onChange={(e) => handleUpdate(col.id, { field: e.target.value })}
placeholder="데이터 필드"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] placeholder:text-gray-400 focus:ring-1"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] placeholder:text-muted-foreground focus:ring-1"
style={{ fontSize: "10px" }}
/>
</div>
@@ -150,7 +150,7 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
onValueChange={(value: "left" | "center" | "right") => handleUpdate(col.id, { align: value })}
>
<SelectTrigger
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
style={{ fontSize: "10px", height: "24px", minHeight: "24px" }}
>
<SelectValue />
@@ -175,9 +175,9 @@ export function ManualColumnEditor({ config, onConfigChange }: ManualColumnEdito
</div>
{columns.length === 0 && (
<div className="mt-3 flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50/50 px-3 py-2">
<span className="text-amber-600"></span>
<span className="text-[10px] text-amber-700"> </span>
<div className="mt-3 flex items-center gap-2 rounded-md border border-warning bg-warning/10 px-3 py-2">
<span className="text-warning"></span>
<span className="text-[10px] text-warning"> </span>
<button
onClick={handleAddColumn}
className="bg-primary hover:bg-primary/90 ml-auto flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium text-white transition-colors"

View File

@@ -96,7 +96,7 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
<div>
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<p className="text-[10px] text-gray-500"> </p>
<p className="text-[10px] text-muted-foreground"> </p>
<button
onClick={handleAddColumn}
className="bg-primary hover:bg-primary/90 flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-medium text-white transition-colors"
@@ -124,7 +124,7 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
className={`group relative rounded-md border transition-all ${
col.visible
? "border-primary/40 bg-primary/5 shadow-sm"
: "border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"
: "border-border bg-background hover:border-border hover:shadow-sm"
} ${draggedIndex === index ? "scale-95 opacity-50" : ""}`}
>
{/* 헤더 */}
@@ -146,19 +146,19 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
}}
className="cursor-grab active:cursor-grabbing"
>
<GripVertical className="group-hover:text-primary h-3.5 w-3.5 shrink-0 text-gray-400 transition-colors" />
<GripVertical className="group-hover:text-primary h-3.5 w-3.5 shrink-0 text-muted-foreground transition-colors" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<span className="truncate text-[11px] font-medium text-gray-900">
<span className="truncate text-[11px] font-medium text-foreground">
{col.field || "(필드명 없음)"}
</span>
{previewText && <span className="shrink-0 text-[9px] text-gray-400">: {previewText}</span>}
{previewText && <span className="shrink-0 text-[9px] text-muted-foreground">: {previewText}</span>}
</div>
</div>
<button
onClick={() => handleRemove(col.id)}
className="ml-auto flex h-5 w-5 items-center justify-center rounded text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600"
className="ml-auto flex h-5 w-5 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
>
<Trash2 className="h-3 w-3" />
</button>
@@ -166,7 +166,7 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
{/* 설정 영역 */}
{col.visible && (
<div className="border-t border-gray-100 bg-gray-50/50 px-2.5 py-1.5">
<div className="border-t border-border bg-muted/50 px-2.5 py-1.5">
<div className="grid grid-cols-2 gap-1.5">
{/* 표시 이름 */}
<div className="min-w-0">
@@ -174,7 +174,7 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
value={col.label}
onChange={(e) => handleUpdate(col.id, { label: e.target.value })}
placeholder="표시 이름"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] placeholder:text-gray-400 focus:ring-1"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] placeholder:text-muted-foreground focus:ring-1"
style={{ fontSize: "10px" }}
/>
</div>
@@ -186,7 +186,7 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
onValueChange={(value: "left" | "center" | "right") => handleUpdate(col.id, { align: value })}
>
<SelectTrigger
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-gray-200 bg-white px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
className="focus:border-primary focus:ring-primary/20 h-6 w-full border-border bg-background px-1.5 text-[10px] focus:ring-1 [&>span]:leading-none [&>svg]:h-3 [&>svg]:w-3"
style={{ fontSize: "10px", height: "24px", minHeight: "24px" }}
>
<SelectValue />
@@ -213,9 +213,9 @@ export function UnifiedColumnEditor({ queryResult, config, onConfigChange }: Uni
</div>
{columns.length === 0 && (
<div className="mt-3 flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50/50 px-3 py-2">
<span className="text-amber-600"></span>
<span className="text-[10px] text-amber-700"> </span>
<div className="mt-3 flex items-center gap-2 rounded-md border border-warning bg-warning/10 px-3 py-2">
<span className="text-warning"></span>
<span className="text-[10px] text-warning"> </span>
</div>
)}
</div>

View File

@@ -94,13 +94,13 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
<div className="space-y-4">
{/* 자재 정보 */}
<div className="rounded-lg bg-gray-50 p-4">
<div className="mb-2 text-sm font-medium text-gray-600"> </div>
<div className="rounded-lg bg-muted p-4">
<div className="mb-2 text-sm font-medium text-foreground"> </div>
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded border" style={{ backgroundColor: material.default_color }} />
<div>
<div className="font-medium">{material.material_name}</div>
<div className="text-sm text-gray-600">{material.material_code}</div>
<div className="text-sm text-foreground">{material.material_code}</div>
</div>
</div>
</div>
@@ -117,7 +117,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
min="1"
className="flex-1"
/>
<span className="text-sm text-gray-600">{material.unit}</span>
<span className="text-sm text-foreground">{material.unit}</span>
</div>
</div>
@@ -126,7 +126,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
<Label>3D </Label>
<div className="grid grid-cols-3 gap-2">
<div>
<Label htmlFor="posX" className="text-xs text-gray-600">
<Label htmlFor="posX" className="text-xs text-foreground">
X ()
</Label>
<Input
@@ -138,7 +138,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
/>
</div>
<div>
<Label htmlFor="posY" className="text-xs text-gray-600">
<Label htmlFor="posY" className="text-xs text-foreground">
Y ()
</Label>
<Input
@@ -150,7 +150,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
/>
</div>
<div>
<Label htmlFor="posZ" className="text-xs text-gray-600">
<Label htmlFor="posZ" className="text-xs text-foreground">
Z ()
</Label>
<Input
@@ -169,7 +169,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
<Label>3D </Label>
<div className="grid grid-cols-3 gap-2">
<div>
<Label htmlFor="sizeX" className="text-xs text-gray-600">
<Label htmlFor="sizeX" className="text-xs text-foreground">
</Label>
<Input
@@ -182,7 +182,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
/>
</div>
<div>
<Label htmlFor="sizeY" className="text-xs text-gray-600">
<Label htmlFor="sizeY" className="text-xs text-foreground">
</Label>
<Input
@@ -195,7 +195,7 @@ export default function MaterialAddModal({ isOpen, material, onClose, onAdd }: M
/>
</div>
<div>
<Label htmlFor="sizeZ" className="text-xs text-gray-600">
<Label htmlFor="sizeZ" className="text-xs text-foreground">
</Label>
<Input

View File

@@ -72,7 +72,7 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
};
return (
<div className="w-80 border-l bg-white p-4">
<div className="w-80 border-l bg-background p-4">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-semibold"> </h3>
<Button variant="ghost" size="sm" onClick={onClose}>
@@ -82,18 +82,18 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
<div className="space-y-4">
{/* 읽기 전용 정보 */}
<div className="space-y-3 rounded-lg bg-gray-50 p-3">
<div className="text-xs font-medium text-gray-500"> ( )</div>
<div className="space-y-3 rounded-lg bg-muted p-3">
<div className="text-xs font-medium text-muted-foreground"> ( )</div>
<div>
<div className="text-xs text-gray-600"> </div>
<div className="text-xs text-foreground"> </div>
<div className="mt-1 text-sm font-medium">{placement.material_code}</div>
</div>
<div>
<div className="text-xs text-gray-600"> </div>
<div className="text-xs text-foreground"> </div>
<div className="mt-1 text-sm font-medium">{placement.material_name}</div>
</div>
<div>
<div className="text-xs text-gray-600"></div>
<div className="text-xs text-foreground"></div>
<div className="mt-1 text-sm font-medium">
{placement.quantity} {placement.unit}
</div>
@@ -102,14 +102,14 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
{/* 배치 정보 (편집 가능) */}
<div className="space-y-3">
<div className="text-xs font-medium text-gray-500"> ( )</div>
<div className="text-xs font-medium text-muted-foreground"> ( )</div>
{/* 3D 크기 */}
<div>
<Label className="text-xs"></Label>
<div className="grid grid-cols-3 gap-2">
<div>
<Label htmlFor="edit-sizeX" className="text-xs text-gray-600">
<Label htmlFor="edit-sizeX" className="text-xs text-foreground">
</Label>
<Input
@@ -123,7 +123,7 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
/>
</div>
<div>
<Label htmlFor="edit-sizeY" className="text-xs text-gray-600">
<Label htmlFor="edit-sizeY" className="text-xs text-foreground">
</Label>
<Input
@@ -137,7 +137,7 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
/>
</div>
<div>
<Label htmlFor="edit-sizeZ" className="text-xs text-gray-600">
<Label htmlFor="edit-sizeZ" className="text-xs text-foreground">
</Label>
<Input
@@ -217,7 +217,7 @@ export default function MaterialEditPanel({ placement, onClose, onUpdate, onRemo
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction onClick={handleRemove} className="bg-red-600 hover:bg-red-700">
<AlertDialogAction onClick={handleRemove} className="bg-destructive hover:bg-destructive/90">
</AlertDialogAction>
</AlertDialogFooter>

View File

@@ -94,7 +94,7 @@ export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialL
{/* 검색 및 필터 */}
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="자재 코드 또는 이름 검색..."
value={searchText}
@@ -105,7 +105,7 @@ export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialL
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
className="rounded-md border border-border px-3 py-2 text-sm"
>
<option value=""> </option>
{categories.map((category) => (
@@ -119,10 +119,10 @@ export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialL
{/* 자재 목록 */}
{isLoading ? (
<div className="flex h-64 items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : materials.length === 0 ? (
<div className="flex h-64 items-center justify-center text-gray-500">
<div className="flex h-64 items-center justify-center text-muted-foreground">
{searchText || selectedCategory ? "검색 결과가 없습니다" : "등록된 자재가 없습니다"}
</div>
) : (
@@ -142,7 +142,7 @@ export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialL
<TableRow
key={material.id}
className={`cursor-pointer ${
selectedMaterial?.id === material.id ? "bg-blue-50" : "hover:bg-gray-50"
selectedMaterial?.id === material.id ? "bg-primary/10" : "hover:bg-muted"
}`}
onClick={() => setSelectedMaterial(material)}
>
@@ -162,17 +162,17 @@ export default function MaterialLibrary({ isOpen, onClose, onSelect }: MaterialL
{/* 선택된 자재 정보 */}
{selectedMaterial && (
<div className="rounded-lg bg-blue-50 p-4">
<div className="mb-2 text-sm font-medium text-blue-900"> </div>
<div className="rounded-lg bg-primary/10 p-4">
<div className="mb-2 text-sm font-medium text-primary"> </div>
<div className="flex items-center gap-4">
<div className="h-10 w-10 rounded border" style={{ backgroundColor: selectedMaterial.default_color }} />
<div className="flex-1">
<div className="font-medium">{selectedMaterial.material_name}</div>
<div className="text-sm text-gray-600">{selectedMaterial.material_code}</div>
<div className="text-sm text-foreground">{selectedMaterial.material_code}</div>
</div>
</div>
{selectedMaterial.description && (
<div className="mt-2 text-sm text-gray-600">{selectedMaterial.description}</div>
<div className="mt-2 text-sm text-foreground">{selectedMaterial.description}</div>
)}
</div>
)}

View File

@@ -90,10 +90,10 @@ export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
if (isLoading) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="flex h-full w-full items-center justify-center bg-muted">
<div className="text-center">
<Loader2 className="mx-auto h-8 w-8 animate-spin text-blue-600" />
<div className="mt-2 text-sm text-gray-600">3D ...</div>
<Loader2 className="mx-auto h-8 w-8 animate-spin text-primary" />
<div className="mt-2 text-sm text-foreground">3D ...</div>
</div>
</div>
);
@@ -101,10 +101,10 @@ export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
if (error) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="flex h-full w-full items-center justify-center bg-muted">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600">{error}</div>
<div className="text-sm font-medium text-foreground">{error}</div>
</div>
</div>
);
@@ -112,10 +112,10 @@ export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
if (placements.length === 0) {
return (
<div className="flex h-full w-full items-center justify-center bg-gray-50">
<div className="flex h-full w-full items-center justify-center bg-muted">
<div className="text-center">
<div className="mb-2 text-4xl">📦</div>
<div className="text-sm font-medium text-gray-600"> </div>
<div className="text-sm font-medium text-foreground"> </div>
</div>
</div>
);
@@ -132,23 +132,23 @@ export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
{/* 야드 이름 (좌측 상단) */}
{layoutName && (
<div className="absolute top-4 left-4 z-49 rounded-lg border border-gray-300 bg-white px-4 py-2 shadow-lg">
<h2 className="text-base font-bold text-gray-900">{layoutName}</h2>
<div className="absolute top-4 left-4 z-49 rounded-lg border border-border bg-background px-4 py-2 shadow-lg">
<h2 className="text-base font-bold text-foreground">{layoutName}</h2>
</div>
)}
{/* 선택된 자재 정보 패널 (우측 상단) */}
{selectedPlacement && (
<div className="absolute top-4 right-4 z-50 w-64 rounded-lg border border-gray-300 bg-white p-4 shadow-xl">
<div className="absolute top-4 right-4 z-50 w-64 rounded-lg border border-border bg-background p-4 shadow-xl">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-sm font-semibold text-gray-800">
<h3 className="text-sm font-semibold text-foreground">
{selectedPlacement.material_name ? "자재 정보" : "미설정 요소"}
</h3>
<button
onClick={() => {
setSelectedPlacement(null);
}}
className="rounded-full p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
className="rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
>
</button>
@@ -157,23 +157,23 @@ export default function Yard3DViewer({ layoutId }: Yard3DViewerProps) {
{selectedPlacement.material_name && selectedPlacement.quantity && selectedPlacement.unit ? (
<div className="space-y-2">
<div>
<label className="text-xs font-medium text-gray-500"></label>
<div className="mt-1 text-sm font-semibold text-gray-900">{selectedPlacement.material_name}</div>
<label className="text-xs font-medium text-muted-foreground"></label>
<div className="mt-1 text-sm font-semibold text-foreground">{selectedPlacement.material_name}</div>
</div>
<div>
<label className="text-xs font-medium text-gray-500"></label>
<div className="mt-1 text-sm font-semibold text-gray-900">
<label className="text-xs font-medium text-muted-foreground"></label>
<div className="mt-1 text-sm font-semibold text-foreground">
{selectedPlacement.quantity} {selectedPlacement.unit}
</div>
</div>
</div>
) : (
<div className="rounded-lg bg-orange-50 p-3 text-center">
<div className="rounded-lg bg-warning/10 p-3 text-center">
<div className="mb-2 text-2xl"></div>
<div className="text-sm font-medium text-orange-700"> </div>
<div className="text-sm font-medium text-orange-700"> </div>
<div className="mt-2 text-xs text-orange-600"> </div>
<div className="text-sm font-medium text-warning"> </div>
<div className="text-sm font-medium text-warning"> </div>
<div className="mt-2 text-xs text-warning"> </div>
</div>
)}
</div>

View File

@@ -17,7 +17,7 @@ const Yard3DCanvas = dynamic(() => import("./Yard3DCanvas"), {
ssr: false,
loading: () => (
<div className="flex h-full items-center justify-center bg-gray-900">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
),
});
@@ -465,7 +465,7 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
};
return (
<div className="flex h-full flex-col bg-white">
<div className="flex h-full flex-col bg-background">
{/* 상단 툴바 */}
<div className="flex items-center justify-between border-b p-4">
<div className="flex items-center gap-4">
@@ -476,16 +476,16 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<div className="flex items-center gap-2">
<div>
<h2 className="text-lg font-semibold">{layout.name}</h2>
{layout.description && <p className="text-sm text-gray-500">{layout.description}</p>}
{layout.description && <p className="text-sm text-muted-foreground">{layout.description}</p>}
</div>
<Button variant="ghost" size="sm" onClick={handleEditLayout} className="h-8 w-8 p-0">
<Edit2 className="h-4 w-4 text-gray-500" />
<Edit2 className="h-4 w-4 text-muted-foreground" />
</Button>
</div>
</div>
<div className="flex items-center gap-2">
{hasUnsavedChanges && <span className="text-sm font-medium text-orange-600"> </span>}
{hasUnsavedChanges && <span className="text-sm font-medium text-warning"> </span>}
<Button size="sm" onClick={handleSave} disabled={isSaving || !hasUnsavedChanges}>
{isSaving ? (
<>
@@ -515,8 +515,8 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
{/* 좌측: 3D 캔버스 */}
<div className="flex-1">
{isLoading ? (
<div className="flex h-full items-center justify-center bg-gray-50">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
<div className="flex h-full items-center justify-center bg-muted">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : (
<Yard3DCanvas
@@ -537,7 +537,7 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
</div>
{/* 우측: 요소 목록 또는 설정 패널 */}
<div className="w-80 border-l bg-white">
<div className="w-80 border-l bg-background">
{showConfigPanel && selectedPlacement ? (
// 설정 패널
<YardElementConfigPanel
@@ -556,12 +556,12 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
</Button>
</div>
<p className="text-xs text-gray-500"> {placements.length}</p>
<p className="text-xs text-muted-foreground"> {placements.length}</p>
</div>
<div className="flex-1 overflow-auto p-2">
{placements.length === 0 ? (
<div className="flex h-full items-center justify-center p-4 text-center text-sm text-gray-500">
<div className="flex h-full items-center justify-center p-4 text-center text-sm text-muted-foreground">
.
<br />
{'위의 "요소 추가" 버튼을 클릭하세요.'}
@@ -577,10 +577,10 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
key={placement.id}
className={`rounded-lg border p-3 transition-all ${
isSelected
? "border-blue-500 bg-blue-50"
? "border-primary bg-primary/10"
: configured
? "border-gray-200 bg-white hover:border-gray-300"
: "border-orange-200 bg-orange-50"
? "border-border bg-background hover:border-border"
: "border-warning bg-warning/10"
}`}
onClick={() => handleSelectPlacement(placement)}
>
@@ -588,15 +588,15 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<div className="flex-1">
{configured ? (
<>
<div className="text-sm font-medium text-gray-900">{placement.material_name}</div>
<div className="mt-1 text-xs text-gray-500">
<div className="text-sm font-medium text-foreground">{placement.material_name}</div>
<div className="mt-1 text-xs text-muted-foreground">
: {placement.quantity} {placement.unit}
</div>
</>
) : (
<>
<div className="text-sm font-medium text-orange-700"> #{placement.id}</div>
<div className="mt-1 text-xs text-orange-600"> </div>
<div className="text-sm font-medium text-warning"> #{placement.id}</div>
<div className="mt-1 text-xs text-warning"> </div>
</>
)}
</div>
@@ -618,7 +618,7 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<Button
variant="outline"
size="sm"
className="text-red-600 hover:bg-red-50"
className="text-destructive hover:bg-destructive/10"
onClick={(e) => {
e.stopPropagation();
handleDeletePlacement(placement.id);
@@ -645,12 +645,12 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<DialogTitle className="flex items-center gap-2">
{saveResultDialog.success ? (
<>
<CheckCircle className="h-5 w-5 text-green-600" />
<CheckCircle className="h-5 w-5 text-success" />
</>
) : (
<>
<AlertCircle className="h-5 w-5 text-red-600" />
<AlertCircle className="h-5 w-5 text-destructive" />
</>
)}
@@ -671,20 +671,20 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<DialogContent onPointerDown={(e) => e.stopPropagation()}>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertCircle className="h-5 w-5 text-orange-600" />
<AlertCircle className="h-5 w-5 text-warning" />
</DialogTitle>
<DialogDescription className="pt-2">
?
<br />
<span className="font-semibold text-orange-600"> .</span>
<span className="font-semibold text-warning"> .</span>
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setDeleteConfirmDialog({ open: false, placementId: null })}>
</Button>
<Button onClick={confirmDeletePlacement} className="bg-red-600 hover:bg-red-700">
<Button onClick={confirmDeletePlacement} className="bg-destructive hover:bg-destructive/90">
</Button>
</div>
@@ -699,7 +699,7 @@ export default function YardEditor({ layout, onBack }: YardEditorProps) {
<DialogContent onPointerDown={(e) => e.stopPropagation()}>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Edit2 className="h-5 w-5 text-blue-600" />
<Edit2 className="h-5 w-5 text-primary" />
</DialogTitle>
</DialogHeader>

View File

@@ -298,7 +298,7 @@ export default function YardElementConfigPanel({ placement, onSave, onCancel }:
<SelectItem key={conn.id} value={String(conn.id)}>
<div className="flex items-center gap-2">
<span className="font-medium">{conn.connection_name}</span>
<span className="text-xs text-gray-500">({conn.db_type.toUpperCase()})</span>
<span className="text-xs text-muted-foreground">({conn.db_type.toUpperCase()})</span>
</div>
</SelectItem>
))}
@@ -367,7 +367,7 @@ export default function YardElementConfigPanel({ placement, onSave, onCancel }:
placeholder="data.items"
className="mt-1"
/>
<p className="mt-1 text-xs text-gray-500">: data.items ( )</p>
<p className="mt-1 text-xs text-muted-foreground">: data.items ( )</p>
</div>
<Button onClick={executeRestApi} disabled={isExecuting} size="sm" className="w-full">
@@ -409,7 +409,7 @@ export default function YardElementConfigPanel({ placement, onSave, onCancel }:
{queryResult.rows.slice(0, 10).map((row, idx) => (
<TableRow
key={idx}
className={selectedRowIndex === idx ? "bg-blue-50" : ""}
className={selectedRowIndex === idx ? "bg-primary/10" : ""}
onClick={() => setSelectedRowIndex(idx)}
>
<TableCell>
@@ -428,7 +428,7 @@ export default function YardElementConfigPanel({ placement, onSave, onCancel }:
</Table>
</div>
{queryResult.rows.length > 10 && (
<p className="mt-2 text-xs text-gray-500">... {queryResult.rows.length - 10} </p>
<p className="mt-2 text-xs text-muted-foreground">... {queryResult.rows.length - 10} </p>
)}
</div>
@@ -468,7 +468,7 @@ export default function YardElementConfigPanel({ placement, onSave, onCancel }:
<div>
<Label className="text-xs"> </Label>
<Input value={unit} onChange={(e) => setUnit(e.target.value)} placeholder="EA" className="mt-1" />
<p className="mt-1 text-xs text-gray-500">: EA, BOX, KG, M, L </p>
<p className="mt-1 text-xs text-muted-foreground">: EA, BOX, KG, M, L </p>
</div>
</div>
</Card>

View File

@@ -74,7 +74,7 @@ export default function YardLayoutCreateModal({ isOpen, onClose, onCreate }: Yar
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="yard-name">
<span className="text-red-500">*</span>
<span className="text-destructive">*</span>
</Label>
<Input
id="yard-name"

View File

@@ -126,7 +126,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
if (isLoading) {
return (
<div className="flex h-full items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
);
}
@@ -136,7 +136,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
{/* 검색 및 정렬 */}
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400" />
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="야드 이름 또는 설명 검색..."
value={searchText}
@@ -147,7 +147,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
<select
value={sortOrder}
onChange={(e) => setSortOrder(e.target.value as "recent" | "name")}
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
className="rounded-md border border-border px-3 py-2 text-sm"
>
<option value="recent"> </option>
<option value="name"></option>
@@ -157,7 +157,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
{/* 테이블 */}
{sortedLayouts.length === 0 ? (
<div className="flex flex-1 items-center justify-center">
<div className="text-center text-gray-500">
<div className="text-center text-muted-foreground">
{searchText ? "검색 결과가 없습니다" : "등록된 야드가 없습니다"}
</div>
</div>
@@ -175,11 +175,11 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
</TableHeader>
<TableBody>
{sortedLayouts.map((layout) => (
<TableRow key={layout.id} className="cursor-pointer hover:bg-gray-50" onClick={() => onSelect(layout)}>
<TableRow key={layout.id} className="cursor-pointer hover:bg-muted" onClick={() => onSelect(layout)}>
<TableCell className="font-medium">{layout.name}</TableCell>
<TableCell className="text-gray-600">{layout.description || "-"}</TableCell>
<TableCell className="text-foreground">{layout.description || "-"}</TableCell>
<TableCell className="text-center">{layout.placement_count}</TableCell>
<TableCell className="text-sm text-gray-500">{formatDate(layout.updated_at)}</TableCell>
<TableCell className="text-sm text-muted-foreground">{formatDate(layout.updated_at)}</TableCell>
<TableCell className="text-center">
<DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
@@ -190,7 +190,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onSelect(layout)}></DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDuplicateClick(layout)}></DropdownMenuItem>
<DropdownMenuItem onClick={() => setDeleteTarget(layout)} className="text-red-600">
<DropdownMenuItem onClick={() => setDeleteTarget(layout)} className="text-destructive">
</DropdownMenuItem>
</DropdownMenuContent>
@@ -204,7 +204,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
)}
{/* 총 개수 */}
<div className="text-sm text-gray-500"> {sortedLayouts.length}</div>
<div className="text-sm text-muted-foreground"> {sortedLayouts.length}</div>
{/* 삭제 확인 모달 */}
<AlertDialog open={!!deleteTarget} onOpenChange={() => setDeleteTarget(null)}>
@@ -222,7 +222,7 @@ export default function YardLayoutList({ layouts, isLoading, onSelect, onDelete,
<AlertDialogAction
onClick={handleDeleteConfirm}
disabled={isDeleting}
className="bg-red-600 hover:bg-red-700"
className="bg-destructive hover:bg-destructive/90"
>
{isDeleting ? (
<>