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:
donald
2025-12-03 23:34:13 +08:00
commit c24634f4b7
18 changed files with 8179 additions and 0 deletions

127
components/ErrorModal.jsx Normal file
View 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;