알아서 배치되는거 하기 전 세이브 디벨롭만 된 상태
This commit is contained in:
parent
6d51aced2c
commit
7097775343
|
|
@ -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(),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue