'use client' import { useState, useEffect } from 'react' import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' import { apiClient } from '@/lib/api-client' // 第一層部門 (depth=0,擁有 email_domain) interface TopLevelDepartment { id: number name: string code: string email_domain?: string effective_email_domain?: string depth: number is_active: boolean } // 子部門 (depth>=1) interface SubDepartment { id: number name: string code: string parent_id: number depth: number effective_email_domain?: string } interface EmployeeFormData { username_base: string legal_name: string english_name: string phone: string mobile: string hire_date: string // 組織與職務資訊 (新多層部門架構) top_department_id: string // 第一層部門 (決定郵件網域) department_id: string // 指定部門 (選填,可為任何層) job_title: string email_quota_mb: string // 到職自動化 auto_onboard: boolean create_keycloak: boolean create_email: boolean create_drive: boolean } interface OnboardResult { created?: boolean disabled?: boolean error?: string | null message?: string username?: string email?: string user_id?: string quota_gb?: number } export default function NewEmployeePage() { const { data: session, status } = useSession() const router = useRouter() const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [topDepartments, setTopDepartments] = useState([]) const [subDepartments, setSubDepartments] = useState([]) const [loadingSubDepts, setLoadingSubDepts] = useState(false) const [onboardResults, setOnboardResults] = useState<{ keycloak: OnboardResult email: OnboardResult drive: OnboardResult } | null>(null) const [formData, setFormData] = useState({ username_base: '', legal_name: '', english_name: '', phone: '', mobile: '', hire_date: new Date().toISOString().split('T')[0], top_department_id: '', department_id: '', job_title: '', email_quota_mb: '5120', auto_onboard: true, create_keycloak: true, create_email: true, create_drive: true, }) // 載入第一層部門列表 useEffect(() => { if (status === 'authenticated') { fetchTopDepartments() } }, [status]) // 當選擇第一層部門時,載入其子部門 useEffect(() => { if (formData.top_department_id) { fetchSubDepartments(parseInt(formData.top_department_id)) } else { setSubDepartments([]) setFormData((prev) => ({ ...prev, department_id: '' })) } }, [formData.top_department_id]) const fetchTopDepartments = async () => { try { const data = await apiClient.get('/departments/?depth=0') setTopDepartments(data) } catch (err) { console.error('載入部門失敗:', err) } } const fetchSubDepartments = async (parentId: number) => { try { setLoadingSubDepts(true) const data = await apiClient.get(`/departments/?parent_id=${parentId}`) setSubDepartments(data) } catch (err) { console.error('載入子部門失敗:', err) setSubDepartments([]) } finally { setLoadingSubDepts(false) } } if (status === 'loading') { return (
載入中...
) } if (status === 'unauthenticated') { router.push('/auth/signin') return null } const selectedTopDept = topDepartments.find( (d) => d.id === parseInt(formData.top_department_id) ) const handleChange = ( e: React.ChangeEvent ) => { const { name, value, type } = e.target const checked = (e.target as HTMLInputElement).checked setFormData((prev) => ({ ...prev, [name]: type === 'checkbox' ? checked : value, })) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setLoading(true) setError(null) try { // 決定 department_id:若有選子部門用子部門,否則用第一層部門 const finalDepartmentId = formData.department_id ? parseInt(formData.department_id) : formData.top_department_id ? parseInt(formData.top_department_id) : undefined const employeePayload = { username_base: formData.username_base, legal_name: formData.legal_name, english_name: formData.english_name || undefined, phone: formData.phone || undefined, mobile: formData.mobile || undefined, hire_date: formData.hire_date, // 新架構:department_id 指向任何層部門 department_id: finalDepartmentId, job_title: formData.job_title, email_quota_mb: parseInt(formData.email_quota_mb), } const newEmployee = await apiClient.post('/employees/', employeePayload) as any const newEmployeeId = newEmployee.id // 自動執行到職流程 if (formData.auto_onboard) { try { const params = new URLSearchParams({ create_keycloak: String(formData.create_keycloak), create_email: String(formData.create_email), create_drive: String(formData.create_drive), }) const onboardResponse = await apiClient.post( `/employees/${newEmployeeId}/onboard?${params.toString()}`, {} ) as any setOnboardResults(onboardResponse.results) setTimeout(() => { router.push(`/employees/${newEmployeeId}`) }, 3000) } catch { router.push(`/employees/${newEmployeeId}`) } } else { router.push(`/employees/${newEmployeeId}`) } } catch (err: any) { const errorMessage = err?.response?.data?.detail || err.message || '新增失敗' setError(errorMessage) setLoading(false) } } return (

新增員工

填寫基本資料以建立新員工

{error && (
{error}
)} {/* 到職流程結果顯示 */} {onboardResults && (

✓ 員工已建立,到職流程執行完成

{formData.create_keycloak && (
{onboardResults.keycloak?.created ? '✓' : '⚠'} Keycloak SSO: {onboardResults.keycloak?.message || (onboardResults.keycloak?.error ? `失敗 - ${onboardResults.keycloak.error}` : '未執行')}
)} {formData.create_email && (
{onboardResults.email?.created ? '✓' : '⚠'} 郵件帳號: {onboardResults.email?.message || (onboardResults.email?.error ? `失敗 - ${onboardResults.email.error}` : '未執行')}
)} {formData.create_drive && (
{onboardResults.drive?.created ? '✓' : '⚠'} 雲端硬碟: {onboardResults.drive?.message || (onboardResults.drive?.error ? `失敗 - ${onboardResults.drive.error}` : '未執行')}
)}

3 秒後自動跳轉至員工詳情頁...

)}
{/* 單一登入帳號 */}

員工的 SSO 登入帳號名稱,系統根據所屬第一層部門自動加上網域
例如: 輸入 porsche.chen → 完整帳號 porsche.chen@ease.taipei

{/* 中文姓名 */}
{/* 英文姓名 */}
{/* 電話 */}
{/* 手機 */}
{/* 到職日 */}
{/* 分隔線 */}

組織與職務資訊

{/* 第一層部門 (原事業部) */}

決定員工的主要郵件網域 {selectedTopDept?.email_domain && ( @{selectedTopDept.email_domain} )}

{/* 子部門 (選填) */}

選填,可稍後在部門成員管理中設定

{/* 職稱 */}
{/* 郵件配額 */}

預設 5120 MB (5 GB),可設定 1024 MB 至 51200 MB 之間

{/* 到職自動化流程 */}

自動執行到職流程

{formData.auto_onboard && (

選擇要自動建立的帳號:

)}
{/* 按鈕區 */}
{/* 提示訊息 */}

💡 自動化流程說明

  • • 員工編號將自動產生 (EMP001, EMP002...)
  • • 目前狀態會設定為「在職」
  • • SSO 帳號將根據所屬第一層部門的網域自動生成
  • • 建立員工後可在「組織架構」頁面管理部門成員
  • • 勾選「自動執行到職流程」可立即建立:
  • - Keycloak SSO 帳號 (含初始密碼)
  • - 主要郵件帳號
  • - 雲端硬碟帳號 (Drive Service)
  • • 也可在建立員工後,從員工詳情頁手動觸發到職流程
) }