Initial commit: HR Performance System
- Database schema with 31 tables for 4-card system - LLM API integration (Gemini, DeepSeek, OpenAI) - Error handling system with modal component - Connection test UI for LLM services - Environment configuration files - Complete database documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
127
components/ErrorModal.jsx
Normal file
127
components/ErrorModal.jsx
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 錯誤彈窗元件
|
||||
* 統一顯示應用程式中的錯誤訊息
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './ErrorModal.css';
|
||||
|
||||
const ErrorModal = ({ error, onClose, autoClose = true, duration = 5000 }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [countdown, setCountdown] = useState(duration / 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setIsVisible(true);
|
||||
setCountdown(duration / 1000);
|
||||
|
||||
if (autoClose) {
|
||||
const timer = setTimeout(() => {
|
||||
handleClose();
|
||||
}, duration);
|
||||
|
||||
const countdownInterval = setInterval(() => {
|
||||
setCountdown((prev) => Math.max(0, prev - 1));
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
clearInterval(countdownInterval);
|
||||
};
|
||||
}
|
||||
}
|
||||
}, [error, autoClose, duration]);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
setTimeout(() => {
|
||||
if (onClose) onClose();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
if (!error) return null;
|
||||
|
||||
const getSeverityClass = () => {
|
||||
if (!error.statusCode) return 'error';
|
||||
if (error.statusCode >= 500) return 'error';
|
||||
if (error.statusCode >= 400) return 'warning';
|
||||
return 'info';
|
||||
};
|
||||
|
||||
const getSeverityIcon = () => {
|
||||
const severity = getSeverityClass();
|
||||
switch (severity) {
|
||||
case 'error':
|
||||
return '❌';
|
||||
case 'warning':
|
||||
return '⚠️';
|
||||
case 'info':
|
||||
return 'ℹ️';
|
||||
default:
|
||||
return '❌';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`error-modal-overlay ${isVisible ? 'visible' : ''}`} onClick={handleClose}>
|
||||
<div
|
||||
className={`error-modal ${getSeverityClass()} ${isVisible ? 'visible' : ''}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="error-modal-header">
|
||||
<div className="error-modal-title">
|
||||
<span className="error-icon">{getSeverityIcon()}</span>
|
||||
<h3>{error.title || '錯誤'}</h3>
|
||||
</div>
|
||||
<button className="error-modal-close" onClick={handleClose} aria-label="關閉">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="error-modal-body">
|
||||
<p className="error-message">{error.message}</p>
|
||||
|
||||
{error.statusCode && (
|
||||
<div className="error-metadata">
|
||||
<span className="error-code">錯誤代碼: {error.statusCode}</span>
|
||||
{error.timestamp && (
|
||||
<span className="error-time">
|
||||
時間: {new Date(error.timestamp).toLocaleString('zh-TW')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error.details && (
|
||||
<details className="error-details">
|
||||
<summary>詳細資訊</summary>
|
||||
<pre>{JSON.stringify(error.details, null, 2)}</pre>
|
||||
</details>
|
||||
)}
|
||||
|
||||
{error.path && (
|
||||
<div className="error-path">
|
||||
<small>路徑: {error.path}</small>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="error-modal-footer">
|
||||
{autoClose && (
|
||||
<span className="error-countdown">
|
||||
{countdown > 0 ? `${countdown} 秒後自動關閉` : '關閉中...'}
|
||||
</span>
|
||||
)}
|
||||
<button className="error-modal-button" onClick={handleClose}>
|
||||
確定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorModal;
|
||||
Reference in New Issue
Block a user