알아서 배치되는거 하기 전 세이브 디벨롭만 된 상태

This commit is contained in:
leeheejin 2025-10-16 16:34:59 +09:00
parent 6d51aced2c
commit 7097775343
4 changed files with 29 additions and 97 deletions

View File

@ -25,8 +25,8 @@ export class RiskAlertService {
const apiKey = process.env.KMA_API_KEY;
if (!apiKey) {
console.log('⚠️ 기상청 API 키가 없습니다. 테스트 데이터를 반환합니다.');
return this.generateDummyWeatherAlerts();
console.log('⚠️ 기상청 API 키가 없습니다. 데이터를 반환합니다.');
return [];
}
const alerts: Alert[] = [];
@ -109,7 +109,7 @@ export class RiskAlertService {
console.log(`✅ 총 ${alerts.length}건의 기상특보 감지`);
} catch (warningError: any) {
console.error('❌ 기상청 특보 API 오류:', warningError.message);
return this.generateDummyWeatherAlerts();
return [];
}
// 특보가 없으면 빈 배열 반환 (0건)
@ -120,8 +120,8 @@ export class RiskAlertService {
return alerts;
} catch (error: any) {
console.error('❌ 기상청 특보 API 오류:', error.message);
// API 오류 시 더미 데이터 반환
return this.generateDummyWeatherAlerts();
// API 오류 시 빈 배열 반환
return [];
}
}
@ -237,9 +237,9 @@ export class RiskAlertService {
console.error('❌ 한국도로공사 API 오류:', error.message);
}
// 모든 API 실패 시 더미 데이터
console.log(' 모든 교통사고 API 실패. 더미 데이터를 반환합니다.');
return this.generateDummyAccidentAlerts();
// 모든 API 실패 시 빈 배열
console.log(' 모든 교통사고 API 실패. 빈 배열을 반환합니다.');
return [];
}
/**
@ -356,9 +356,9 @@ export class RiskAlertService {
console.error('❌ 한국도로공사 API 오류:', error.message);
}
// 모든 API 실패 시 더미 데이터
console.log(' 모든 도로공사 API 실패. 더미 데이터를 반환합니다.');
return this.generateDummyRoadworkAlerts();
// 모든 API 실패 시 빈 배열
console.log(' 모든 도로공사 API 실패. 빈 배열을 반환합니다.');
return [];
}
/**
@ -467,82 +467,5 @@ export class RiskAlertService {
return 'low';
}
/**
*
*/
private generateDummyWeatherAlerts(): Alert[] {
return [
{
id: `weather-${Date.now()}-1`,
type: 'weather',
severity: 'high',
title: '대설특보',
location: '강원 영동지역',
description: '시간당 2cm 이상 폭설. 차량 운행 주의',
timestamp: new Date(Date.now() - 30 * 60000).toISOString(),
},
{
id: `weather-${Date.now()}-2`,
type: 'weather',
severity: 'medium',
title: '강풍특보',
location: '남해안 전 지역',
description: '순간 풍속 20m/s 이상. 고속도로 주행 주의',
timestamp: new Date(Date.now() - 90 * 60000).toISOString(),
},
];
}
/**
*
*/
private generateDummyAccidentAlerts(): Alert[] {
return [
{
id: `accident-${Date.now()}-1`,
type: 'accident',
severity: 'high',
title: '교통사고 발생',
location: '경부고속도로 서울방향 189km',
description: '3중 추돌사고로 2차로 통제 중. 우회 권장',
timestamp: new Date(Date.now() - 10 * 60000).toISOString(),
},
{
id: `accident-${Date.now()}-2`,
type: 'accident',
severity: 'medium',
title: '사고 다발 지역',
location: '영동고속도로 강릉방향 160km',
description: '안개로 인한 가시거리 50m 이하. 서행 운전',
timestamp: new Date(Date.now() - 60 * 60000).toISOString(),
},
];
}
/**
*
*/
private generateDummyRoadworkAlerts(): Alert[] {
return [
{
id: `construction-${Date.now()}-1`,
type: 'construction',
severity: 'medium',
title: '도로 공사',
location: '서울외곽순환 목동IC~화곡IC',
description: '야간 공사로 1차로 통제 (22:00~06:00)',
timestamp: new Date(Date.now() - 45 * 60000).toISOString(),
},
{
id: `construction-${Date.now()}-2`,
type: 'construction',
severity: 'low',
title: '도로 통제',
location: '중부내륙고속도로 김천JC~현풍IC',
description: '도로 유지보수 작업. 차량 속도 제한 60km/h',
timestamp: new Date(Date.now() - 120 * 60000).toISOString(),
},
];
}
}

View File

@ -25,6 +25,10 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
title: string;
description?: string;
elements: DashboardElement[];
settings?: {
backgroundColor?: string;
resolution?: string;
};
createdAt: string;
updatedAt: string;
} | null>(null);
@ -156,7 +160,11 @@ export default function DashboardViewPage({ params }: DashboardViewPageProps) {
{/* 대시보드 뷰어 */}
<div className="h-[calc(100vh-120px)]">
<DashboardViewer elements={dashboard.elements} dashboardId={dashboard.id} />
<DashboardViewer
elements={dashboard.elements}
dashboardId={dashboard.id}
backgroundColor={dashboard.settings?.backgroundColor}
/>
</div>
</div>
);

View File

@ -115,6 +115,7 @@ interface DashboardViewerProps {
elements: DashboardElement[];
dashboardId: string;
refreshInterval?: number; // 전체 대시보드 새로고침 간격 (ms)
backgroundColor?: string; // 배경색
}
/**
@ -123,7 +124,7 @@ interface DashboardViewerProps {
* -
* -
*/
export function DashboardViewer({ elements, dashboardId, refreshInterval }: DashboardViewerProps) {
export function DashboardViewer({ elements, dashboardId, refreshInterval, backgroundColor = "#f9fafb" }: DashboardViewerProps) {
const [elementData, setElementData] = useState<Record<string, QueryResult>>({});
const [loadingElements, setLoadingElements] = useState<Set<string>>(new Set());
const [lastRefresh, setLastRefresh] = useState<Date>(new Date());
@ -230,7 +231,7 @@ export function DashboardViewer({ elements, dashboardId, refreshInterval }: Dash
}
return (
<div className="relative h-full w-full overflow-auto bg-gray-100">
<div className="relative h-full w-full overflow-auto" style={{ backgroundColor }}>
{/* 새로고침 상태 표시 */}
<div className="text-muted-foreground absolute top-4 right-4 z-10 rounded-lg bg-white px-3 py-2 text-xs shadow-sm">
: {lastRefresh.toLocaleTimeString()}

View File

@ -135,7 +135,7 @@ 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">
<div className="h-full bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg border p-4 @container">
{/* 헤더 */}
<div className="flex items-center justify-between mb-3">
<div className="flex-1">
@ -160,10 +160,10 @@ export default function ExchangeWidget({
</Button>
</div>
{/* 통화 선택 */}
<div className="flex items-center gap-2 mb-3">
{/* 통화 선택 - 반응형 (좁을 때 세로 배치) */}
<div className="flex @[300px]:flex-row flex-col items-center gap-2 mb-3">
<Select value={base} onValueChange={setBase}>
<SelectTrigger className="flex-1 bg-white h-8 text-xs">
<SelectTrigger className="w-full @[300px]:flex-1 bg-white 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"
className="h-8 w-8 p-0 rounded-full hover:bg-white @[300px]:rotate-0 rotate-90"
>
<ArrowRightLeft className="h-3 w-3" />
</Button>
<Select value={target} onValueChange={setTarget}>
<SelectTrigger className="flex-1 bg-white h-8 text-xs">
<SelectTrigger className="w-full @[300px]:flex-1 bg-white h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>