Merge pull request '샤드시옌으로 쫙 수정' (#164) from lhj into main

Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/164
This commit is contained in:
hjlee 2025-10-29 17:53:35 +09:00
commit 1c1a8633ae
87 changed files with 1493 additions and 1491 deletions

View File

@ -28,6 +28,8 @@
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-success: var(--success);
--color-warning: var(--warning);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
@ -63,6 +65,8 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--success: oklch(0.647 0.176 142.5);
--warning: oklch(0.808 0.171 85.6);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
@ -106,6 +110,8 @@
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--success: oklch(0.697 0.17 142.5);
--warning: oklch(0.808 0.171 85.6);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);

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 ? (
<>

View File

@ -171,7 +171,7 @@ function renderWidget(element: DashboardElement) {
// === 기본 fallback ===
default:
return (
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-gray-400 to-gray-600 p-4 text-white">
<div className="flex h-full w-full items-center justify-center bg-gradient-to-br from-muted to-muted-foreground p-4 text-white">
<div className="text-center">
<div className="mb-2 text-3xl"></div>
<div className="text-sm"> : {element.subtype}</div>
@ -528,11 +528,11 @@ export function DashboardViewer({
// 요소가 없는 경우
if (elements.length === 0) {
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="mb-4 text-6xl">📊</div>
<div className="mb-2 text-xl font-medium text-gray-700"> </div>
<div className="text-sm text-gray-500"> </div>
<div className="mb-2 text-xl font-medium text-foreground"> </div>
<div className="text-sm text-muted-foreground"> </div>
</div>
</div>
);
@ -541,7 +541,7 @@ export function DashboardViewer({
return (
<DashboardProvider>
{/* 데스크톱: 디자이너에서 설정한 위치 그대로 렌더링 (화면에 맞춰 비율 유지) */}
<div className="hidden min-h-screen bg-gray-100 py-8 lg:block" style={{ backgroundColor }}>
<div className="hidden min-h-screen bg-muted py-8 lg:block" style={{ backgroundColor }}>
<div className="mx-auto px-4" style={{ maxWidth: `${canvasConfig.width}px` }}>
{/* 다운로드 버튼 */}
<div className="mb-4 flex justify-end">
@ -584,7 +584,7 @@ export function DashboardViewer({
</div>
{/* 태블릿 이하: 반응형 세로 정렬 */}
<div className="block min-h-screen bg-gray-100 p-4 lg:hidden" style={{ backgroundColor }}>
<div className="block min-h-screen bg-muted p-4 lg:hidden" style={{ backgroundColor }}>
<div className="mx-auto max-w-3xl space-y-4">
{/* 다운로드 버튼 */}
<div className="flex justify-end">
@ -646,16 +646,16 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
// 태블릿 이하: 세로 스택 카드 스타일
return (
<div
className="relative overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm"
className="relative overflow-hidden rounded-lg border border-border bg-background shadow-sm"
style={{ minHeight: "300px" }}
>
{element.showHeader !== false && (
<div className="flex items-center justify-between px-2 py-1">
<h3 className="text-xs font-semibold text-gray-800">{element.customTitle || element.title}</h3>
<h3 className="text-xs font-semibold text-foreground">{element.customTitle || element.title}</h3>
<button
onClick={onRefresh}
disabled={isLoading}
className="text-gray-400 transition-colors hover:text-gray-600 disabled:opacity-50"
className="text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50"
title="새로고침"
>
<svg
@ -677,7 +677,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
<div className={element.showHeader !== false ? "p-2" : "p-2"} style={{ minHeight: "250px" }}>
{!isMounted ? (
<div className="flex h-full w-full items-center justify-center">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent" />
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
) : element.type === "chart" ? (
<ChartRenderer element={element} data={data} width={undefined} height={250} />
@ -686,10 +686,10 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
)}
</div>
{isLoading && (
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-white">
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-background">
<div className="text-center">
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-blue-500 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>
)}
@ -703,7 +703,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
return (
<div
className="absolute overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm"
className="absolute overflow-hidden rounded-lg border border-border bg-background shadow-sm"
style={{
left: `${(element.position.x / canvasWidth) * 100}%`,
top: element.position.y,
@ -713,11 +713,11 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
>
{element.showHeader !== false && (
<div className="flex items-center justify-between px-2 py-1">
<h3 className="text-xs font-semibold text-gray-800">{element.customTitle || element.title}</h3>
<h3 className="text-xs font-semibold text-foreground">{element.customTitle || element.title}</h3>
<button
onClick={onRefresh}
disabled={isLoading}
className="text-gray-400 transition-colors hover:text-gray-600 disabled:opacity-50"
className="text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50"
title="새로고침"
>
<svg
@ -739,7 +739,7 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
<div className={element.showHeader !== false ? "h-[calc(100%-32px)] w-full" : "h-full w-full"}>
{!isMounted ? (
<div className="flex h-full w-full items-center justify-center">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent" />
<div className="h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
) : element.type === "chart" ? (
<ChartRenderer
@ -753,10 +753,10 @@ function ViewerElement({ element, data, isLoading, onRefresh, isMobile, canvasWi
)}
</div>
{isLoading && (
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-white">
<div className="bg-opacity-75 absolute inset-0 flex items-center justify-center bg-background">
<div className="text-center">
<div className="mx-auto mb-2 h-6 w-6 animate-spin rounded-full border-2 border-blue-500 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>
)}

View File

@ -129,10 +129,10 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
const diff = scheduled.getTime() - now.getTime();
const hours = Math.floor(diff / (1000 * 60 * 60));
if (hours < 0) return { text: "⏰ 시간 초과", color: "text-red-600" };
if (hours < 2) return { text: `⏱️ ${hours}시간 후`, color: "text-red-600" };
if (hours < 4) return { text: `⏱️ ${hours}시간 후`, color: "text-orange-600" };
return { text: `📅 ${hours}시간 후`, color: "text-gray-600" };
if (hours < 0) return { text: "⏰ 시간 초과", color: "text-destructive" };
if (hours < 2) return { text: `⏱️ ${hours}시간 후`, color: "text-destructive" };
if (hours < 4) return { text: `⏱️ ${hours}시간 후`, color: "text-warning" };
return { text: `📅 ${hours}시간 후`, color: "text-foreground" };
};
const isNew = (createdAt: string) => {
@ -143,27 +143,27 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-gray-500"> ...</div>
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-rose-50">
<div className="flex h-full flex-col bg-gradient-to-br from-background to-destructive/10">
{/* 신규 알림 배너 */}
{showNotification && newCount > 0 && (
<div className="animate-pulse border-b border-rose-300 bg-rose-100 px-4 py-2 text-center">
<span className="font-bold text-rose-700">🔔 {newCount} !</span>
<div className="animate-pulse border-b border-destructive bg-destructive/10 px-4 py-2 text-center">
<span className="font-bold text-destructive">🔔 {newCount} !</span>
</div>
)}
{/* 헤더 */}
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="border-b border-border bg-background px-4 py-3">
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<h3 className="text-lg font-bold text-gray-800">{element?.customTitle || "예약 요청 알림"}</h3>
<h3 className="text-lg font-bold text-foreground">{element?.customTitle || "예약 요청 알림"}</h3>
{newCount > 0 && (
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-destructive text-xs font-bold text-white">
{newCount}
</span>
)}
@ -183,7 +183,7 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
key={f}
onClick={() => setFilter(f)}
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
filter === f ? "bg-primary text-white" : "bg-muted text-foreground hover:bg-muted"
}`}
>
{f === "pending" ? "대기중" : f === "accepted" ? "수락됨" : "전체"}
@ -195,7 +195,7 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
{/* 예약 리스트 */}
<div className="flex-1 overflow-y-auto p-4">
{bookings.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="flex h-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-4xl">📭</div>
<div> </div>
@ -206,14 +206,14 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
{bookings.map((booking) => (
<div
key={booking.id}
className={`group relative rounded-lg border-2 bg-white p-4 shadow-sm transition-all hover:shadow-md ${
booking.priority === "urgent" ? "border-red-400" : "border-gray-200"
className={`group relative rounded-lg border-2 bg-background p-4 shadow-sm transition-all hover:shadow-md ${
booking.priority === "urgent" ? "border-destructive" : "border-border"
} ${booking.status !== "pending" ? "opacity-60" : ""}`}
>
{/* NEW 뱃지 */}
{isNew(booking.createdAt) && booking.status === "pending" && (
<div className="absolute -right-2 -top-2 animate-bounce">
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-red-500 text-xs font-bold text-white shadow-lg">
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-destructive text-xs font-bold text-white shadow-lg">
🆕
</span>
</div>
@ -221,7 +221,7 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
{/* 우선순위 표시 */}
{booking.priority === "urgent" && (
<div className="mb-2 flex items-center gap-1 text-sm font-bold text-red-600">
<div className="mb-2 flex items-center gap-1 text-sm font-bold text-destructive">
<AlertCircle className="h-4 w-4" />
</div>
@ -233,8 +233,8 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
<div className="mb-1 flex items-center gap-2">
<span className="text-2xl">{getVehicleIcon(booking.vehicleType)}</span>
<div>
<div className="font-bold text-gray-800">{booking.customerName}</div>
<div className="flex items-center gap-1 text-xs text-gray-600">
<div className="font-bold text-foreground">{booking.customerName}</div>
<div className="flex items-center gap-1 text-xs text-foreground">
<Phone className="h-3 w-3" />
{booking.customerPhone}
</div>
@ -245,14 +245,14 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
<div className="flex gap-1">
<button
onClick={() => handleAccept(booking.id)}
className="flex items-center gap-1 rounded bg-green-500 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-green-600"
className="flex items-center gap-1 rounded bg-success px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-success/90"
>
<Check className="h-4 w-4" />
</button>
<button
onClick={() => handleReject(booking.id)}
className="flex items-center gap-1 rounded bg-red-500 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-red-600"
className="flex items-center gap-1 rounded bg-destructive px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-destructive/90"
>
<X className="h-4 w-4" />
@ -260,26 +260,26 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
</div>
)}
{booking.status === "accepted" && (
<span className="rounded bg-green-100 px-2 py-1 text-xs font-medium text-green-700">
<span className="rounded bg-success/10 px-2 py-1 text-xs font-medium text-success">
</span>
)}
</div>
{/* 경로 정보 */}
<div className="mb-3 space-y-2 border-t border-gray-100 pt-3">
<div className="mb-3 space-y-2 border-t border-border pt-3">
<div className="flex items-start gap-2 text-sm">
<MapPin className="mt-0.5 h-4 w-4 flex-shrink-0 text-blue-600" />
<MapPin className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
<div className="flex-1">
<div className="font-medium text-gray-700"></div>
<div className="text-gray-600">{booking.pickupLocation}</div>
<div className="font-medium text-foreground"></div>
<div className="text-foreground">{booking.pickupLocation}</div>
</div>
</div>
<div className="flex items-start gap-2 text-sm">
<MapPin className="mt-0.5 h-4 w-4 flex-shrink-0 text-rose-600" />
<MapPin className="mt-0.5 h-4 w-4 flex-shrink-0 text-destructive" />
<div className="flex-1">
<div className="font-medium text-gray-700"></div>
<div className="text-gray-600">{booking.dropoffLocation}</div>
<div className="font-medium text-foreground"></div>
<div className="text-foreground">{booking.dropoffLocation}</div>
</div>
</div>
</div>
@ -287,8 +287,8 @@ export default function BookingAlertWidget({ element }: BookingAlertWidgetProps)
{/* 상세 정보 */}
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex items-center gap-1">
<Package className="h-3 w-3 text-gray-500" />
<span className="text-gray-600">
<Package className="h-3 w-3 text-muted-foreground" />
<span className="text-foreground">
{booking.cargoType} ({booking.weight}kg)
</span>
</div>

View File

@ -169,55 +169,55 @@ export default function CalculatorWidget({ element, className = '' }: Calculator
}, [display, previousValue, operation, waitingForOperand]);
return (
<div className={`h-full w-full p-3 bg-gradient-to-br from-slate-50 to-gray-100 ${className}`}>
<div className="h-full flex flex-col gap-2">
<div className={`h-full w-full p-2 sm:p-3 bg-background ${className}`}>
<div className="h-full flex flex-col gap-1.5 sm:gap-2">
{/* 제목 */}
<h3 className="text-base font-semibold text-gray-900 text-center">{element?.customTitle || "계산기"}</h3>
<h3 className="text-sm sm:text-base font-semibold text-foreground text-center">{element?.customTitle || "계산기"}</h3>
{/* 디스플레이 */}
<div className="bg-white border-2 border-gray-200 rounded-lg p-4 shadow-inner min-h-[80px]">
<div className="bg-background border-2 border-border rounded-lg p-2 sm:p-4 shadow-inner min-h-[60px] sm:min-h-[80px]">
<div className="text-right h-full flex flex-col justify-center">
<div className="h-4 mb-1">
<div className="h-3 sm:h-4 mb-0.5 sm:mb-1">
{operation && previousValue !== null && (
<div className="text-xs text-gray-400">
<div className="text-[10px] sm:text-xs text-muted-foreground">
{previousValue} {operation}
</div>
)}
</div>
<div className="text-2xl font-bold text-gray-900 truncate">
<div className="text-lg sm:text-2xl font-bold text-foreground truncate">
{display}
</div>
</div>
</div>
{/* 버튼 그리드 */}
<div className="flex-1 grid grid-cols-4 gap-2">
<div className="flex-1 grid grid-cols-4 gap-1 sm:gap-2">
{/* 첫 번째 줄 */}
<Button
variant="outline"
onClick={handleClear}
className="h-full text-red-600 hover:bg-red-50 hover:text-red-700 font-semibold select-none"
className="h-full text-xs sm:text-base text-destructive hover:bg-destructive/10 hover:text-destructive font-semibold select-none"
>
AC
</Button>
<Button
variant="outline"
onClick={handleSign}
className="h-full text-gray-600 hover:bg-gray-100 font-semibold select-none"
className="h-full text-xs sm:text-base text-foreground hover:bg-muted font-semibold select-none"
>
+/-
</Button>
<Button
variant="outline"
onClick={handlePercent}
className="h-full text-gray-600 hover:bg-gray-100 font-semibold select-none"
className="h-full text-xs sm:text-base text-foreground hover:bg-muted font-semibold select-none"
>
%
</Button>
<Button
variant="default"
onClick={() => handleOperation('÷')}
className="h-full bg-blue-500 hover:bg-blue-600 text-white font-semibold select-none"
className="h-full text-xs sm:text-base bg-primary hover:bg-primary/90 text-white font-semibold select-none"
>
÷
</Button>
@ -226,28 +226,28 @@ export default function CalculatorWidget({ element, className = '' }: Calculator
<Button
variant="outline"
onClick={() => handleNumber('7')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
7
</Button>
<Button
variant="outline"
onClick={() => handleNumber('8')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
8
</Button>
<Button
variant="outline"
onClick={() => handleNumber('9')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
9
</Button>
<Button
variant="default"
onClick={() => handleOperation('×')}
className="h-full bg-blue-500 hover:bg-blue-600 text-white font-semibold select-none"
className="h-full text-xs sm:text-base bg-primary hover:bg-primary/90 text-white font-semibold select-none"
>
×
</Button>
@ -256,28 +256,28 @@ export default function CalculatorWidget({ element, className = '' }: Calculator
<Button
variant="outline"
onClick={() => handleNumber('4')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
4
</Button>
<Button
variant="outline"
onClick={() => handleNumber('5')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
5
</Button>
<Button
variant="outline"
onClick={() => handleNumber('6')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
6
</Button>
<Button
variant="default"
onClick={() => handleOperation('-')}
className="h-full bg-blue-500 hover:bg-blue-600 text-white font-semibold select-none"
className="h-full text-xs sm:text-base bg-primary hover:bg-primary/90 text-white font-semibold select-none"
>
-
</Button>
@ -286,28 +286,28 @@ export default function CalculatorWidget({ element, className = '' }: Calculator
<Button
variant="outline"
onClick={() => handleNumber('1')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
1
</Button>
<Button
variant="outline"
onClick={() => handleNumber('2')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
2
</Button>
<Button
variant="outline"
onClick={() => handleNumber('3')}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
3
</Button>
<Button
variant="default"
onClick={() => handleOperation('+')}
className="h-full bg-blue-500 hover:bg-blue-600 text-white font-semibold select-none"
className="h-full text-xs sm:text-base bg-primary hover:bg-primary/90 text-white font-semibold select-none"
>
+
</Button>
@ -316,21 +316,21 @@ export default function CalculatorWidget({ element, className = '' }: Calculator
<Button
variant="outline"
onClick={() => handleNumber('0')}
className="h-full col-span-2 hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full col-span-2 text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
0
</Button>
<Button
variant="outline"
onClick={handleDecimal}
className="h-full hover:bg-gray-100 font-semibold text-lg select-none"
className="h-full text-sm sm:text-lg hover:bg-muted font-semibold select-none"
>
.
</Button>
<Button
variant="default"
onClick={handleEquals}
className="h-full bg-green-500 hover:bg-green-600 text-white font-semibold select-none"
className="h-full text-xs sm:text-base bg-success hover:bg-success/90 text-white font-semibold select-none"
>
=
</Button>

View File

@ -83,11 +83,11 @@ export default function CargoListWidget({ element }: CargoListWidgetProps) {
if (statusLower.includes("배송중") || statusLower.includes("delivering")) {
return "bg-primary text-primary-foreground";
} else if (statusLower.includes("완료") || statusLower.includes("delivered")) {
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100";
return "bg-success/10 text-success dark:bg-success/20 dark:text-success";
} else if (statusLower.includes("지연") || statusLower.includes("delayed")) {
return "bg-destructive text-destructive-foreground";
} else if (statusLower.includes("픽업") || statusLower.includes("pending")) {
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100";
return "bg-warning/10 text-warning dark:bg-warning/20 dark:text-warning";
}
return "bg-muted text-muted-foreground";
};

View File

@ -330,7 +330,7 @@ export default function ChartTestWidget({ element }: ChartTestWidgetProps) {
}, [data, dataSourceConfigs, mergeMode, dataSources]);
return (
<div className="flex h-full w-full flex-col bg-white">
<div className="flex h-full w-full flex-col bg-background">
{/* 차트 영역 - 전체 공간 사용 */}
<div ref={containerRef} className="flex-1 overflow-hidden p-2">
{error ? (

View File

@ -38,14 +38,14 @@ const calculateMetric = (rows: any[], field: string, aggregation: string): numbe
}
};
// 색상 스타일 매핑
// 색상 스타일 매핑 (차분한 색상)
const colorMap = {
indigo: { bg: "bg-indigo-50", text: "text-indigo-600", border: "border-indigo-200" },
green: { bg: "bg-green-50", text: "text-green-600", border: "border-green-200" },
blue: { bg: "bg-blue-50", text: "text-blue-600", border: "border-blue-200" },
purple: { bg: "bg-purple-50", text: "text-purple-600", border: "border-purple-200" },
orange: { bg: "bg-orange-50", text: "text-orange-600", border: "border-orange-200" },
gray: { bg: "bg-gray-50", text: "text-gray-600", border: "border-gray-200" },
indigo: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
green: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
blue: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
purple: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
orange: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
gray: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
};
/**
@ -704,10 +704,10 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
// 로딩 상태 (원본 스타일)
if (loading) {
return (
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-white p-2">
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-background p-2">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -716,12 +716,12 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
// 에러 상태 (원본 스타일)
if (error) {
return (
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-white p-2">
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-background p-2">
<div className="text-center">
<p className="text-sm text-red-600"> {error}</p>
<p className="text-sm text-destructive"> {error}</p>
<button
onClick={handleManualRefresh}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -733,8 +733,8 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
// 데이터 소스 없음 (원본 스타일)
if (!(element?.dataSources || element?.chartConfig?.dataSources) && !isGroupByMode) {
return (
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-white p-2">
<p className="text-sm text-gray-500"> </p>
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-background p-2">
<p className="text-sm text-muted-foreground"> </p>
</div>
);
}
@ -742,15 +742,15 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
// 메트릭 설정 없음 (원본 스타일)
if (metricConfig.length === 0 && !isGroupByMode) {
return (
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-white p-2">
<p className="text-sm text-gray-500"> </p>
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-background p-2">
<p className="text-sm text-muted-foreground"> </p>
</div>
);
}
// 메인 렌더링 (원본 스타일 - 심플하게)
return (
<div className="flex h-full w-full flex-col bg-white p-2">
<div className="flex h-full w-full flex-col bg-background p-2">
{/* 콘텐츠 영역 - 스크롤 가능하도록 개선 */}
<div
className="grid w-full gap-2 overflow-y-auto"
@ -769,7 +769,7 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
key={`group-${index}`}
className={`flex flex-col items-center justify-center rounded-lg border ${colors.bg} ${colors.border} p-2`}
>
<div className="text-[10px] text-gray-600">{card.label}</div>
<div className="text-[10px] text-foreground">{card.label}</div>
<div className={`mt-0.5 text-xl font-bold ${colors.text}`}>{card.value.toLocaleString()}</div>
</div>
);
@ -792,7 +792,7 @@ export default function CustomMetricTestWidget({ element }: CustomMetricTestWidg
}}
className={`flex cursor-pointer flex-col items-center justify-center rounded-lg border ${colors.bg} ${colors.border} p-2 transition-all hover:shadow-md`}
>
<div className="text-[10px] text-gray-600">{metric.label}</div>
<div className="text-[10px] text-foreground">{metric.label}</div>
<div className={`mt-0.5 text-xl font-bold ${colors.text}`}>
{formattedValue}
{metric.unit && <span className="ml-0.5 text-sm">{metric.unit}</span>}

View File

@ -35,12 +35,12 @@ const calculateMetric = (rows: any[], field: string, aggregation: string): numbe
// 색상 스타일 매핑
const colorMap = {
indigo: { bg: "bg-indigo-50", text: "text-indigo-600", border: "border-indigo-200" },
green: { bg: "bg-green-50", text: "text-green-600", border: "border-green-200" },
blue: { bg: "bg-blue-50", text: "text-blue-600", border: "border-blue-200" },
purple: { bg: "bg-purple-50", text: "text-purple-600", border: "border-purple-200" },
orange: { bg: "bg-orange-50", text: "text-orange-600", border: "border-orange-200" },
gray: { bg: "bg-gray-50", text: "text-gray-600", border: "border-gray-200" },
indigo: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
green: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
blue: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
purple: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
orange: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
gray: { bg: "bg-muted", text: "text-foreground", border: "border-border" },
};
export default function CustomMetricWidget({ element }: CustomMetricWidgetProps) {
@ -298,10 +298,10 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
if (loading) {
return (
<div className="flex h-full items-center justify-center bg-white">
<div className="flex h-full items-center justify-center bg-background">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -309,12 +309,12 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
if (error) {
return (
<div className="flex h-full items-center justify-center bg-white p-4">
<div className="flex h-full items-center justify-center bg-background p-4">
<div className="text-center">
<p className="text-sm text-red-600"> {error}</p>
<p className="text-sm text-destructive"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -344,10 +344,10 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
if (shouldShowEmpty) {
return (
<div className="flex h-full items-center justify-center bg-white p-4">
<div className="flex h-full items-center justify-center bg-background p-4">
<div className="max-w-xs space-y-2 text-center">
<h3 className="text-sm font-bold text-gray-900"> </h3>
<div className="space-y-1.5 text-xs text-gray-600">
<h3 className="text-sm font-bold text-foreground"> </h3>
<div className="space-y-1.5 text-xs text-foreground">
<p className="font-medium">📊 </p>
<ul className="space-y-0.5 text-left">
<li> SQL </li>
@ -359,7 +359,7 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
</li>
</ul>
</div>
<div className="mt-2 rounded-lg bg-blue-50 p-2 text-[10px] text-blue-700">
<div className="mt-2 rounded-lg bg-primary/10 p-2 text-[10px] text-primary">
<p className="font-medium"> </p>
<p className="mb-1">
{isGroupByMode
@ -379,7 +379,7 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
return (
<div
className={`flex h-full w-full overflow-hidden bg-white p-0.5 ${
className={`flex h-full w-full overflow-hidden bg-background p-0.5 ${
isHorizontalLayout ? "flex-row gap-0.5" : "flex-col gap-0.5"
}`}
>
@ -396,7 +396,7 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
key={`group-${index}`}
className={`flex flex-1 flex-col items-center justify-center rounded border ${colors.bg} ${colors.border} p-0.5`}
>
<div className="text-[8px] leading-tight text-gray-600">{card.label}</div>
<div className="text-[8px] leading-tight text-foreground">{card.label}</div>
<div className={`mt-0 text-xs leading-none font-bold ${colors.text}`}>{card.value.toLocaleString()}</div>
</div>
);
@ -412,7 +412,7 @@ export default function CustomMetricWidget({ element }: CustomMetricWidgetProps)
key={metric.id}
className={`flex flex-1 flex-col items-center justify-center rounded border ${colors.bg} ${colors.border} p-0.5`}
>
<div className="text-[8px] leading-tight text-gray-600">{metric.label}</div>
<div className="text-[8px] leading-tight text-foreground">{metric.label}</div>
<div className={`mt-0 text-xs leading-none font-bold ${colors.text}`}>
{formattedValue}
<span className="ml-0 text-[8px]">{metric.unit}</span>

View File

@ -600,26 +600,26 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
// 색상 매핑
const getColorClasses = (color: string) => {
const colorMap: { [key: string]: { bg: string; text: string } } = {
indigo: { bg: "bg-indigo-50", text: "text-indigo-600" },
green: { bg: "bg-green-50", text: "text-green-600" },
blue: { bg: "bg-blue-50", text: "text-blue-600" },
purple: { bg: "bg-purple-50", text: "text-purple-600" },
orange: { bg: "bg-orange-50", text: "text-orange-600" },
yellow: { bg: "bg-yellow-50", text: "text-yellow-600" },
cyan: { bg: "bg-cyan-50", text: "text-cyan-600" },
pink: { bg: "bg-pink-50", text: "text-pink-600" },
teal: { bg: "bg-teal-50", text: "text-teal-600" },
gray: { bg: "bg-gray-50", text: "text-gray-600" },
indigo: { bg: "bg-primary/10", text: "text-primary" },
green: { bg: "bg-success/10", text: "text-success" },
blue: { bg: "bg-primary/10", text: "text-primary" },
purple: { bg: "bg-purple-500/10", text: "text-purple-500" },
orange: { bg: "bg-warning/10", text: "text-warning" },
yellow: { bg: "bg-warning/10", text: "text-warning" },
cyan: { bg: "bg-primary/10", text: "text-primary" },
pink: { bg: "bg-muted", text: "text-foreground" },
teal: { bg: "bg-primary/10", text: "text-primary" },
gray: { bg: "bg-muted", text: "text-foreground" },
};
return colorMap[color] || colorMap.gray;
};
if (isLoading && stats.length === 0) {
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="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
<div className="mt-2 text-sm text-gray-600"> ...</div>
<div className="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="mt-2 text-sm text-foreground"> ...</div>
</div>
</div>
);
@ -627,14 +627,14 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
if (error) {
return (
<div className="flex h-full items-center justify-center bg-gray-50 p-4">
<div className="flex h-full items-center justify-center bg-muted p-4">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600">{error}</div>
{!element?.dataSource?.query && <div className="mt-2 text-xs text-gray-500"> </div>}
<div className="text-sm font-medium text-foreground">{error}</div>
{!element?.dataSource?.query && <div className="mt-2 text-xs text-muted-foreground"> </div>}
<button
onClick={loadData}
className="mt-3 rounded-lg bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
className="mt-3 rounded-lg bg-primary px-4 py-2 text-sm text-white hover:bg-primary/90"
>
</button>
@ -645,11 +645,11 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
if (stats.length === 0) {
return (
<div className="flex h-full items-center justify-center bg-gray-50 p-4">
<div className="flex h-full items-center justify-center bg-muted p-4">
<div className="text-center">
<div className="mb-2 text-4xl">📊</div>
<div className="text-sm font-medium text-gray-600"> </div>
<div className="mt-2 text-xs text-gray-500"> </div>
<div className="text-sm font-medium text-foreground"> </div>
<div className="mt-2 text-xs text-muted-foreground"> </div>
</div>
</div>
);
@ -685,13 +685,13 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
// console.log("🎨 렌더링 - allStats:", allStats.map(s => s.label));
return (
<div className="relative flex h-full flex-col bg-white">
<div className="relative flex h-full flex-col bg-background">
{/* 헤더 영역 */}
<div className="flex items-center justify-between border-b bg-gray-50 px-4 py-2">
<div className="flex items-center justify-between border-b bg-muted px-4 py-2">
<div className="flex items-center gap-2">
<span className="text-lg">📊</span>
<span className="text-sm font-medium text-gray-700"> </span>
<span className="text-xs text-gray-500">({stats.length} )</span>
<span className="text-sm font-medium text-foreground"> </span>
<span className="text-xs text-muted-foreground">({stats.length} )</span>
</div>
<button
onClick={() => {
@ -701,7 +701,7 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
setSelectedStats(currentLabels);
setShowSettings(true);
}}
className="flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100"
className="flex items-center gap-1 rounded-lg px-3 py-1.5 text-sm text-foreground hover:bg-muted"
title="표시할 통계 선택"
>
<span></span>
@ -716,7 +716,7 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
const colors = getColorClasses(stat.color);
return (
<div key={index} className={`rounded-lg border ${colors.bg} p-4 text-center`}>
<div className="text-sm text-gray-600">{stat.label}</div>
<div className="text-sm text-foreground">{stat.label}</div>
<div className={`mt-2 text-3xl font-bold ${colors.text}`}>
{stat.value.toFixed(stat.unit === "%" || stat.unit === "분" ? 1 : 0).toLocaleString()}
<span className="ml-1 text-lg">{stat.unit}</span>
@ -730,15 +730,15 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
{/* 설정 모달 */}
{showSettings && (
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/50">
<div className="max-h-[80%] w-[90%] max-w-md overflow-auto rounded-lg bg-white p-6 shadow-xl">
<div className="max-h-[80%] w-[90%] max-w-md overflow-auto rounded-lg bg-background p-6 shadow-xl">
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-bold"> </h3>
<button onClick={() => setShowSettings(false)} className="text-2xl text-gray-500 hover:text-gray-700">
<button onClick={() => setShowSettings(false)} className="text-2xl text-muted-foreground hover:text-foreground">
×
</button>
</div>
<div className="mb-4 text-sm text-gray-600"> ( )</div>
<div className="mb-4 text-sm text-foreground"> ( )</div>
<div className="space-y-2">
{allStats.map((stat, index) => {
@ -747,7 +747,7 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
<label
key={index}
className={`flex cursor-pointer items-center gap-3 rounded-lg border p-3 transition-colors ${
isChecked ? "border-blue-500 bg-blue-50" : "hover:bg-gray-50"
isChecked ? "border-primary bg-primary/10" : "hover:bg-muted"
}`}
>
<input
@ -761,9 +761,9 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
/>
<div className="flex-1">
<div className="font-medium">{stat.label}</div>
<div className="text-sm text-gray-500">: {stat.unit}</div>
<div className="text-sm text-muted-foreground">: {stat.unit}</div>
</div>
{isChecked && <span className="text-blue-600"></span>}
{isChecked && <span className="text-primary"></span>}
</label>
);
})}
@ -772,7 +772,7 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
<div className="mt-6 flex gap-2">
<button
onClick={handleApplySettings}
className="flex-1 rounded-lg bg-blue-500 py-2 text-white hover:bg-blue-600"
className="flex-1 rounded-lg bg-primary py-2 text-white hover:bg-primary/90"
>
({selectedStats.length} )
</button>
@ -781,7 +781,7 @@ export default function CustomStatsWidget({ element, refreshInterval = 60000 }:
// console.log("❌ 설정 취소");
setShowSettings(false);
}}
className="rounded-lg border px-4 py-2 hover:bg-gray-50"
className="rounded-lg border px-4 py-2 hover:bg-muted"
>
</button>

View File

@ -85,9 +85,9 @@ export default function CustomerIssuesWidget({ element }: CustomerIssuesWidgetPr
if (priorityLower.includes("긴급") || priorityLower.includes("high") || priorityLower.includes("urgent")) {
return "bg-destructive text-destructive-foreground";
} else if (priorityLower.includes("보통") || priorityLower.includes("medium") || priorityLower.includes("normal")) {
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100";
return "bg-warning/10 text-warning dark:bg-warning/20 dark:text-warning";
} else if (priorityLower.includes("낮음") || priorityLower.includes("low")) {
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100";
return "bg-success/10 text-success dark:bg-success/20 dark:text-success";
}
return "bg-muted text-muted-foreground";
};
@ -98,7 +98,7 @@ export default function CustomerIssuesWidget({ element }: CustomerIssuesWidgetPr
if (statusLower.includes("처리중") || statusLower.includes("processing") || statusLower.includes("pending")) {
return "bg-primary text-primary-foreground";
} else if (statusLower.includes("완료") || statusLower.includes("resolved") || statusLower.includes("closed")) {
return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100";
return "bg-success/10 text-success dark:bg-success/20 dark:text-success";
}
return "bg-muted text-muted-foreground";
};
@ -188,7 +188,7 @@ export default function CustomerIssuesWidget({ element }: CustomerIssuesWidgetPr
onClick={() => setFilterPriority("보통")}
className={`rounded-md px-3 py-1 text-xs transition-colors ${
filterPriority === "보통"
? "bg-yellow-100 text-yellow-800"
? "bg-warning/10 text-warning"
: "bg-muted text-muted-foreground hover:bg-muted/80"
}`}
>
@ -198,7 +198,7 @@ export default function CustomerIssuesWidget({ element }: CustomerIssuesWidgetPr
onClick={() => setFilterPriority("낮음")}
className={`rounded-md px-3 py-1 text-xs transition-colors ${
filterPriority === "낮음"
? "bg-green-100 text-green-800"
? "bg-success/10 text-success"
: "bg-muted text-muted-foreground hover:bg-muted/80"
}`}
>

View File

@ -89,45 +89,45 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
const getBorderColor = (status: string) => {
switch (status) {
case "배송중":
return "border-blue-500";
return "border-primary";
case "완료":
return "border-green-500";
return "border-success";
case "지연":
return "border-red-500";
return "border-destructive";
case "픽업 대기":
return "border-yellow-500";
return "border-warning";
default:
return "border-gray-500";
return "border-border";
}
};
const getDotColor = (status: string) => {
switch (status) {
case "배송중":
return "bg-blue-500";
return "bg-primary";
case "완료":
return "bg-green-500";
return "bg-success";
case "지연":
return "bg-red-500";
return "bg-destructive";
case "픽업 대기":
return "bg-yellow-500";
return "bg-warning/100";
default:
return "bg-gray-500";
return "bg-muted0";
}
};
const getTextColor = (status: string) => {
switch (status) {
case "배송중":
return "text-blue-600";
return "text-primary";
case "완료":
return "text-green-600";
return "text-success";
case "지연":
return "text-red-600";
return "text-destructive";
case "픽업 대기":
return "text-yellow-600";
return "text-warning";
default:
return "text-gray-600";
return "text-foreground";
}
};
@ -136,7 +136,7 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -145,11 +145,11 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-red-500">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -161,7 +161,7 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
if (!element?.dataSource?.query) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-gray-500">
<div className="text-center text-muted-foreground">
<p className="text-sm"> </p>
</div>
</div>
@ -171,20 +171,20 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
const totalCount = statusData.reduce((sum, item) => sum + item.count, 0);
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-slate-50 to-blue-50 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-primary/10 p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">📊 </h3>
<h3 className="text-sm font-bold text-foreground">📊 </h3>
{totalCount > 0 ? (
<p className="text-xs text-gray-500"> {totalCount.toLocaleString()}</p>
<p className="text-xs text-muted-foreground"> {totalCount.toLocaleString()}</p>
) : (
<p className="text-xs text-orange-500"> </p>
<p className="text-xs text-warning"> </p>
)}
</div>
<button
onClick={loadData}
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-white p-0 text-xs disabled:opacity-50"
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-background p-0 text-xs disabled:opacity-50"
disabled={loading}
>
{loading ? "⏳" : "🔄"}
@ -198,11 +198,11 @@ export default function DeliveryStatusSummaryWidget({ element }: DeliveryStatusS
{statusData.map((item) => (
<div
key={item.status}
className={`rounded border-l-2 bg-white p-1.5 shadow-sm ${getBorderColor(item.status)}`}
className={`rounded border-l-2 bg-background p-1.5 shadow-sm ${getBorderColor(item.status)}`}
>
<div className="mb-0.5 flex items-center gap-1">
<div className={`h-1.5 w-1.5 rounded-full ${getDotColor(item.status)}`}></div>
<div className="text-xs font-medium text-gray-600">{item.status}</div>
<div className="text-xs font-medium text-foreground">{item.status}</div>
</div>
<div className={`text-lg font-bold ${getTextColor(item.status)}`}>{item.count.toLocaleString()}</div>
</div>

View File

@ -176,15 +176,15 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
const getStatusColor = (status: DeliveryItem["status"]) => {
switch (status) {
case "in_transit":
return "bg-blue-100 text-blue-700 border-blue-300";
return "bg-primary/10 text-primary border-primary";
case "delivered":
return "bg-green-100 text-green-700 border-green-300";
return "bg-success/10 text-success border-success";
case "delayed":
return "bg-red-100 text-red-700 border-red-300";
return "bg-destructive/10 text-destructive border-destructive";
case "pickup_waiting":
return "bg-yellow-100 text-yellow-700 border-yellow-300";
return "bg-warning/10 text-warning border-warning";
default:
return "bg-gray-100 text-gray-700 border-gray-300";
return "bg-muted text-foreground border-border";
}
};
@ -236,13 +236,13 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
const getIssueStatusColor = (status: CustomerIssue["status"]) => {
switch (status) {
case "open":
return "bg-red-100 text-red-700 border-red-300";
return "bg-destructive/10 text-destructive border-destructive";
case "in_progress":
return "bg-yellow-100 text-yellow-700 border-yellow-300";
return "bg-warning/10 text-warning border-warning";
case "resolved":
return "bg-green-100 text-green-700 border-green-300";
return "bg-success/10 text-success border-success";
default:
return "bg-gray-100 text-gray-700 border-gray-300";
return "bg-muted text-foreground border-border";
}
};
@ -293,12 +293,12 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
};
return (
<div className="h-full w-full overflow-auto bg-gradient-to-br from-slate-50 to-blue-50 p-4">
<div className="h-full w-full overflow-auto bg-gradient-to-br from-background to-primary/10 p-4">
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-900">📦 / </h3>
<p className="text-xs text-gray-500"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
<h3 className="text-lg font-bold text-foreground">📦 / </h3>
<p className="text-xs text-muted-foreground"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
</div>
<Button variant="outline" size="sm" onClick={loadData} disabled={isLoading} className="h-8 w-8 p-0">
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
@ -307,96 +307,96 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
{/* 배송 상태 요약 */}
<div className="mb-3">
<h4 className="mb-2 text-sm font-semibold text-gray-700"> ( )</h4>
<h4 className="mb-2 text-sm font-semibold text-foreground"> ( )</h4>
<div className="grid grid-cols-2 gap-2 md:grid-cols-5">
<button
onClick={() => setSelectedStatus("all")}
className={`rounded-lg border-l-4 p-1.5 shadow-sm transition-all ${
selectedStatus === "all"
? "border-gray-900 bg-gray-100 ring-2 ring-gray-900"
: "border-gray-500 bg-white hover:bg-gray-50"
? "border-foreground bg-muted ring-2 ring-foreground"
: "border-border bg-background hover:bg-muted"
}`}
>
<div className="mb-0.5 text-xs text-gray-600"></div>
<div className="text-lg font-bold text-gray-900">{deliveries.length}</div>
<div className="mb-0.5 text-xs text-foreground"></div>
<div className="text-lg font-bold text-foreground">{deliveries.length}</div>
</button>
<button
onClick={() => setSelectedStatus("in_transit")}
className={`rounded-lg border-l-4 p-1.5 shadow-sm transition-all ${
selectedStatus === "in_transit"
? "border-blue-900 bg-blue-100 ring-2 ring-blue-900"
: "border-blue-500 bg-white hover:bg-blue-50"
? "border-primary bg-primary/10 ring-2 ring-primary"
: "border-primary bg-background hover:bg-primary/10"
}`}
>
<div className="mb-0.5 text-xs text-gray-600"></div>
<div className="text-lg font-bold text-blue-600">{statusStats.in_transit}</div>
<div className="mb-0.5 text-xs text-foreground"></div>
<div className="text-lg font-bold text-primary">{statusStats.in_transit}</div>
</button>
<button
onClick={() => setSelectedStatus("delivered")}
className={`rounded-lg border-l-4 p-1.5 shadow-sm transition-all ${
selectedStatus === "delivered"
? "border-green-900 bg-green-100 ring-2 ring-green-900"
: "border-green-500 bg-white hover:bg-green-50"
? "border-success bg-success/10 ring-2 ring-success"
: "border-success bg-background hover:bg-success/10"
}`}
>
<div className="mb-0.5 text-xs text-gray-600"></div>
<div className="text-lg font-bold text-green-600">{statusStats.delivered}</div>
<div className="mb-0.5 text-xs text-foreground"></div>
<div className="text-lg font-bold text-success">{statusStats.delivered}</div>
</button>
<button
onClick={() => setSelectedStatus("delayed")}
className={`rounded-lg border-l-4 p-1.5 shadow-sm transition-all ${
selectedStatus === "delayed"
? "border-red-900 bg-red-100 ring-2 ring-red-900"
: "border-red-500 bg-white hover:bg-red-50"
? "border-destructive bg-destructive/10 ring-2 ring-destructive"
: "border-destructive bg-background hover:bg-destructive/10"
}`}
>
<div className="mb-0.5 text-xs text-gray-600"></div>
<div className="text-lg font-bold text-red-600">{statusStats.delayed}</div>
<div className="mb-0.5 text-xs text-foreground"></div>
<div className="text-lg font-bold text-destructive">{statusStats.delayed}</div>
</button>
<button
onClick={() => setSelectedStatus("pickup_waiting")}
className={`rounded-lg border-l-4 p-1.5 shadow-sm transition-all ${
selectedStatus === "pickup_waiting"
? "border-yellow-900 bg-yellow-100 ring-2 ring-yellow-900"
: "border-yellow-500 bg-white hover:bg-yellow-50"
? "border-warning bg-warning/10 ring-2 ring-warning"
: "border-warning bg-background hover:bg-warning/10"
}`}
>
<div className="mb-0.5 text-xs text-gray-600"> </div>
<div className="text-lg font-bold text-yellow-600">{statusStats.pickup_waiting}</div>
<div className="mb-0.5 text-xs text-foreground"> </div>
<div className="text-lg font-bold text-warning">{statusStats.pickup_waiting}</div>
</button>
</div>
</div>
{/* 오늘 발송/도착 건수 */}
<div className="mb-3">
<h4 className="mb-2 text-sm font-semibold text-gray-700"> </h4>
<h4 className="mb-2 text-sm font-semibold text-foreground"> </h4>
<div className="grid grid-cols-2 gap-2">
<div className="rounded-lg border-l-4 border-gray-500 bg-white p-1.5 shadow-sm">
<div className="mb-0.5 text-xs text-gray-600"> </div>
<div className="text-lg font-bold text-gray-900">{todayStats.shipped}</div>
<div className="text-xs text-gray-500"></div>
<div className="rounded-lg border-l-4 border-border bg-background p-1.5 shadow-sm">
<div className="mb-0.5 text-xs text-foreground"> </div>
<div className="text-lg font-bold text-foreground">{todayStats.shipped}</div>
<div className="text-xs text-muted-foreground"></div>
</div>
<div className="rounded-lg border-l-4 border-gray-500 bg-white p-1.5 shadow-sm">
<div className="mb-0.5 text-xs text-gray-600"> </div>
<div className="text-lg font-bold text-gray-900">{todayStats.delivered}</div>
<div className="text-xs text-gray-500"></div>
<div className="rounded-lg border-l-4 border-border bg-background p-1.5 shadow-sm">
<div className="mb-0.5 text-xs text-foreground"> </div>
<div className="text-lg font-bold text-foreground">{todayStats.delivered}</div>
<div className="text-xs text-muted-foreground"></div>
</div>
</div>
</div>
{/* 필터링된 화물 리스트 */}
<div className="mb-3">
<h4 className="mb-2 flex items-center gap-2 text-sm font-semibold text-gray-700">
<Package className="h-4 w-4 text-gray-600" />
<h4 className="mb-2 flex items-center gap-2 text-sm font-semibold text-foreground">
<Package className="h-4 w-4 text-foreground" />
{selectedStatus === "all" && `전체 화물 (${filteredDeliveries.length})`}
{selectedStatus === "in_transit" && `배송 중인 화물 (${filteredDeliveries.length})`}
{selectedStatus === "delivered" && `배송 완료 (${filteredDeliveries.length})`}
{selectedStatus === "delayed" && `지연 중인 화물 (${filteredDeliveries.length})`}
{selectedStatus === "pickup_waiting" && `픽업 대기 (${filteredDeliveries.length})`}
</h4>
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
<div className="overflow-hidden rounded-lg border border-border bg-background shadow-sm">
{filteredDeliveries.length === 0 ? (
<div className="p-6 text-center text-sm text-gray-500">
<div className="p-6 text-center text-sm text-muted-foreground">
{selectedStatus === "all" ? "화물이 없습니다" : "해당 상태의 화물이 없습니다"}
</div>
) : (
@ -404,12 +404,12 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
{filteredDeliveries.map((delivery) => (
<div
key={delivery.id}
className="border-b border-gray-200 p-3 transition-colors last:border-b-0 hover:bg-gray-50"
className="border-b border-border p-3 transition-colors last:border-b-0 hover:bg-muted"
>
<div className="mb-2 flex items-start justify-between">
<div>
<div className="text-sm font-semibold text-gray-900">{delivery.customer}</div>
<div className="text-xs text-gray-600">{delivery.trackingNumber}</div>
<div className="text-sm font-semibold text-foreground">{delivery.customer}</div>
<div className="text-xs text-foreground">{delivery.trackingNumber}</div>
</div>
<span
className={`rounded-md border px-2 py-1 text-xs font-semibold ${getStatusColor(delivery.status)}`}
@ -417,7 +417,7 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
{getStatusText(delivery.status)}
</span>
</div>
<div className="space-y-1 text-xs text-gray-600">
<div className="space-y-1 text-xs text-foreground">
<div className="flex items-center gap-1">
<span className="font-medium">:</span>
<span>
@ -429,7 +429,7 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
<span>{delivery.estimatedDelivery}</span>
</div>
{delivery.delayReason && (
<div className="flex items-center gap-1 text-red-600">
<div className="flex items-center gap-1 text-destructive">
<AlertTriangle className="h-3 w-3" />
<span className="font-medium">:</span>
<span>{delivery.delayReason}</span>
@ -445,27 +445,27 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
{/* 고객 클레임/이슈 리포트 */}
<div>
<h4 className="mb-2 flex items-center gap-2 text-sm font-semibold text-gray-700">
<XCircle className="h-4 w-4 text-orange-600" />
<h4 className="mb-2 flex items-center gap-2 text-sm font-semibold text-foreground">
<XCircle className="h-4 w-4 text-warning" />
/ ({issues.filter((i) => i.status !== "resolved").length})
</h4>
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
<div className="overflow-hidden rounded-lg border border-border bg-background shadow-sm">
{issues.length === 0 ? (
<div className="p-6 text-center text-sm text-gray-500"> </div>
<div className="p-6 text-center text-sm text-muted-foreground"> </div>
) : (
<div className="scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 max-h-[200px] overflow-y-auto">
{issues.map((issue) => (
<div
key={issue.id}
className="border-b border-gray-200 p-3 transition-colors last:border-b-0 hover:bg-gray-50"
className="border-b border-border p-3 transition-colors last:border-b-0 hover:bg-muted"
>
<div className="mb-2 flex items-start justify-between">
<div>
<div className="text-sm font-semibold text-gray-900">{issue.customer}</div>
<div className="text-xs text-gray-600">{issue.trackingNumber}</div>
<div className="text-sm font-semibold text-foreground">{issue.customer}</div>
<div className="text-xs text-foreground">{issue.trackingNumber}</div>
</div>
<div className="flex gap-1">
<span className="rounded-md border border-gray-300 bg-gray-100 px-2 py-1 text-xs font-semibold text-gray-700">
<span className="rounded-md border border-border bg-muted px-2 py-1 text-xs font-semibold text-foreground">
{getIssueTypeText(issue.issueType)}
</span>
<span
@ -475,9 +475,9 @@ export default function DeliveryStatusWidget({ element, refreshInterval = 60000
</span>
</div>
</div>
<div className="space-y-1 text-xs text-gray-600">
<div className="space-y-1 text-xs text-foreground">
<div>{issue.description}</div>
<div className="text-gray-500">: {issue.reportedAt}</div>
<div className="text-muted-foreground">: {issue.reportedAt}</div>
</div>
</div>
))}

View File

@ -95,7 +95,7 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -104,11 +104,11 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-red-500">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -120,7 +120,7 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
if (!element?.dataSource?.query) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-gray-500">
<div className="text-center text-muted-foreground">
<p className="text-sm"> </p>
</div>
</div>
@ -128,11 +128,11 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
}
return (
<div className="flex h-full flex-col overflow-hidden bg-white p-4">
<div className="flex h-full flex-col overflow-hidden bg-background p-4">
{/* 헤더 */}
<div className="mb-4 flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-800"> </h3>
<button onClick={loadData} className="rounded-full p-1 text-gray-500 hover:bg-gray-100" title="새로고침">
<h3 className="text-lg font-semibold text-foreground"> </h3>
<button onClick={loadData} className="rounded-full p-1 text-muted-foreground hover:bg-muted" title="새로고침">
🔄
</button>
</div>
@ -140,19 +140,19 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
{/* 통계 카드 */}
<div className="flex flex-1 flex-col gap-4">
{/* 오늘 발송 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-blue-50 to-blue-100 p-6">
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-primary/10 to-primary/20 p-6">
<div className="mb-2 text-4xl">📤</div>
<p className="text-sm font-medium text-blue-700"> </p>
<p className="mt-2 text-4xl font-bold text-blue-800">{todayStats.shipped.toLocaleString()}</p>
<p className="mt-1 text-xs text-blue-600"></p>
<p className="text-sm font-medium text-primary"> </p>
<p className="mt-2 text-4xl font-bold text-primary">{todayStats.shipped.toLocaleString()}</p>
<p className="mt-1 text-xs text-primary"></p>
</div>
{/* 오늘 도착 */}
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-green-50 to-green-100 p-6">
<div className="flex flex-1 flex-col items-center justify-center rounded-lg bg-gradient-to-br from-success/10 to-success/20 p-6">
<div className="mb-2 text-4xl">📥</div>
<p className="text-sm font-medium text-green-700"> </p>
<p className="mt-2 text-4xl font-bold text-green-800">{todayStats.delivered.toLocaleString()}</p>
<p className="mt-1 text-xs text-green-600"></p>
<p className="text-sm font-medium text-success"> </p>
<p className="mt-2 text-4xl font-bold text-success">{todayStats.delivered.toLocaleString()}</p>
<p className="mt-1 text-xs text-success"></p>
</div>
</div>
</div>

View File

@ -105,13 +105,13 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
const getCategoryColor = (category: Document["category"]) => {
switch (category) {
case "계약서":
return "bg-blue-100 text-blue-700";
return "bg-primary/10 text-primary";
case "보험":
return "bg-green-100 text-green-700";
return "bg-success/10 text-success";
case "세금계산서":
return "bg-amber-100 text-amber-700";
return "bg-warning/10 text-warning";
case "기타":
return "bg-gray-100 text-gray-700";
return "bg-muted text-foreground";
}
};
@ -128,11 +128,11 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
};
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-blue-50">
<div className="flex h-full flex-col bg-background">
{/* 헤더 */}
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="border-b border-border bg-background px-4 py-3">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-lg font-bold text-gray-800">{element?.customTitle || "문서 관리"}</h3>
<h3 className="text-lg font-bold text-foreground">{element?.customTitle || "문서 관리"}</h3>
<button className="rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90">
+
</button>
@ -140,33 +140,33 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
{/* 통계 */}
<div className="mb-3 grid grid-cols-4 gap-2 text-xs">
<div className="rounded bg-gray-50 px-2 py-1.5 text-center">
<div className="font-bold text-gray-700">{stats.total}</div>
<div className="text-gray-600"></div>
<div className="rounded bg-muted px-2 py-1.5 text-center">
<div className="font-bold text-foreground">{stats.total}</div>
<div className="text-foreground"></div>
</div>
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
<div className="font-bold text-blue-700">{stats.contract}</div>
<div className="text-blue-600"></div>
<div className="rounded bg-primary/10 px-2 py-1.5 text-center">
<div className="font-bold text-primary">{stats.contract}</div>
<div className="text-primary"></div>
</div>
<div className="rounded bg-green-50 px-2 py-1.5 text-center">
<div className="font-bold text-green-700">{stats.insurance}</div>
<div className="text-green-600"></div>
<div className="rounded bg-success/10 px-2 py-1.5 text-center">
<div className="font-bold text-success">{stats.insurance}</div>
<div className="text-success"></div>
</div>
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
<div className="font-bold text-amber-700">{stats.tax}</div>
<div className="text-amber-600"></div>
<div className="rounded bg-warning/10 px-2 py-1.5 text-center">
<div className="font-bold text-warning">{stats.tax}</div>
<div className="text-warning"></div>
</div>
</div>
{/* 검색 */}
<div className="mb-3 relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="문서명 검색..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full rounded border border-gray-300 py-2 pl-10 pr-3 text-sm focus:border-primary focus:outline-none"
className="w-full rounded border border-border py-2 pl-10 pr-3 text-sm focus:border-primary focus:outline-none"
/>
</div>
@ -177,7 +177,7 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
key={f}
onClick={() => setFilter(f)}
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
filter === f ? "bg-primary text-white" : "bg-muted text-foreground hover:bg-muted"
}`}
>
{f === "all" ? "전체" : f}
@ -189,7 +189,7 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
{/* 문서 리스트 */}
<div className="flex-1 overflow-y-auto p-4">
{filteredDocuments.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="flex h-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-4xl">📭</div>
<div> </div>
@ -200,10 +200,10 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
{filteredDocuments.map((doc) => (
<div
key={doc.id}
className="group flex items-center gap-3 rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-all hover:border-primary hover:shadow-md"
className="group flex items-center gap-3 rounded-lg border border-border bg-background p-3 shadow-sm transition-all hover:border-primary hover:shadow-md"
>
{/* 아이콘 */}
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg bg-gray-50 text-2xl">
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-lg bg-muted text-2xl">
{getCategoryIcon(doc.category)}
</div>
@ -211,11 +211,11 @@ export default function DocumentWidget({ element }: DocumentWidgetProps) {
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="truncate font-medium text-gray-800">{doc.name}</div>
<div className="truncate font-medium text-foreground">{doc.name}</div>
{doc.description && (
<div className="mt-0.5 truncate text-xs text-gray-600">{doc.description}</div>
<div className="mt-0.5 truncate text-xs text-foreground">{doc.description}</div>
)}
<div className="mt-1 flex items-center gap-3 text-xs text-gray-500">
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
<span className={`rounded px-2 py-0.5 ${getCategoryColor(doc.category)}`}>
{doc.category}
</span>

View File

@ -122,10 +122,10 @@ export default function ExchangeWidget({
// 로딩 상태
if (loading && !exchangeRate) {
return (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-6">
<div className="flex h-full items-center justify-center bg-background rounded-lg border p-6">
<div className="flex flex-col items-center gap-2">
<RefreshCw className="h-8 w-8 animate-spin text-green-500" />
<p className="text-sm text-gray-600"> ...</p>
<RefreshCw className="h-8 w-8 animate-spin text-success" />
<p className="text-sm text-foreground"> ...</p>
</div>
</div>
);
@ -135,12 +135,12 @@ export default function ExchangeWidget({
const hasError = error || !exchangeRate;
return (
<div className="h-full bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-4 @container">
<div className="h-full bg-background rounded-lg border p-4 @container">
{/* 헤더 */}
<div className="flex items-center justify-between mb-3">
<div className="flex-1">
<h3 className="text-base font-semibold text-gray-900 mb-1">{element?.customTitle || "환율"}</h3>
<p className="text-xs text-gray-500">
<h3 className="text-base font-semibold text-foreground mb-1">{element?.customTitle || "환율"}</h3>
<p className="text-xs text-muted-foreground">
{lastUpdated
? `업데이트: ${lastUpdated.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -163,7 +163,7 @@ export default function ExchangeWidget({
{/* 통화 선택 - 반응형 (좁을 때 세로 배치) */}
<div className="flex @[300px]:flex-row flex-col items-center gap-2 mb-3">
<Select value={base} onValueChange={setBase}>
<SelectTrigger className="w-full @[300px]:flex-1 bg-white h-8 text-xs">
<SelectTrigger className="w-full @[300px]:flex-1 bg-background h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -179,13 +179,13 @@ export default function ExchangeWidget({
variant="ghost"
size="sm"
onClick={handleSwap}
className="h-8 w-8 p-0 rounded-full hover:bg-white @[300px]:rotate-0 rotate-90"
className="h-8 w-8 p-0 rounded-full hover:bg-background @[300px]:rotate-0 rotate-90"
>
<ArrowRightLeft className="h-3 w-3" />
</Button>
<Select value={target} onValueChange={setTarget}>
<SelectTrigger className="w-full @[300px]:flex-1 bg-white h-8 text-xs">
<SelectTrigger className="w-full @[300px]:flex-1 bg-background h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -200,11 +200,11 @@ export default function ExchangeWidget({
{/* 에러 메시지 */}
{hasError && (
<div className="mb-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-xs text-red-600 text-center">{error || '환율 정보를 불러올 수 없습니다.'}</p>
<div className="mb-3 p-3 bg-destructive/10 border border-destructive rounded-lg">
<p className="text-xs text-destructive text-center">{error || '환율 정보를 불러올 수 없습니다.'}</p>
<button
onClick={fetchExchangeRate}
className="mt-2 w-full text-xs text-red-600 hover:text-red-700 underline"
className="mt-2 w-full text-xs text-destructive hover:text-destructive underline"
>
</button>
@ -213,12 +213,12 @@ export default function ExchangeWidget({
{/* 환율 표시 */}
{!hasError && (
<div className="mb-2 bg-white rounded-lg border p-2">
<div className="mb-2 bg-background rounded-lg border p-2">
<div className="text-center">
<div className="text-xs text-gray-400 mb-0.5">
<div className="text-xs text-muted-foreground mb-0.5">
{exchangeRate.base === 'KRW' ? '1,000' : '1'} {getCurrencySymbol(exchangeRate.base)} =
</div>
<div className="text-lg font-bold text-gray-900">
<div className="text-lg font-bold text-foreground">
{exchangeRate.base === 'KRW'
? (exchangeRate.rate * 1000).toLocaleString('ko-KR', {
minimumFractionDigits: 2,
@ -229,13 +229,13 @@ export default function ExchangeWidget({
maximumFractionDigits: 4,
})}
</div>
<div className="text-xs text-gray-400 mt-0.5">{getCurrencySymbol(exchangeRate.target)}</div>
<div className="text-xs text-muted-foreground mt-0.5">{getCurrencySymbol(exchangeRate.target)}</div>
</div>
</div>
)}
{/* 계산기 입력 */}
<div className="bg-white rounded-lg border p-2">
<div className="bg-background rounded-lg border p-2">
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Input
@ -247,30 +247,30 @@ export default function ExchangeWidget({
autoComplete="off"
className="flex-1 text-center text-sm font-semibold"
/>
<span className="text-xs text-gray-400 w-12">{base}</span>
<span className="text-xs text-muted-foreground w-12">{base}</span>
</div>
<div className="flex items-center justify-center gap-2">
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-gray-300 to-transparent" />
<span className="text-xs text-gray-400"></span>
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-gray-300 to-transparent" />
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-border to-transparent" />
<span className="text-xs text-muted-foreground"></span>
<div className="h-px flex-1 bg-gradient-to-r from-transparent via-border to-transparent" />
</div>
<div className="flex items-center gap-2">
<div className="flex-1 text-center text-lg font-bold text-green-600 bg-green-50 border border-green-200 rounded px-2 py-1.5">
<div className="flex-1 text-center text-lg font-bold text-success bg-success/10 border border-success rounded px-2 py-1.5">
{calculateResult().toLocaleString('ko-KR', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})}
</div>
<span className="text-xs text-gray-400 w-12">{target}</span>
<span className="text-xs text-muted-foreground w-12">{target}</span>
</div>
</div>
</div>
{/* 데이터 출처 */}
<div className="mt-3 pt-2 border-t text-center">
<p className="text-xs text-gray-400">: {exchangeRate.source}</p>
<p className="text-xs text-muted-foreground">: {exchangeRate.source}</p>
</div>
</div>
);

View File

@ -203,7 +203,7 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -212,11 +212,11 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-red-500">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -230,8 +230,8 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
<div className="flex h-full items-center justify-center p-3">
<div className="max-w-xs space-y-2 text-center">
<div className="text-3xl">📋</div>
<h3 className="text-sm font-bold text-gray-900"> </h3>
<div className="space-y-1.5 text-xs text-gray-600">
<h3 className="text-sm font-bold text-foreground"> </h3>
<div className="space-y-1.5 text-xs text-foreground">
<p className="font-medium">📋 </p>
<ul className="space-y-0.5 text-left">
<li> SQL </li>
@ -240,7 +240,7 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
<li> </li>
</ul>
</div>
<div className="mt-2 rounded-lg bg-blue-50 p-2 text-[10px] text-blue-700">
<div className="mt-2 rounded-lg bg-primary/10 p-2 text-[10px] text-primary">
<p>SQL </p>
</div>
</div>
@ -249,16 +249,16 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
}
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-slate-50 to-blue-50 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-primary/10 p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">📋 {displayTitle}</h3>
<p className="text-xs text-gray-500"> {filteredData.length.toLocaleString()}</p>
<h3 className="text-sm font-bold text-foreground">📋 {displayTitle}</h3>
<p className="text-xs text-muted-foreground"> {filteredData.length.toLocaleString()}</p>
</div>
<button
onClick={loadData}
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-white p-0 text-xs disabled:opacity-50"
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-background p-0 text-xs disabled:opacity-50"
disabled={loading}
>
{loading ? "⏳" : "🔄"}
@ -273,7 +273,7 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
placeholder="검색..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="focus:border-primary focus:ring-primary w-full rounded border border-gray-300 px-2 py-1 text-xs focus:ring-1 focus:outline-none"
className="focus:border-primary focus:ring-primary w-full rounded border border-border px-2 py-1 text-xs focus:ring-1 focus:outline-none"
/>
</div>
)}
@ -282,20 +282,20 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
<div className="flex-1 overflow-auto">
{filteredData.length > 0 ? (
<table className="w-full border-collapse text-xs">
<thead className="sticky top-0 bg-gray-100">
<thead className="sticky top-0 bg-muted">
<tr>
{columns.map((col) => (
<th key={col.key} className="border border-gray-300 px-2 py-1 text-left font-semibold text-gray-700">
<th key={col.key} className="border border-border px-2 py-1 text-left font-semibold text-foreground">
{col.label}
</th>
))}
</tr>
</thead>
<tbody className="bg-white">
<tbody className="bg-background">
{filteredData.map((row, idx) => (
<tr key={idx} className="hover:bg-gray-50">
<tr key={idx} className="hover:bg-muted">
{columns.map((col) => (
<td key={col.key} className="border border-gray-300 px-2 py-1 text-gray-800">
<td key={col.key} className="border border-border px-2 py-1 text-foreground">
{String(row[col.key] || "")}
</td>
))}
@ -304,7 +304,7 @@ export default function ListSummaryWidget({ element }: ListSummaryWidgetProps) {
</tbody>
</table>
) : (
<div className="flex h-full items-center justify-center text-gray-500">
<div className="flex h-full items-center justify-center text-muted-foreground">
<p className="text-sm"> </p>
</div>
)}

View File

@ -70,13 +70,13 @@ export default function MaintenanceWidget() {
const getStatusBadge = (status: MaintenanceSchedule["status"]) => {
switch (status) {
case "scheduled":
return <span className="rounded bg-blue-100 px-2 py-1 text-xs font-medium text-blue-700"></span>;
return <span className="rounded bg-primary/10 px-2 py-1 text-xs font-medium text-primary"></span>;
case "in_progress":
return <span className="rounded bg-amber-100 px-2 py-1 text-xs font-medium text-amber-700"></span>;
return <span className="rounded bg-warning/10 px-2 py-1 text-xs font-medium text-warning"></span>;
case "completed":
return <span className="rounded bg-green-100 px-2 py-1 text-xs font-medium text-green-700"></span>;
return <span className="rounded bg-success/10 px-2 py-1 text-xs font-medium text-success"></span>;
case "overdue":
return <span className="rounded bg-red-100 px-2 py-1 text-xs font-medium text-red-700"></span>;
return <span className="rounded bg-destructive/10 px-2 py-1 text-xs font-medium text-destructive"></span>;
}
};
@ -115,11 +115,11 @@ export default function MaintenanceWidget() {
};
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-teal-50">
<div className="flex h-full flex-col bg-gradient-to-br from-background to-primary/10">
{/* 헤더 */}
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="border-b border-border bg-background px-4 py-3">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-lg font-bold text-gray-800">🔧 </h3>
<h3 className="text-lg font-bold text-foreground">🔧 </h3>
<button className="rounded-lg bg-primary px-3 py-1.5 text-sm text-white transition-colors hover:bg-primary/90">
+
</button>
@ -127,21 +127,21 @@ export default function MaintenanceWidget() {
{/* 통계 */}
<div className="mb-3 grid grid-cols-4 gap-2 text-xs">
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
<div className="font-bold text-blue-700">{stats.scheduled}</div>
<div className="text-blue-600"></div>
<div className="rounded bg-primary/10 px-2 py-1.5 text-center">
<div className="font-bold text-primary">{stats.scheduled}</div>
<div className="text-primary"></div>
</div>
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
<div className="font-bold text-amber-700">{stats.inProgress}</div>
<div className="text-amber-600"></div>
<div className="rounded bg-warning/10 px-2 py-1.5 text-center">
<div className="font-bold text-warning">{stats.inProgress}</div>
<div className="text-warning"></div>
</div>
<div className="rounded bg-red-50 px-2 py-1.5 text-center">
<div className="font-bold text-red-700">{stats.overdue}</div>
<div className="text-red-600"></div>
<div className="rounded bg-destructive/10 px-2 py-1.5 text-center">
<div className="font-bold text-destructive">{stats.overdue}</div>
<div className="text-destructive"></div>
</div>
<div className="rounded bg-gray-50 px-2 py-1.5 text-center">
<div className="font-bold text-gray-700">{stats.total}</div>
<div className="text-gray-600"></div>
<div className="rounded bg-muted px-2 py-1.5 text-center">
<div className="font-bold text-foreground">{stats.total}</div>
<div className="text-foreground"></div>
</div>
</div>
@ -152,7 +152,7 @@ export default function MaintenanceWidget() {
key={f}
onClick={() => setFilter(f)}
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
filter === f ? "bg-primary text-white" : "bg-muted text-foreground hover:bg-muted"
}`}
>
{f === "all" ? "전체" : f === "scheduled" ? "예정" : f === "in_progress" ? "진행중" : "지연"}
@ -164,7 +164,7 @@ export default function MaintenanceWidget() {
{/* 일정 리스트 */}
<div className="flex-1 overflow-y-auto p-4">
{filteredSchedules.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="flex h-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-4xl">📅</div>
<div> </div>
@ -175,34 +175,34 @@ export default function MaintenanceWidget() {
{filteredSchedules.map((schedule) => (
<div
key={schedule.id}
className={`group rounded-lg border-2 bg-white p-4 shadow-sm transition-all hover:shadow-md ${
schedule.status === "overdue" ? "border-red-300" : "border-gray-200"
className={`group rounded-lg border-2 bg-background p-4 shadow-sm transition-all hover:shadow-md ${
schedule.status === "overdue" ? "border-destructive" : "border-border"
}`}
>
<div className="mb-2 flex items-start justify-between">
<div className="flex items-center gap-2">
<span className="text-2xl">{getMaintenanceIcon(schedule.maintenanceType)}</span>
<div>
<div className="font-bold text-gray-800">{schedule.vehicleNumber}</div>
<div className="text-xs text-gray-600">{schedule.vehicleType}</div>
<div className="font-bold text-foreground">{schedule.vehicleNumber}</div>
<div className="text-xs text-foreground">{schedule.vehicleType}</div>
</div>
</div>
{getStatusBadge(schedule.status)}
</div>
<div className="mb-3 rounded bg-gray-50 p-2">
<div className="text-sm font-medium text-gray-700">{schedule.maintenanceType}</div>
{schedule.notes && <div className="mt-1 text-xs text-gray-600">{schedule.notes}</div>}
<div className="mb-3 rounded bg-muted p-2">
<div className="text-sm font-medium text-foreground">{schedule.maintenanceType}</div>
{schedule.notes && <div className="mt-1 text-xs text-foreground">{schedule.notes}</div>}
</div>
<div className="grid grid-cols-2 gap-2 text-xs">
<div className="flex items-center gap-1 text-gray-600">
<div className="flex items-center gap-1 text-foreground">
<Calendar className="h-3 w-3" />
{new Date(schedule.scheduledDate).toLocaleDateString()}
</div>
<div
className={`flex items-center gap-1 font-medium ${
schedule.status === "overdue" ? "text-red-600" : "text-blue-600"
schedule.status === "overdue" ? "text-destructive" : "text-primary"
}`}
>
<Clock className="h-3 w-3" />
@ -218,17 +218,17 @@ export default function MaintenanceWidget() {
{/* 액션 버튼 */}
{schedule.status === "scheduled" && (
<div className="mt-3 flex gap-2">
<button className="flex-1 rounded bg-blue-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-600">
<button className="flex-1 rounded bg-primary px-3 py-1.5 text-xs font-medium text-white hover:bg-primary/90">
</button>
<button className="flex-1 rounded bg-gray-200 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-300">
<button className="flex-1 rounded bg-muted px-3 py-1.5 text-xs font-medium text-foreground hover:bg-muted/90">
</button>
</div>
)}
{schedule.status === "in_progress" && (
<div className="mt-3">
<button className="w-full rounded bg-green-500 px-3 py-1.5 text-xs font-medium text-white hover:bg-green-600">
<button className="w-full rounded bg-success px-3 py-1.5 text-xs font-medium text-white hover:bg-success/90">
<Check className="mr-1 inline h-3 w-3" />
</button>

View File

@ -155,20 +155,20 @@ export default function MapSummaryWidget({ element }: MapSummaryWidgetProps) {
const displayTitle = element.customTitle || (tableName ? `${translateTableName(tableName)} 위치` : "위치 지도");
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-slate-50 to-blue-50 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-primary/10 p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">{displayTitle}</h3>
<h3 className="text-sm font-bold text-foreground">{displayTitle}</h3>
{element?.dataSource?.query ? (
<p className="text-xs text-gray-500"> {markers.length.toLocaleString()} </p>
<p className="text-xs text-muted-foreground"> {markers.length.toLocaleString()} </p>
) : (
<p className="text-xs text-orange-500"> </p>
<p className="text-xs text-warning"> </p>
)}
</div>
<button
onClick={loadMapData}
className="flex h-7 w-7 items-center justify-center rounded border border-border bg-white p-0 text-xs hover:bg-accent disabled:opacity-50"
className="flex h-7 w-7 items-center justify-center rounded border border-border bg-background p-0 text-xs hover:bg-accent disabled:opacity-50"
disabled={loading || !element?.dataSource?.query}
>
{loading ? "⏳" : "🔄"}
@ -177,13 +177,13 @@ export default function MapSummaryWidget({ element }: MapSummaryWidgetProps) {
{/* 에러 메시지 (지도 위에 오버레이) */}
{error && (
<div className="mb-2 rounded border border-red-300 bg-red-50 p-2 text-center text-xs text-red-600">
<div className="mb-2 rounded border border-destructive bg-destructive/10 p-2 text-center text-xs text-destructive">
{error}
</div>
)}
{/* 지도 (항상 표시) */}
<div className="relative flex-1 rounded border border-gray-300 bg-white overflow-hidden z-0">
<div className="relative flex-1 rounded border border-border bg-background overflow-hidden z-0">
<MapContainer
key={`map-${element.id}`}
center={[36.5, 127.5]}

View File

@ -247,15 +247,15 @@ const findNearestCity = (lat: number, lng: number): string => {
const getWeatherIcon = (weatherMain: string) => {
switch (weatherMain.toLowerCase()) {
case "clear":
return <Sun className="h-4 w-4 text-yellow-500" />;
return <Sun className="h-4 w-4 text-warning" />;
case "rain":
return <CloudRain className="h-4 w-4 text-blue-500" />;
return <CloudRain className="h-4 w-4 text-primary" />;
case "snow":
return <CloudSnow className="h-4 w-4 text-blue-300" />;
return <CloudSnow className="h-4 w-4 text-primary/70" />;
case "clouds":
return <Cloud className="h-4 w-4 text-gray-400" />;
return <Cloud className="h-4 w-4 text-muted-foreground" />;
default:
return <Wind className="h-4 w-4 text-gray-500" />;
return <Wind className="h-4 w-4 text-muted-foreground" />;
}
};
@ -867,22 +867,22 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
const dataSource = element?.dataSource;
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-slate-50 to-blue-50 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-primary/10 p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">{displayTitle}</h3>
<h3 className="text-sm font-bold text-foreground">{displayTitle}</h3>
{dataSource ? (
<p className="text-xs text-gray-500">
<p className="text-xs text-muted-foreground">
{dataSource.type === "api" ? "🌐 REST API" : "💾 Database"} · {markers.length.toLocaleString()}
</p>
) : (
<p className="text-xs text-orange-500"> </p>
<p className="text-xs text-warning"> </p>
)}
</div>
<button
onClick={loadMapData}
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-white p-0 text-xs disabled:opacity-50"
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-background p-0 text-xs disabled:opacity-50"
disabled={loading || !element?.dataSource}
>
{loading ? "⏳" : "🔄"}
@ -891,19 +891,19 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
{/* 에러 메시지 (지도 위에 오버레이) */}
{error && (
<div className="mb-2 rounded border border-red-300 bg-red-50 p-2 text-center text-xs text-red-600">
<div className="mb-2 rounded border border-destructive bg-destructive/10 p-2 text-center text-xs text-destructive">
{error}
</div>
)}
{/* 지도 또는 빈 상태 */}
<div className="relative z-0 flex-1 overflow-hidden rounded border border-gray-300 bg-white">
<div className="relative z-0 flex-1 overflow-hidden rounded border border-border bg-background">
{!element?.chartConfig?.tileMapUrl && !element?.dataSource ? (
// 타일맵 URL도 없고 데이터 소스도 없을 때: 빈 상태 표시
<div className="flex h-full items-center justify-center">
<div className="text-center">
<p className="text-sm font-medium text-gray-600">🗺 </p>
<p className="mt-2 text-xs text-gray-500">
<p className="text-sm font-medium text-foreground">🗺 </p>
<p className="mt-2 text-xs text-muted-foreground">
URL을
<br />
@ -979,10 +979,10 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
<div style="font-weight: 600; font-size: 12px; color: ${getAlertColor(alert.severity)};">
${alert.title}
</div>
<div style="font-size: 11px; color: #6b7280; margin-top: 4px;">
<div style="font-size: 11px; color: hsl(var(--muted-foreground)); margin-top: 4px;">
${alert.description}
</div>
<div style="font-size: 10px; color: #9ca3af; margin-top: 4px;">
<div style="font-size: 10px; color: hsl(var(--muted-foreground) / 0.7); margin-top: 4px;">
${new Date(alert.timestamp).toLocaleString("ko-KR")}
</div>
</div>
@ -1064,10 +1064,10 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
}}
>
<div style={{ fontWeight: "600", fontSize: "11px", color: alertColor }}>{alert.title}</div>
<div style={{ fontSize: "10px", color: "#6b7280", marginTop: "3px" }}>
<div className="text-[10px] text-muted-foreground mt-[3px]">
{alert.description}
</div>
<div style={{ fontSize: "9px", color: "#9ca3af", marginTop: "3px" }}>
<div className="text-[9px] text-muted-foreground/70 mt-[3px]">
{new Date(alert.timestamp).toLocaleString("ko-KR")}
</div>
</div>
@ -1113,7 +1113,7 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
<div className="mb-2 border-b pb-2">
<div className="mb-1 text-sm font-bold">{marker.name}</div>
{marker.description && (
<div className="text-xs text-gray-600 whitespace-pre-line">{marker.description}</div>
<div className="text-xs text-foreground whitespace-pre-line">{marker.description}</div>
)}
{marker.info && Object.entries(marker.info)
.filter(([key]) => !["latitude", "longitude", "lat", "lng"].includes(key.toLowerCase()))
@ -1131,22 +1131,22 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
{getWeatherIcon(marker.weather.weatherMain)}
<span className="text-xs font-semibold"> </span>
</div>
<div className="text-xs text-gray-600">{marker.weather.weatherDescription}</div>
<div className="text-xs text-foreground">{marker.weather.weatherDescription}</div>
<div className="mt-2 space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-medium">{marker.weather.temperature}°C</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-medium">{marker.weather.feelsLike}°C</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-medium">{marker.weather.humidity}%</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-medium">{marker.weather.windSpeed} m/s</span>
</div>
</div>
@ -1162,7 +1162,7 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
{/* 범례 (특보가 있을 때만 표시) */}
{weatherAlerts && weatherAlerts.length > 0 && (
<div className="absolute right-4 bottom-4 z-10 rounded-lg border bg-white p-3 shadow-lg">
<div className="absolute right-4 bottom-4 z-10 rounded-lg border bg-background p-3 shadow-lg">
<div className="mb-2 flex items-center gap-1 text-xs font-semibold">
<AlertTriangle className="h-3 w-3" />
@ -1181,7 +1181,7 @@ function MapTestWidget({ element }: MapTestWidgetProps) {
<span> </span>
</div>
</div>
<div className="mt-2 border-t pt-2 text-[10px] text-gray-500"> {weatherAlerts.length} </div>
<div className="mt-2 border-t pt-2 text-[10px] text-muted-foreground"> {weatherAlerts.length} </div>
</div>
)}
</div>

View File

@ -490,9 +490,9 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
const getSeverityColor = (severity: "high" | "medium" | "low") => {
switch (severity) {
case "high": return "bg-red-500";
case "medium": return "bg-yellow-500";
case "low": return "bg-blue-500";
case "high": return "bg-destructive";
case "medium": return "bg-warning/100";
case "low": return "bg-primary";
}
};
@ -503,7 +503,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -512,11 +512,11 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-red-500">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadMultipleDataSources}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -530,8 +530,8 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
<div className="flex h-full items-center justify-center p-3">
<div className="max-w-xs space-y-2 text-center">
<div className="text-3xl">🚨</div>
<h3 className="text-sm font-bold text-gray-900">/</h3>
<div className="space-y-1.5 text-xs text-gray-600">
<h3 className="text-sm font-bold text-foreground">/</h3>
<div className="space-y-1.5 text-xs text-foreground">
<p className="font-medium"> </p>
<ul className="space-y-0.5 text-left">
<li> REST API </li>
@ -540,7 +540,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
<li> </li>
</ul>
</div>
<div className="mt-2 rounded-lg bg-blue-50 p-2 text-[10px] text-blue-700">
<div className="mt-2 rounded-lg bg-primary/10 p-2 text-[10px] text-primary">
<p className="font-medium"> </p>
<p> </p>
</div>
@ -550,9 +550,9 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
}
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-red-50 to-orange-50">
<div className="flex h-full w-full flex-col overflow-hidden bg-background">
{/* 헤더 */}
<div className="flex items-center justify-between border-b bg-white/80 p-3">
<div className="flex items-center justify-between border-b bg-background/80 p-3">
<div>
<h3 className="text-base font-semibold">
{element?.customTitle || "리스크/알림"}
@ -610,14 +610,14 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
<div className="flex-1 space-y-1.5 overflow-y-auto pr-1">
{filteredAlerts.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-500">
<div className="flex h-full items-center justify-center text-muted-foreground">
<p className="text-sm"> </p>
</div>
) : (
filteredAlerts.map((alert) => (
<Card key={alert.id} className="border-l-4 p-2" style={{ borderLeftColor: alert.severity === "high" ? "#ef4444" : alert.severity === "medium" ? "#f59e0b" : "#3b82f6" }}>
<Card key={alert.id} className="p-2">
<div className="flex items-start gap-2">
<div className={`mt-0.5 rounded-full p-1 ${alert.severity === "high" ? "bg-red-100 text-red-600" : alert.severity === "medium" ? "bg-yellow-100 text-yellow-600" : "bg-blue-100 text-blue-600"}`}>
<div className={`mt-0.5 rounded-full p-1 ${alert.severity === "high" ? "bg-destructive/10 text-destructive" : alert.severity === "medium" ? "bg-warning/10 text-warning" : "bg-primary/10 text-primary"}`}>
{getTypeIcon(alert.type)}
</div>
<div className="flex-1 min-w-0">
@ -630,10 +630,10 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
</Badge>
</div>
{alert.location && (
<p className="text-[10px] text-gray-500 mt-0.5">📍 {alert.location}</p>
<p className="text-[10px] text-muted-foreground mt-0.5">📍 {alert.location}</p>
)}
<p className="text-[10px] text-gray-600 mt-0.5 line-clamp-2">{alert.description}</p>
<div className="mt-1 flex items-center gap-2 text-[9px] text-gray-400">
<p className="text-[10px] text-foreground mt-0.5 line-clamp-2">{alert.description}</p>
<div className="mt-1 flex items-center gap-2 text-[9px] text-muted-foreground">
<span>{new Date(alert.timestamp).toLocaleString("ko-KR")}</span>
{alert.source && <span>· {alert.source}</span>}
</div>

View File

@ -135,11 +135,11 @@ export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) {
const getAlertIcon = (type: AlertType) => {
switch (type) {
case "accident":
return <AlertTriangle className="h-5 w-5 text-red-600" />;
return <AlertTriangle className="h-5 w-5 text-destructive" />;
case "weather":
return <Cloud className="h-5 w-5 text-blue-600" />;
return <Cloud className="h-5 w-5 text-primary" />;
case "construction":
return <Construction className="h-5 w-5 text-yellow-600" />;
return <Construction className="h-5 w-5 text-warning" />;
}
};
@ -208,30 +208,30 @@ export default function RiskAlertWidget({ element }: RiskAlertWidgetProps) {
<div className="grid grid-cols-3 gap-3">
<Card
className={`cursor-pointer p-3 transition-all hover:shadow-md ${
filter === "accident" ? "bg-red-50" : ""
filter === "accident" ? "bg-destructive/10" : ""
}`}
onClick={() => setFilter(filter === "accident" ? "all" : "accident")}
>
<div className="text-xs text-muted-foreground"></div>
<div className="text-2xl font-bold text-red-600">{stats.accident}</div>
<div className="text-2xl font-bold text-destructive">{stats.accident}</div>
</Card>
<Card
className={`cursor-pointer p-3 transition-all hover:shadow-md ${
filter === "weather" ? "bg-blue-50" : ""
filter === "weather" ? "bg-primary/10" : ""
}`}
onClick={() => setFilter(filter === "weather" ? "all" : "weather")}
>
<div className="text-xs text-muted-foreground"></div>
<div className="text-2xl font-bold text-blue-600">{stats.weather}</div>
<div className="text-2xl font-bold text-primary">{stats.weather}</div>
</Card>
<Card
className={`cursor-pointer p-3 transition-all hover:shadow-md ${
filter === "construction" ? "bg-yellow-50" : ""
filter === "construction" ? "bg-warning/10" : ""
}`}
onClick={() => setFilter(filter === "construction" ? "all" : "construction")}
>
<div className="text-xs text-muted-foreground"></div>
<div className="text-2xl font-bold text-yellow-600">{stats.construction}</div>
<div className="text-2xl font-bold text-warning">{stats.construction}</div>
</Card>
</div>

View File

@ -141,7 +141,7 @@ export default function StatusSummaryWidget({
element,
title = "상태 요약",
icon = "📊",
bgGradient = "from-slate-50 to-blue-50",
bgGradient = "from-background to-primary/10",
statusConfig,
}: StatusSummaryWidgetProps) {
const [statusData, setStatusData] = useState<StatusData[]>([]);
@ -265,13 +265,13 @@ export default function StatusSummaryWidget({
}
const colorMap = {
blue: { border: "border-blue-500", dot: "bg-blue-500", text: "text-blue-600" },
green: { border: "border-green-500", dot: "bg-green-500", text: "text-green-600" },
red: { border: "border-red-500", dot: "bg-red-500", text: "text-red-600" },
yellow: { border: "border-yellow-500", dot: "bg-yellow-500", text: "text-yellow-600" },
orange: { border: "border-orange-500", dot: "bg-orange-500", text: "text-orange-600" },
purple: { border: "border-purple-500", dot: "bg-purple-500", text: "text-purple-600" },
gray: { border: "border-gray-500", dot: "bg-gray-500", text: "text-gray-600" },
blue: { border: "border-primary", dot: "bg-primary", text: "text-primary" },
green: { border: "border-success", dot: "bg-success", text: "text-success" },
red: { border: "border-destructive", dot: "bg-destructive", text: "text-destructive" },
yellow: { border: "border-warning", dot: "bg-warning", text: "text-warning" },
orange: { border: "border-warning", dot: "bg-warning", text: "text-warning" },
purple: { border: "border-purple-500", dot: "bg-purple-500/100", text: "text-purple-500" },
gray: { border: "border-border", dot: "bg-muted-foreground", text: "text-muted-foreground" },
};
return colorMap[color as keyof typeof colorMap] || colorMap.gray;
@ -282,7 +282,7 @@ export default function StatusSummaryWidget({
<div className="flex h-full items-center justify-center">
<div className="text-center">
<div className="border-primary mx-auto h-8 w-8 animate-spin rounded-full border-2 border-t-transparent" />
<p className="mt-2 text-sm text-gray-500"> ...</p>
<p className="mt-2 text-sm text-muted-foreground"> ...</p>
</div>
</div>
);
@ -291,11 +291,11 @@ export default function StatusSummaryWidget({
if (error) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center text-red-500">
<div className="text-center text-destructive">
<p className="text-sm"> {error}</p>
<button
onClick={loadData}
className="mt-2 rounded bg-red-100 px-3 py-1 text-xs text-red-700 hover:bg-red-200"
className="mt-2 rounded bg-destructive/10 px-3 py-1 text-xs text-destructive hover:bg-destructive/20"
>
</button>
@ -309,8 +309,8 @@ export default function StatusSummaryWidget({
<div className="flex h-full items-center justify-center p-3">
<div className="max-w-xs space-y-2 text-center">
<div className="text-3xl">{icon}</div>
<h3 className="text-sm font-bold text-gray-900">{title}</h3>
<div className="space-y-1.5 text-xs text-gray-600">
<h3 className="text-sm font-bold text-foreground">{title}</h3>
<div className="space-y-1.5 text-xs text-foreground">
<p className="font-medium">📊 </p>
<ul className="space-y-0.5 text-left">
<li> SQL </li>
@ -319,7 +319,7 @@ export default function StatusSummaryWidget({
<li> </li>
</ul>
</div>
<div className="mt-2 rounded-lg bg-blue-50 p-2 text-[10px] text-blue-700">
<div className="mt-2 rounded-lg bg-primary/10 p-2 text-[10px] text-primary">
<p className="font-medium"> </p>
<p>SQL </p>
</div>
@ -357,18 +357,18 @@ export default function StatusSummaryWidget({
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">
<h3 className="text-sm font-bold text-foreground">
{icon} {displayTitle}
</h3>
{totalCount > 0 ? (
<p className="text-xs text-gray-500"> {totalCount.toLocaleString()}</p>
<p className="text-xs text-muted-foreground"> {totalCount.toLocaleString()}</p>
) : (
<p className="text-xs text-orange-500"> </p>
<p className="text-xs text-warning"> </p>
)}
</div>
<button
onClick={loadData}
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-white p-0 text-xs disabled:opacity-50"
className="border-border hover:bg-accent flex h-7 w-7 items-center justify-center rounded border bg-background p-0 text-xs disabled:opacity-50"
disabled={loading}
>
{loading ? "⏳" : "🔄"}
@ -382,10 +382,10 @@ export default function StatusSummaryWidget({
{statusData.map((item) => {
const colors = getColorClasses(item.status);
return (
<div key={item.status} className="rounded border border-gray-200 bg-white p-1.5 shadow-sm">
<div key={item.status} className="rounded border border-border bg-background p-1.5 shadow-sm">
<div className="mb-0.5 flex items-center gap-1">
<div className={`h-1.5 w-1.5 rounded-full ${colors.dot}`}></div>
<div className="text-xs font-medium text-gray-600">{item.status}</div>
<div className="text-xs font-medium text-foreground">{item.status}</div>
</div>
<div className={`text-lg font-bold ${colors.text}`}>{item.count.toLocaleString()}</div>
</div>

View File

@ -481,10 +481,10 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
const getPriorityColor = (priority: TaskItem["priority"]) => {
switch (priority) {
case "urgent": return "bg-red-100 text-red-700 border-red-300";
case "high": return "bg-orange-100 text-orange-700 border-orange-300";
case "normal": return "bg-blue-100 text-blue-700 border-blue-300";
case "low": return "bg-gray-100 text-gray-700 border-gray-300";
case "urgent": return "bg-destructive/10 text-destructive border-destructive";
case "high": return "bg-warning/10 text-warning border-warning";
case "normal": return "bg-primary/10 text-primary border-primary";
case "low": return "bg-muted text-foreground border-border";
}
};
@ -549,22 +549,22 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-gray-500"> ...</div>
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-blue-50">
<div className="flex h-full flex-col bg-background">
{/* 제목 */}
<div className="border-b border-gray-200 bg-white px-4 py-2">
<div className="border-b border-border bg-background px-4 py-2">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-800">
<h3 className="text-lg font-bold text-foreground">
{element?.customTitle || "일정관리 위젯"}
</h3>
{selectedDate && (
<div className="mt-1 flex items-center gap-1 text-xs text-green-600">
<div className="mt-1 flex items-center gap-1 text-xs text-success">
<CalendarIcon className="h-3 w-3" />
<span className="font-semibold">{formatSelectedDate()} </span>
</div>
@ -582,24 +582,24 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
{/* 헤더 (통계, 필터) */}
{element?.showHeader !== false && (
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="border-b border-border bg-background px-4 py-3">
{stats && (
<div className="grid grid-cols-4 gap-2 text-xs mb-3">
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
<div className="font-bold text-blue-700">{stats.pending}</div>
<div className="text-blue-600"></div>
<div className="rounded bg-primary/10 px-2 py-1.5 text-center">
<div className="font-bold text-primary">{stats.pending}</div>
<div className="text-primary"></div>
</div>
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
<div className="font-bold text-amber-700">{stats.inProgress}</div>
<div className="text-amber-600"></div>
<div className="rounded bg-warning/10 px-2 py-1.5 text-center">
<div className="font-bold text-warning">{stats.inProgress}</div>
<div className="text-warning"></div>
</div>
<div className="rounded bg-red-50 px-2 py-1.5 text-center">
<div className="font-bold text-red-700">{stats.urgent}</div>
<div className="text-red-600"></div>
<div className="rounded bg-destructive/10 px-2 py-1.5 text-center">
<div className="font-bold text-destructive">{stats.urgent}</div>
<div className="text-destructive"></div>
</div>
<div className="rounded bg-rose-50 px-2 py-1.5 text-center">
<div className="font-bold text-rose-700">{stats.overdue}</div>
<div className="text-rose-600"></div>
<div className="rounded bg-destructive/10 px-2 py-1.5 text-center">
<div className="font-bold text-destructive">{stats.overdue}</div>
<div className="text-destructive"></div>
</div>
</div>
)}
@ -610,7 +610,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
key={f}
onClick={() => setFilter(f)}
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f ? "bg-primary text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"
filter === f ? "bg-primary text-white" : "bg-muted text-foreground hover:bg-muted"
}`}
>
{f === "all" ? "전체" : f === "pending" ? "대기" : f === "in_progress" ? "진행중" : "완료"}
@ -622,7 +622,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
{/* 추가 폼 */}
{showAddForm && (
<div className="max-h-[400px] overflow-y-auto border-b border-gray-200 bg-white p-4">
<div className="max-h-[400px] overflow-y-auto border-b border-border bg-background p-4">
<div className="space-y-2">
<input
type="text"
@ -630,21 +630,21 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })}
onKeyDown={(e) => e.stopPropagation()}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="w-full rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
<textarea
placeholder="상세 설명 (선택)"
value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })}
onKeyDown={(e) => e.stopPropagation()}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="w-full rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
rows={2}
/>
<div className="grid grid-cols-2 gap-2">
<select
value={newTask.priority}
onChange={(e) => setNewTask({ ...newTask, priority: e.target.value as TaskItem["priority"] })}
className="rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
>
<option value="low">🟢 </option>
<option value="normal">🟡 </option>
@ -655,7 +655,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
type="datetime-local"
value={newTask.dueDate}
onChange={(e) => setNewTask({ ...newTask, dueDate: e.target.value })}
className="rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
</div>
<div className="flex items-center gap-2">
@ -664,9 +664,9 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
type="checkbox"
checked={newTask.isUrgent}
onChange={(e) => setNewTask({ ...newTask, isUrgent: e.target.checked })}
className="h-4 w-4 rounded border-gray-300"
className="h-4 w-4 rounded border-border"
/>
<span className="text-red-600 font-medium"></span>
<span className="text-destructive font-medium"></span>
</label>
</div>
<div className="flex gap-2">
@ -678,7 +678,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
</button>
<button
onClick={() => setShowAddForm(false)}
className="rounded bg-gray-200 px-4 py-2 text-sm text-gray-700 hover:bg-gray-300"
className="rounded bg-muted px-4 py-2 text-sm text-foreground hover:bg-muted/90"
>
</button>
@ -690,7 +690,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
{/* Task 리스트 */}
<div className="flex-1 overflow-y-auto p-4 min-h-0">
{filteredTasks.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="flex h-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-4xl">📝</div>
<div>{selectedDate ? `${formatSelectedDate()} 일정이 없습니다` : `일정이 없습니다`}</div>
@ -701,8 +701,8 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
{filteredTasks.map((task) => (
<div
key={task.id}
className={`group relative rounded-lg border-2 bg-white p-3 shadow-sm transition-all hover:shadow-md ${
task.isUrgent || task.status === "overdue" ? "border-red-400" : "border-gray-200"
className={`group relative rounded-lg border-2 bg-background p-3 shadow-sm transition-all hover:shadow-md ${
task.isUrgent || task.status === "overdue" ? "border-destructive" : "border-border"
} ${task.status === "completed" ? "opacity-60" : ""}`}
>
<div className="flex items-start gap-3">
@ -716,26 +716,26 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className={`font-medium ${task.status === "completed" ? "line-through" : ""}`}>
{task.isUrgent && <span className="mr-1 text-red-600"></span>}
{task.isUrgent && <span className="mr-1 text-destructive"></span>}
{task.vehicleNumber ? (
<>
<span className="font-bold">{task.vehicleNumber}</span>
{task.vehicleType && <span className="ml-2 text-xs text-gray-600">({task.vehicleType})</span>}
{task.vehicleType && <span className="ml-2 text-xs text-foreground">({task.vehicleType})</span>}
</>
) : (
task.title
)}
</div>
{task.maintenanceType && (
<div className="mt-1 rounded bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700">
<div className="mt-1 rounded bg-muted px-2 py-1 text-xs font-medium text-foreground">
{task.maintenanceType}
</div>
)}
{task.description && (
<div className="mt-1 text-xs text-gray-600">{task.description}</div>
<div className="mt-1 text-xs text-foreground">{task.description}</div>
)}
{task.dueDate && (
<div className="mt-1 text-xs text-gray-500">{getTimeRemaining(task.dueDate)}</div>
<div className="mt-1 text-xs text-muted-foreground">{getTimeRemaining(task.dueDate)}</div>
)}
{task.estimatedCost && (
<div className="mt-1 text-xs font-bold text-primary">
@ -749,7 +749,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
{task.status !== "completed" && (
<button
onClick={() => handleUpdateStatus(task.id, "completed")}
className="rounded p-1 text-green-600 hover:bg-green-50"
className="rounded p-1 text-success hover:bg-success/10"
title="완료"
>
<Check className="h-4 w-4" />
@ -757,7 +757,7 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
)}
<button
onClick={() => handleDelete(task.id)}
className="rounded p-1 text-red-600 hover:bg-red-50"
className="rounded p-1 text-destructive hover:bg-destructive/10"
title="삭제"
>
<X className="h-4 w-4" />
@ -772,8 +772,8 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
onClick={() => handleUpdateStatus(task.id, "pending")}
className={`rounded px-2 py-1 text-xs ${
task.status === "pending"
? "bg-blue-100 text-blue-700"
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
? "bg-primary/10 text-primary"
: "bg-muted text-foreground hover:bg-muted"
}`}
>
@ -782,8 +782,8 @@ export default function TaskWidget({ element }: TaskWidgetProps) {
onClick={() => handleUpdateStatus(task.id, "in_progress")}
className={`rounded px-2 py-1 text-xs ${
task.status === "in_progress"
? "bg-amber-100 text-amber-700"
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
? "bg-warning/10 text-warning"
: "bg-muted text-foreground hover:bg-muted"
}`}
>

View File

@ -267,13 +267,13 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
const getPriorityColor = (priority: TodoItem["priority"]) => {
switch (priority) {
case "urgent":
return "bg-red-100 text-red-700 border-red-300";
return "bg-destructive/10 text-destructive border-destructive";
case "high":
return "bg-orange-100 text-orange-700 border-orange-300";
return "bg-warning/10 text-warning border-warning";
case "normal":
return "bg-blue-100 text-blue-700 border-blue-300";
return "bg-primary/10 text-primary border-primary";
case "low":
return "bg-gray-100 text-gray-700 border-gray-300";
return "bg-muted text-foreground border-border";
}
};
@ -327,20 +327,20 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
if (loading) {
return (
<div className="flex h-full items-center justify-center">
<div className="text-gray-500"> ...</div>
<div className="text-muted-foreground"> ...</div>
</div>
);
}
return (
<div className="flex h-full flex-col bg-gradient-to-br from-slate-50 to-blue-50">
<div className="flex h-full flex-col bg-background">
{/* 제목 - 항상 표시 */}
<div className="border-b border-gray-200 bg-white px-4 py-2">
<div className="border-b border-border bg-background px-4 py-2">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-800">{element?.customTitle || "To-Do / 긴급 지시"}</h3>
<h3 className="text-lg font-bold text-foreground">{element?.customTitle || "To-Do / 긴급 지시"}</h3>
{selectedDate && (
<div className="mt-1 flex items-center gap-1 text-xs text-green-600">
<div className="mt-1 flex items-center gap-1 text-xs text-success">
<CalendarIcon className="h-3 w-3" />
<span className="font-semibold">{formatSelectedDate()} </span>
</div>
@ -360,25 +360,25 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
{/* 헤더 (통계, 필터) - showHeader가 false일 때만 숨김 */}
{element?.showHeader !== false && (
<div className="border-b border-gray-200 bg-white px-4 py-3">
<div className="border-b border-border bg-background px-4 py-3">
{/* 통계 */}
{stats && (
<div className="grid grid-cols-4 gap-2 text-xs mb-3">
<div className="rounded bg-blue-50 px-2 py-1.5 text-center">
<div className="font-bold text-blue-700">{stats.pending}</div>
<div className="text-blue-600"></div>
<div className="rounded bg-primary/10 px-2 py-1.5 text-center">
<div className="font-bold text-primary">{stats.pending}</div>
<div className="text-primary"></div>
</div>
<div className="rounded bg-amber-50 px-2 py-1.5 text-center">
<div className="font-bold text-amber-700">{stats.inProgress}</div>
<div className="text-amber-600"></div>
<div className="rounded bg-warning/10 px-2 py-1.5 text-center">
<div className="font-bold text-warning">{stats.inProgress}</div>
<div className="text-warning"></div>
</div>
<div className="rounded bg-red-50 px-2 py-1.5 text-center">
<div className="font-bold text-red-700">{stats.urgent}</div>
<div className="text-red-600"></div>
<div className="rounded bg-destructive/10 px-2 py-1.5 text-center">
<div className="font-bold text-destructive">{stats.urgent}</div>
<div className="text-destructive"></div>
</div>
<div className="rounded bg-rose-50 px-2 py-1.5 text-center">
<div className="font-bold text-rose-700">{stats.overdue}</div>
<div className="text-rose-600"></div>
<div className="rounded bg-destructive/10 px-2 py-1.5 text-center">
<div className="font-bold text-destructive">{stats.overdue}</div>
<div className="text-destructive"></div>
</div>
</div>
)}
@ -392,7 +392,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
className={`rounded px-3 py-1 text-xs font-medium transition-colors ${
filter === f
? "bg-primary text-white"
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
: "bg-muted text-foreground hover:bg-muted"
}`}
>
{f === "all" ? "전체" : f === "pending" ? "대기" : f === "in_progress" ? "진행중" : "완료"}
@ -404,7 +404,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
{/* 추가 폼 */}
{showAddForm && (
<div className="max-h-[400px] overflow-y-auto border-b border-gray-200 bg-white p-4">
<div className="max-h-[400px] overflow-y-auto border-b border-border bg-background p-4">
<div className="space-y-2">
<input
type="text"
@ -412,21 +412,21 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
value={newTodo.title}
onChange={(e) => setNewTodo({ ...newTodo, title: e.target.value })}
onKeyDown={(e) => e.stopPropagation()}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="w-full rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
<textarea
placeholder="상세 설명 (선택)"
value={newTodo.description}
onChange={(e) => setNewTodo({ ...newTodo, description: e.target.value })}
onKeyDown={(e) => e.stopPropagation()}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="w-full rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
rows={2}
/>
<div className="grid grid-cols-2 gap-2">
<select
value={newTodo.priority}
onChange={(e) => setNewTodo({ ...newTodo, priority: e.target.value as TodoItem["priority"] })}
className="rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
>
<option value="low">🟢 </option>
<option value="normal">🟡 </option>
@ -437,7 +437,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
type="datetime-local"
value={newTodo.dueDate}
onChange={(e) => setNewTodo({ ...newTodo, dueDate: e.target.value })}
className="rounded border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
className="rounded border border-border px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
</div>
<div className="flex items-center gap-2">
@ -446,9 +446,9 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
type="checkbox"
checked={newTodo.isUrgent}
onChange={(e) => setNewTodo({ ...newTodo, isUrgent: e.target.checked })}
className="h-4 w-4 rounded border-gray-300"
className="h-4 w-4 rounded border-border"
/>
<span className="text-red-600 font-medium"> </span>
<span className="text-destructive font-medium"> </span>
</label>
</div>
<div className="flex gap-2">
@ -460,7 +460,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
</button>
<button
onClick={() => setShowAddForm(false)}
className="rounded bg-gray-200 px-4 py-2 text-sm text-gray-700 hover:bg-gray-300"
className="rounded bg-muted px-4 py-2 text-sm text-foreground hover:bg-muted/90"
>
</button>
@ -472,7 +472,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
{/* To-Do 리스트 */}
<div className="flex-1 overflow-y-auto p-4 min-h-0">
{filteredTodos.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-400">
<div className="flex h-full items-center justify-center text-muted-foreground">
<div className="text-center">
<div className="mb-2 text-4xl">📝</div>
<div>{selectedDate ? `${formatSelectedDate()} 할 일이 없습니다` : "할 일이 없습니다"}</div>
@ -483,8 +483,8 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
{filteredTodos.map((todo) => (
<div
key={todo.id}
className={`group relative rounded-lg border-2 bg-white p-3 shadow-sm transition-all hover:shadow-md ${
todo.isUrgent ? "border-red-400" : "border-gray-200"
className={`group relative rounded-lg border-2 bg-background p-3 shadow-sm transition-all hover:shadow-md ${
todo.isUrgent ? "border-destructive" : "border-border"
} ${todo.status === "completed" ? "opacity-60" : ""}`}
>
<div className="flex items-start gap-3">
@ -496,14 +496,14 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className={`font-medium ${todo.status === "completed" ? "line-through" : ""}`}>
{todo.isUrgent && <span className="mr-1 text-red-600"></span>}
{todo.isUrgent && <span className="mr-1 text-destructive"></span>}
{todo.title}
</div>
{todo.description && (
<div className="mt-1 text-xs text-gray-600">{todo.description}</div>
<div className="mt-1 text-xs text-foreground">{todo.description}</div>
)}
{todo.dueDate && (
<div className="mt-1 text-xs text-gray-500">{getTimeRemaining(todo.dueDate)}</div>
<div className="mt-1 text-xs text-muted-foreground">{getTimeRemaining(todo.dueDate)}</div>
)}
</div>
@ -512,7 +512,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
{todo.status !== "completed" && (
<button
onClick={() => handleUpdateStatus(todo.id, "completed")}
className="rounded p-1 text-green-600 hover:bg-green-50"
className="rounded p-1 text-success hover:bg-success/10"
title="완료"
>
<Check className="h-4 w-4" />
@ -520,7 +520,7 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
)}
<button
onClick={() => handleDelete(todo.id)}
className="rounded p-1 text-red-600 hover:bg-red-50"
className="rounded p-1 text-destructive hover:bg-destructive/10"
title="삭제"
>
<X className="h-4 w-4" />
@ -535,8 +535,8 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
onClick={() => handleUpdateStatus(todo.id, "pending")}
className={`rounded px-2 py-1 text-xs ${
todo.status === "pending"
? "bg-blue-100 text-blue-700"
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
? "bg-primary/10 text-primary"
: "bg-muted text-foreground hover:bg-muted"
}`}
>
@ -545,8 +545,8 @@ export default function TodoWidget({ element }: TodoWidgetProps) {
onClick={() => handleUpdateStatus(todo.id, "in_progress")}
className={`rounded px-2 py-1 text-xs ${
todo.status === "in_progress"
? "bg-amber-100 text-amber-700"
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
? "bg-warning/10 text-warning"
: "bg-muted text-foreground hover:bg-muted"
}`}
>

View File

@ -247,10 +247,10 @@ export default function TransportStatsWidget({ element, refreshInterval = 60000
if (isLoading && !stats) {
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="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
<div className="mt-2 text-sm text-gray-600"> ...</div>
<div className="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="mt-2 text-sm text-foreground"> ...</div>
</div>
</div>
);
@ -258,14 +258,14 @@ export default function TransportStatsWidget({ element, refreshInterval = 60000
if (error || !stats) {
return (
<div className="flex h-full items-center justify-center bg-gray-50 p-4">
<div className="flex h-full items-center justify-center bg-muted p-4">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600">{error || "데이터 없음"}</div>
{!element?.dataSource?.query && <div className="mt-2 text-xs text-gray-500"> </div>}
<div className="text-sm font-medium text-foreground">{error || "데이터 없음"}</div>
{!element?.dataSource?.query && <div className="mt-2 text-xs text-muted-foreground"> </div>}
<button
onClick={loadData}
className="mt-3 rounded-lg bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
className="mt-3 rounded-lg bg-primary px-4 py-2 text-sm text-white hover:bg-primary/90"
>
</button>
@ -275,48 +275,48 @@ export default function TransportStatsWidget({ element, refreshInterval = 60000
}
return (
<div className="flex h-full items-center justify-center bg-white p-6">
<div className="flex h-full items-center justify-center bg-background p-6">
<div className="grid w-full gap-4" style={{ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))" }}>
{/* 총 건수 */}
<div className="rounded-lg border bg-indigo-50 p-4 text-center">
<div className="text-sm text-gray-600"> </div>
<div className="mt-2 text-3xl font-bold text-indigo-600">
<div className="rounded-lg border bg-primary/10 p-4 text-center">
<div className="text-sm text-foreground"> </div>
<div className="mt-2 text-3xl font-bold text-primary">
{stats.total_count.toLocaleString()}
<span className="ml-1 text-lg"></span>
</div>
</div>
{/* 총 운송량 */}
<div className="rounded-lg border bg-green-50 p-4 text-center">
<div className="text-sm text-gray-600"> </div>
<div className="mt-2 text-3xl font-bold text-green-600">
<div className="rounded-lg border bg-success/10 p-4 text-center">
<div className="text-sm text-foreground"> </div>
<div className="mt-2 text-3xl font-bold text-success">
{stats.total_weight.toFixed(1)}
<span className="ml-1 text-lg"></span>
</div>
</div>
{/* 누적 거리 */}
<div className="rounded-lg border bg-blue-50 p-4 text-center">
<div className="text-sm text-gray-600"> </div>
<div className="mt-2 text-3xl font-bold text-blue-600">
<div className="rounded-lg border bg-primary/10 p-4 text-center">
<div className="text-sm text-foreground"> </div>
<div className="mt-2 text-3xl font-bold text-primary">
{stats.total_distance.toFixed(1)}
<span className="ml-1 text-lg">km</span>
</div>
</div>
{/* 정시 도착률 */}
<div className="rounded-lg border bg-purple-50 p-4 text-center">
<div className="text-sm text-gray-600"> </div>
<div className="mt-2 text-3xl font-bold text-purple-600">
<div className="rounded-lg border bg-purple-500/10 p-4 text-center">
<div className="text-sm text-foreground"> </div>
<div className="mt-2 text-3xl font-bold text-purple-500">
{stats.on_time_rate.toFixed(1)}
<span className="ml-1 text-lg">%</span>
</div>
</div>
{/* 평균 배송시간 */}
<div className="rounded-lg border bg-orange-50 p-4 text-center">
<div className="text-sm text-gray-600"> </div>
<div className="mt-2 text-3xl font-bold text-orange-600">
<div className="rounded-lg border bg-warning/10 p-4 text-center">
<div className="text-sm text-foreground"> </div>
<div className="mt-2 text-3xl font-bold text-warning">
{stats.avg_delivery_time.toFixed(1)}
<span className="ml-1 text-lg"></span>
</div>

View File

@ -74,11 +74,11 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
const getStatusColor = (status: string) => {
const s = status?.toLowerCase() || "";
if (s === "active" || s === "running") return "bg-green-500";
if (s === "inactive" || s === "idle") return "bg-yellow-500";
if (s === "maintenance") return "bg-orange-500";
if (s === "warning" || s === "breakdown") return "bg-red-500";
return "bg-gray-500";
if (s === "active" || s === "running") return "bg-success";
if (s === "inactive" || s === "idle") return "bg-warning/100";
if (s === "maintenance") return "bg-warning";
if (s === "warning" || s === "breakdown") return "bg-destructive";
return "bg-muted0";
};
const getStatusText = (status: string) => {
@ -94,12 +94,12 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
selectedStatus === "all" ? vehicles : vehicles.filter((v) => v.status?.toLowerCase() === selectedStatus);
return (
<div className="flex h-full w-full flex-col bg-gradient-to-br from-slate-50 to-blue-50 p-4">
<div className="flex h-full w-full flex-col bg-gradient-to-br from-background to-primary/10 p-4">
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-900"> </h3>
<p className="text-xs text-gray-500"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
<h3 className="text-lg font-bold text-foreground"> </h3>
<p className="text-xs text-muted-foreground"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
</div>
<Button variant="outline" size="sm" onClick={loadVehicles} disabled={isLoading} className="h-8 w-8 p-0">
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
@ -111,7 +111,7 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
<button
onClick={() => setSelectedStatus("all")}
className={`rounded-md px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors ${
selectedStatus === "all" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-100"
selectedStatus === "all" ? "bg-gray-900 text-white" : "bg-background text-foreground hover:bg-muted"
}`}
>
({vehicles.length})
@ -119,7 +119,7 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
<button
onClick={() => setSelectedStatus("active")}
className={`rounded-md px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors ${
selectedStatus === "active" ? "bg-green-500 text-white" : "bg-white text-gray-700 hover:bg-gray-100"
selectedStatus === "active" ? "bg-success text-white" : "bg-background text-foreground hover:bg-muted"
}`}
>
({vehicles.filter((v) => v.status?.toLowerCase() === "active").length})
@ -127,7 +127,7 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
<button
onClick={() => setSelectedStatus("inactive")}
className={`rounded-md px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors ${
selectedStatus === "inactive" ? "bg-yellow-500 text-white" : "bg-white text-gray-700 hover:bg-gray-100"
selectedStatus === "inactive" ? "bg-warning/100 text-white" : "bg-background text-foreground hover:bg-muted"
}`}
>
({vehicles.filter((v) => v.status?.toLowerCase() === "inactive").length})
@ -135,7 +135,7 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
<button
onClick={() => setSelectedStatus("maintenance")}
className={`rounded-md px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors ${
selectedStatus === "maintenance" ? "bg-orange-500 text-white" : "bg-white text-gray-700 hover:bg-gray-100"
selectedStatus === "maintenance" ? "bg-warning text-white" : "bg-background text-foreground hover:bg-muted"
}`}
>
({vehicles.filter((v) => v.status?.toLowerCase() === "maintenance").length})
@ -143,7 +143,7 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
<button
onClick={() => setSelectedStatus("warning")}
className={`rounded-md px-3 py-1 text-xs font-medium whitespace-nowrap transition-colors ${
selectedStatus === "warning" ? "bg-red-500 text-white" : "bg-white text-gray-700 hover:bg-gray-100"
selectedStatus === "warning" ? "bg-destructive text-white" : "bg-background text-foreground hover:bg-muted"
}`}
>
({vehicles.filter((v) => v.status?.toLowerCase() === "warning").length})
@ -153,10 +153,10 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
{/* 차량 목록 */}
<div className="flex-1 overflow-y-auto">
{filteredVehicles.length === 0 ? (
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-white">
<div className="flex h-full items-center justify-center rounded-lg border-2 border-dashed border-border bg-background">
<div className="text-center">
<Truck className="mx-auto h-12 w-12 text-gray-300" />
<p className="mt-2 text-sm text-gray-500"> </p>
<Truck className="mx-auto h-12 w-12 text-muted-foreground" />
<p className="mt-2 text-sm text-muted-foreground"> </p>
</div>
</div>
) : (
@ -164,12 +164,12 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
{filteredVehicles.map((vehicle) => (
<div
key={vehicle.id}
className="rounded-lg border border-gray-200 bg-white p-3 shadow-sm transition-all hover:shadow-md"
className="rounded-lg border border-border bg-background p-3 shadow-sm transition-all hover:shadow-md"
>
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<Truck className="h-4 w-4 text-gray-600" />
<span className="font-semibold text-gray-900">{vehicle.vehicle_name}</span>
<Truck className="h-4 w-4 text-foreground" />
<span className="font-semibold text-foreground">{vehicle.vehicle_name}</span>
</div>
<span
className={`rounded-full px-2 py-0.5 text-xs font-semibold text-white ${getStatusColor(vehicle.status)}`}
@ -178,22 +178,22 @@ export default function VehicleListWidget({ element, refreshInterval = 30000 }:
</span>
</div>
<div className="space-y-1 text-xs text-gray-600">
<div className="space-y-1 text-xs text-foreground">
<div className="flex items-center justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-mono font-medium">{vehicle.vehicle_number}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-gray-500"></span>
<span className="text-muted-foreground"></span>
<span className="font-medium">{vehicle.driver_name || "미배정"}</span>
</div>
<div className="flex items-center gap-1">
<Navigation className="h-3 w-3 text-gray-400" />
<span className="flex-1 truncate text-gray-700">{vehicle.destination || "대기 중"}</span>
<Navigation className="h-3 w-3 text-muted-foreground" />
<span className="flex-1 truncate text-foreground">{vehicle.destination || "대기 중"}</span>
</div>
<div className="flex items-center gap-1">
<Gauge className="h-3 w-3 text-gray-400" />
<span className="text-gray-700">{vehicle.speed || 0} km/h</span>
<Gauge className="h-3 w-3 text-muted-foreground" />
<span className="text-foreground">{vehicle.speed || 0} km/h</span>
</div>
</div>
</div>

View File

@ -164,12 +164,12 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
};
return (
<div className="h-full w-full bg-gradient-to-br from-slate-50 to-blue-50 p-4">
<div className="h-full w-full bg-gradient-to-br from-background to-primary/10 p-4">
{/* 헤더 */}
<div className="mb-3 flex items-center justify-between">
<div>
<h3 className="text-lg font-bold text-gray-900">🗺 </h3>
<p className="text-xs text-gray-500"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
<h3 className="text-lg font-bold text-foreground">🗺 </h3>
<p className="text-xs text-muted-foreground"> : {lastUpdate.toLocaleTimeString("ko-KR")}</p>
</div>
<Button variant="outline" size="sm" onClick={loadVehicles} disabled={isLoading} className="h-8 w-8 p-0">
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
@ -178,7 +178,7 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
{/* 지도 영역 - 브이월드 타일맵 */}
<div className="h-[calc(100%-60px)]">
<div className="relative z-0 h-full overflow-hidden rounded-lg border-2 border-gray-300 bg-white">
<div className="relative z-0 h-full overflow-hidden rounded-lg border-2 border-border bg-background">
<MapContainer
key={`vehicle-map-${element.id}`}
center={[36.5, 127.5]}
@ -235,19 +235,19 @@ export default function VehicleMapOnlyWidget({ element, refreshInterval = 30000
</MapContainer>
{/* 지도 정보 */}
<div className="absolute top-2 right-2 z-[1000] rounded-lg bg-white/90 p-2 shadow-lg backdrop-blur-sm">
<div className="text-xs text-gray-600">
<div className="absolute top-2 right-2 z-[1000] rounded-lg bg-background/90 p-2 shadow-lg backdrop-blur-sm">
<div className="text-xs text-foreground">
<div className="mb-1 font-semibold">🗺 (VWorld)</div>
<div className="text-xs"> </div>
</div>
</div>
{/* 차량 수 표시 또는 설정 안내 */}
<div className="absolute bottom-2 left-2 z-[1000] rounded-lg bg-white/90 p-2 shadow-lg backdrop-blur-sm">
<div className="absolute bottom-2 left-2 z-[1000] rounded-lg bg-background/90 p-2 shadow-lg backdrop-blur-sm">
{vehicles.length > 0 ? (
<div className="text-xs font-semibold text-gray-900"> {vehicles.length} </div>
<div className="text-xs font-semibold text-foreground"> {vehicles.length} </div>
) : (
<div className="text-xs text-gray-600"> </div>
<div className="text-xs text-foreground"> </div>
)}
</div>
</div>

View File

@ -124,15 +124,15 @@ export default function VehicleStatusWidget({ element, refreshInterval = 30000 }
const activeRate = statusData.total > 0 ? ((statusData.active / statusData.total) * 100).toFixed(1) : "0";
return (
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-slate-50 to-green-50 p-2">
<div className="flex h-full w-full flex-col overflow-hidden bg-gradient-to-br from-background to-success/10 p-2">
{/* 헤더 */}
<div className="mb-2 flex flex-shrink-0 items-center justify-between">
<div className="flex-1">
<h3 className="text-sm font-bold text-gray-900">📊 </h3>
<h3 className="text-sm font-bold text-foreground">📊 </h3>
{statusData.total > 0 ? (
<p className="text-xs text-gray-500">{lastUpdate.toLocaleTimeString("ko-KR")}</p>
<p className="text-xs text-muted-foreground">{lastUpdate.toLocaleTimeString("ko-KR")}</p>
) : (
<p className="text-xs text-orange-500"> </p>
<p className="text-xs text-warning"> </p>
)}
</div>
<Button variant="outline" size="sm" onClick={loadStatusData} disabled={isLoading} className="h-7 w-7 p-0">
@ -143,15 +143,15 @@ export default function VehicleStatusWidget({ element, refreshInterval = 30000 }
{/* 스크롤 가능한 콘텐츠 영역 */}
<div className="flex-1 overflow-y-auto">
{/* 총 차량 수 */}
<div className="mb-1 rounded border border-gray-200 bg-white p-1.5 shadow-sm">
<div className="mb-1 rounded border border-border bg-background p-1.5 shadow-sm">
<div className="flex items-center justify-between">
<div>
<div className="text-xs text-gray-600"> </div>
<div className="text-base font-bold text-gray-900">{statusData.total}</div>
<div className="text-xs text-foreground"> </div>
<div className="text-base font-bold text-foreground">{statusData.total}</div>
</div>
<div className="text-right">
<div className="text-xs text-gray-600"></div>
<div className="flex items-center gap-0.5 text-sm font-bold text-green-600">{activeRate}%</div>
<div className="text-xs text-foreground"></div>
<div className="flex items-center gap-0.5 text-sm font-bold text-success">{activeRate}%</div>
</div>
</div>
</div>
@ -159,39 +159,39 @@ export default function VehicleStatusWidget({ element, refreshInterval = 30000 }
{/* 상태별 카드 */}
<div className="grid grid-cols-2 gap-1.5">
{/* 운행 중 */}
<div className="rounded border-l-2 border-green-500 bg-white p-1.5 shadow-sm">
<div className="rounded border-l-2 border-success bg-background p-1.5 shadow-sm">
<div className="mb-0.5 flex items-center gap-1">
<div className="h-1.5 w-1.5 rounded-full bg-green-500"></div>
<div className="text-xs font-medium text-gray-600"></div>
<div className="h-1.5 w-1.5 rounded-full bg-success"></div>
<div className="text-xs font-medium text-foreground"></div>
</div>
<div className="text-lg font-bold text-green-600">{statusData.active}</div>
<div className="text-lg font-bold text-success">{statusData.active}</div>
</div>
{/* 대기 */}
<div className="rounded border-l-2 border-yellow-500 bg-white p-1.5 shadow-sm">
<div className="rounded border-l-2 border-warning bg-background p-1.5 shadow-sm">
<div className="mb-0.5 flex items-center gap-1">
<div className="h-1.5 w-1.5 rounded-full bg-yellow-500"></div>
<div className="text-xs font-medium text-gray-600"></div>
<div className="h-1.5 w-1.5 rounded-full bg-warning/100"></div>
<div className="text-xs font-medium text-foreground"></div>
</div>
<div className="text-lg font-bold text-yellow-600">{statusData.inactive}</div>
<div className="text-lg font-bold text-warning">{statusData.inactive}</div>
</div>
{/* 정비 */}
<div className="rounded border-l-2 border-orange-500 bg-white p-1.5 shadow-sm">
<div className="rounded border-l-2 border-warning bg-background p-1.5 shadow-sm">
<div className="mb-0.5 flex items-center gap-1">
<div className="h-1.5 w-1.5 rounded-full bg-orange-500"></div>
<div className="text-xs font-medium text-gray-600"></div>
<div className="h-1.5 w-1.5 rounded-full bg-warning"></div>
<div className="text-xs font-medium text-foreground"></div>
</div>
<div className="text-lg font-bold text-orange-600">{statusData.maintenance}</div>
<div className="text-lg font-bold text-warning">{statusData.maintenance}</div>
</div>
{/* 고장 */}
<div className="rounded border-l-2 border-red-500 bg-white p-1.5 shadow-sm">
<div className="rounded border-l-2 border-destructive bg-background p-1.5 shadow-sm">
<div className="mb-0.5 flex items-center gap-1">
<div className="h-1.5 w-1.5 rounded-full bg-red-500"></div>
<div className="text-xs font-medium text-gray-600"></div>
<div className="h-1.5 w-1.5 rounded-full bg-destructive"></div>
<div className="text-xs font-medium text-foreground"></div>
</div>
<div className="text-lg font-bold text-red-600">{statusData.warning}</div>
<div className="text-lg font-bold text-destructive">{statusData.warning}</div>
</div>
</div>
</div>

View File

@ -38,15 +38,15 @@ interface WeatherMapWidgetProps {
const getWeatherIcon = (weatherMain: string) => {
switch (weatherMain.toLowerCase()) {
case "clear":
return <Sun className="h-6 w-6 text-yellow-500" />;
return <Sun className="h-6 w-6 text-warning" />;
case "rain":
return <CloudRain className="h-6 w-6 text-blue-500" />;
return <CloudRain className="h-6 w-6 text-primary" />;
case "snow":
return <CloudSnow className="h-6 w-6 text-blue-300" />;
return <CloudSnow className="h-6 w-6 text-primary/70" />;
case "clouds":
return <Cloud className="h-6 w-6 text-gray-400" />;
return <Cloud className="h-6 w-6 text-muted-foreground" />;
default:
return <Wind className="h-6 w-6 text-gray-500" />;
return <Wind className="h-6 w-6 text-muted-foreground" />;
}
};

View File

@ -263,28 +263,28 @@ export default function WeatherWidget({
const getWeatherIcon = (weatherMain: string) => {
switch (weatherMain.toLowerCase()) {
case 'clear':
return <Sun className="h-12 w-12 text-yellow-500" />;
return <Sun className="h-12 w-12 text-warning" />;
case 'clouds':
return <Cloud className="h-12 w-12 text-gray-400" />;
return <Cloud className="h-12 w-12 text-muted-foreground" />;
case 'rain':
case 'drizzle':
return <CloudRain className="h-12 w-12 text-blue-500" />;
return <CloudRain className="h-12 w-12 text-primary" />;
case 'snow':
return <CloudSnow className="h-12 w-12 text-blue-300" />;
return <CloudSnow className="h-12 w-12 text-primary/70" />;
default:
return <Cloud className="h-12 w-12 text-gray-400" />;
return <Cloud className="h-12 w-12 text-muted-foreground" />;
}
};
// 로딩 상태
if (loading && !weather) {
return (
<div className="flex h-full items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border p-6">
<div className="flex h-full items-center justify-center bg-background rounded-lg border p-6">
<div className="flex flex-col items-center gap-3">
<RefreshCw className="h-8 w-8 animate-spin text-blue-500" />
<RefreshCw className="h-8 w-8 animate-spin text-primary" />
<div className="text-center">
<p className="text-sm font-semibold text-gray-800 mb-1"> API ...</p>
<p className="text-xs text-gray-500"> </p>
<p className="text-sm font-semibold text-foreground mb-1"> API ...</p>
<p className="text-xs text-muted-foreground"> </p>
</div>
</div>
</div>
@ -295,21 +295,17 @@ export default function WeatherWidget({
if (error || !weather) {
const isTestMode = error?.includes('API 키가 설정되지 않았습니다');
return (
<div className={`flex h-full flex-col items-center justify-center rounded-lg border p-6 ${
isTestMode
? 'bg-gradient-to-br from-yellow-50 to-orange-50'
: 'bg-gradient-to-br from-red-50 to-orange-50'
}`}>
<Cloud className="h-12 w-12 text-gray-400 mb-2" />
<div className="flex h-full flex-col items-center justify-center rounded-lg border p-6 bg-background">
<Cloud className="h-12 w-12 text-muted-foreground mb-2" />
<div className="text-center mb-3">
<p className="text-sm font-semibold text-gray-800 mb-1">
<p className="text-sm font-semibold text-foreground mb-1">
{isTestMode ? '⚠️ 테스트 모드' : '❌ 연결 실패'}
</p>
<p className="text-xs text-gray-600">
<p className="text-xs text-foreground">
{error || '날씨 정보를 불러올 수 없습니다.'}
</p>
{isTestMode && (
<p className="text-xs text-yellow-700 mt-2">
<p className="text-xs text-warning mt-2">
</p>
)}
@ -328,11 +324,11 @@ export default function WeatherWidget({
}
return (
<div className="h-full bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg border p-4">
<div className="h-full bg-background rounded-lg border p-4">
{/* 헤더 */}
<div className="flex items-center justify-between mb-3">
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 mb-1">🌤 {element?.customTitle || "날씨"}</h3>
<h3 className="text-lg font-semibold text-foreground mb-1">🌤 {element?.customTitle || "날씨"}</h3>
<div className="flex items-center gap-2 mb-1">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
@ -340,7 +336,7 @@ export default function WeatherWidget({
variant="ghost"
role="combobox"
aria-expanded={open}
className="justify-between text-sm text-gray-600 hover:bg-white/50 h-auto py-0.5 px-2"
className="justify-between text-sm text-foreground hover:bg-muted/80 h-auto py-0.5 px-2"
>
{cities.find((city) => city.value === selectedCity)?.label || '도시 선택'}
<ChevronsUpDown className="ml-2 h-3.5 w-3.5 shrink-0 opacity-50" />
@ -376,7 +372,7 @@ export default function WeatherWidget({
</PopoverContent>
</Popover>
</div>
<p className="text-xs text-gray-500 pl-2">
<p className="text-xs text-muted-foreground pl-2">
{lastUpdated
? `업데이트: ${lastUpdated.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -397,7 +393,7 @@ export default function WeatherWidget({
</PopoverTrigger>
<PopoverContent className="w-[200px] p-3" align="end">
<div className="space-y-2">
<h4 className="text-sm font-semibold text-gray-900 mb-3"> </h4>
<h4 className="text-sm font-semibold text-foreground mb-3"> </h4>
{weatherItems.map((item) => {
const Icon = item.icon;
return (
@ -407,8 +403,8 @@ export default function WeatherWidget({
className={cn(
'w-full flex items-center gap-2 px-2 py-1.5 rounded text-xs transition-colors',
selectedItems.includes(item.id)
? 'bg-blue-50 text-blue-700'
: 'text-gray-600 hover:bg-gray-50'
? 'bg-primary/10 text-primary'
: 'text-foreground hover:bg-muted'
)}
>
<Check
@ -439,31 +435,31 @@ export default function WeatherWidget({
{/* 반응형 그리드 레이아웃 - 자동 조정 */}
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))' }}>
{/* 날씨 아이콘 및 온도 */}
<div className="bg-white/50 rounded-lg p-3">
<div className="bg-muted/80 rounded-lg p-3">
<div className="flex items-center gap-1.5">
<div className="flex-shrink-0">
{(() => {
const iconClass = "h-5 w-5";
switch (weather.weatherMain.toLowerCase()) {
case 'clear':
return <Sun className={`${iconClass} text-yellow-500`} />;
return <Sun className={`${iconClass} text-warning`} />;
case 'clouds':
return <Cloud className={`${iconClass} text-gray-400`} />;
return <Cloud className={`${iconClass} text-muted-foreground`} />;
case 'rain':
case 'drizzle':
return <CloudRain className={`${iconClass} text-blue-500`} />;
return <CloudRain className={`${iconClass} text-primary`} />;
case 'snow':
return <CloudSnow className={`${iconClass} text-blue-300`} />;
return <CloudSnow className={`${iconClass} text-primary/70`} />;
default:
return <Cloud className={`${iconClass} text-gray-400`} />;
return <Cloud className={`${iconClass} text-muted-foreground`} />;
}
})()}
</div>
<div className="min-w-0 flex-1">
<div className="text-sm font-bold text-gray-900 leading-tight truncate">
<div className="text-sm font-bold text-foreground leading-tight truncate">
{weather.temperature}°C
</div>
<p className="text-xs text-gray-400 capitalize leading-tight truncate">
<p className="text-xs text-muted-foreground capitalize leading-tight truncate">
{weather.weatherDescription}
</p>
</div>
@ -472,11 +468,11 @@ export default function WeatherWidget({
{/* 기온 - 선택 가능 */}
{selectedItems.includes('temperature') && (
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
<Sun className="h-3.5 w-3.5 text-orange-500 flex-shrink-0" />
<div className="flex items-center gap-1.5 bg-muted/80 rounded-lg p-3">
<Sun className="h-3.5 w-3.5 text-warning flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 leading-tight truncate"></p>
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
<p className="text-xs text-muted-foreground leading-tight truncate"></p>
<p className="text-sm font-semibold text-foreground leading-tight truncate">
{weather.temperature}°C
</p>
</div>
@ -485,11 +481,11 @@ export default function WeatherWidget({
{/* 체감 온도 */}
{selectedItems.includes('feelsLike') && (
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
<Wind className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
<div className="flex items-center gap-1.5 bg-muted/80 rounded-lg p-3">
<Wind className="h-3.5 w-3.5 text-primary flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 leading-tight truncate"></p>
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
<p className="text-xs text-muted-foreground leading-tight truncate"></p>
<p className="text-sm font-semibold text-foreground leading-tight truncate">
{weather.feelsLike}°C
</p>
</div>
@ -498,11 +494,11 @@ export default function WeatherWidget({
{/* 습도 */}
{selectedItems.includes('humidity') && (
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
<Droplets className="h-3.5 w-3.5 text-blue-500 flex-shrink-0" />
<div className="flex items-center gap-1.5 bg-muted/80 rounded-lg p-3">
<Droplets className="h-3.5 w-3.5 text-primary flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 leading-tight truncate"></p>
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
<p className="text-xs text-muted-foreground leading-tight truncate"></p>
<p className="text-sm font-semibold text-foreground leading-tight truncate">
{weather.humidity}%
</p>
</div>
@ -511,11 +507,11 @@ export default function WeatherWidget({
{/* 풍속 */}
{selectedItems.includes('windSpeed') && (
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
<Wind className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
<div className="flex items-center gap-1.5 bg-muted/80 rounded-lg p-3">
<Wind className="h-3.5 w-3.5 text-success flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 leading-tight truncate"></p>
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
<p className="text-xs text-muted-foreground leading-tight truncate"></p>
<p className="text-sm font-semibold text-foreground leading-tight truncate">
{weather.windSpeed} m/s
</p>
</div>
@ -524,11 +520,11 @@ export default function WeatherWidget({
{/* 기압 */}
{selectedItems.includes('pressure') && (
<div className="flex items-center gap-1.5 bg-white/50 rounded-lg p-3">
<div className="flex items-center gap-1.5 bg-muted/80 rounded-lg p-3">
<Gauge className="h-3.5 w-3.5 text-purple-500 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-gray-400 leading-tight truncate"></p>
<p className="text-sm font-semibold text-gray-900 leading-tight truncate">
<p className="text-xs text-muted-foreground leading-tight truncate"></p>
<p className="text-sm font-semibold text-foreground leading-tight truncate">
{weather.pressure} hPa
</p>
</div>

View File

@ -77,10 +77,10 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
if (isLoading && data.length === 0) {
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="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
<div className="mt-2 text-sm text-gray-600"> ...</div>
<div className="mx-auto h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="mt-2 text-sm text-foreground"> ...</div>
</div>
</div>
);
@ -88,14 +88,14 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
if (error) {
return (
<div className="flex h-full items-center justify-center bg-gray-50 p-4">
<div className="flex h-full items-center justify-center bg-muted p-4">
<div className="text-center">
<div className="mb-2 text-4xl"></div>
<div className="text-sm font-medium text-gray-600">{error}</div>
{!element.dataSource?.query && <div className="mt-2 text-xs text-gray-500"> </div>}
<div className="text-sm font-medium text-foreground">{error}</div>
{!element.dataSource?.query && <div className="mt-2 text-xs text-muted-foreground"> </div>}
<button
onClick={loadData}
className="mt-3 rounded-lg bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
className="mt-3 rounded-lg bg-primary px-4 py-2 text-sm text-white hover:bg-primary/90"
>
</button>
@ -105,7 +105,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
}
return (
<div className="flex h-full flex-col bg-white">
<div className="flex h-full flex-col bg-background">
{/* 필터 */}
<div className="flex gap-2 border-b p-3">
<select
@ -134,7 +134,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
<button
onClick={loadData}
className="ml-auto rounded bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600"
className="ml-auto rounded bg-primary px-3 py-1 text-sm text-white hover:bg-primary/90"
>
🔄
</button>
@ -143,7 +143,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
{/* 테이블 */}
<div className="flex-1 overflow-auto">
<table className="w-full text-sm">
<thead className="sticky top-0 bg-gray-50 text-left">
<thead className="sticky top-0 bg-muted text-left">
<tr>
<th className="border-b px-3 py-2 font-medium"></th>
<th className="border-b px-3 py-2 font-medium"></th>
@ -158,7 +158,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
<tbody>
{data.length === 0 ? (
<tr>
<td colSpan={8} className="py-8 text-center text-gray-500">
<td colSpan={8} className="py-8 text-center text-muted-foreground">
</td>
</tr>
@ -167,7 +167,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
.filter((item) => selectedType === "all" || item.work_type === selectedType)
.filter((item) => selectedStatus === "all" || item.status === selectedStatus)
.map((item, index) => (
<tr key={item.id || index} className="border-b hover:bg-gray-50">
<tr key={item.id || index} className="border-b hover:bg-muted">
<td className="px-3 py-2 font-mono text-xs">{item.work_number}</td>
<td className="px-3 py-2">
{item.work_date
@ -180,7 +180,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
: "-"}
</td>
<td className="px-3 py-2">
<span className="rounded bg-blue-100 px-2 py-1 text-xs font-medium text-blue-800">
<span className="rounded bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
{WORK_TYPE_LABELS[item.work_type as WorkType] || item.work_type}
</span>
</td>
@ -194,7 +194,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
</td>
<td className="px-3 py-2">
<span
className={`rounded px-2 py-1 text-xs font-medium ${WORK_STATUS_COLORS[item.status as WorkStatus] || "bg-gray-100 text-gray-800"}`}
className={`rounded px-2 py-1 text-xs font-medium ${WORK_STATUS_COLORS[item.status as WorkStatus] || "bg-muted text-foreground"}`}
>
{WORK_STATUS_LABELS[item.status as WorkStatus] || item.status}
</span>
@ -207,7 +207,7 @@ export default function WorkHistoryWidget({ element, refreshInterval = 60000 }:
</div>
{/* 푸터 */}
<div className="border-t bg-gray-50 px-3 py-2 text-xs text-gray-600"> {data.length}</div>
<div className="border-t bg-muted px-3 py-2 text-xs text-foreground"> {data.length}</div>
</div>
);
}