feat: add profile image to employee

This commit is contained in:
pray4skylark
2026-06-02 18:41:30 +09:00
parent adb7de09ef
commit b471598d6c
8 changed files with 54 additions and 6 deletions
+29 -4
View File
@@ -2,7 +2,7 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { UserCircle, Mail, Briefcase, Calendar, Lock, Shield, CheckCircle, XCircle, AlertCircle, Phone, Plus, Pencil, Trash2, X, Save, GraduationCap, MapPin } from 'lucide-react';
import { UserCircle, Mail, Briefcase, Calendar, Lock, Shield, CheckCircle, XCircle, AlertCircle, Phone, Plus, Pencil, Trash2, X, Save, GraduationCap, MapPin, Camera, Loader2 } from 'lucide-react';
import employeeService from '@/services/employeeService';
import type { ApiResponse, Employee, DispatchHistory, EmergencyContact } from '@/types';
@@ -266,9 +266,34 @@ export default function ProfilePage() {
{/* Profile Card */}
<div className="bg-surface border border-border rounded-2xl p-6 shadow-soft">
<div className="flex items-start gap-6">
{/* Avatar */}
<div className="w-24 h-24 rounded-full bg-gradient-to-br from-primary to-purple-400 flex items-center justify-center text-white text-3xl font-bold shadow-lg shadow-primary/20 flex-shrink-0">
{formatInitial(employee.name)}
{/* Avatar with upload */}
<div className="relative flex-shrink-0 group">
{employee.profileImage ? (
<img src={employee.profileImage} alt={employee.name}
className="w-24 h-24 rounded-full object-cover border-4 border-white shadow-lg" />
) : (
<div className="w-24 h-24 rounded-full bg-gradient-to-br from-primary to-purple-400 flex items-center justify-center text-white text-3xl font-bold shadow-lg shadow-primary/20">
{formatInitial(employee.name)}
</div>
)}
<label className="absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/40 transition-all flex items-center justify-center cursor-pointer">
<Camera className="w-6 h-6 text-white opacity-0 group-hover:opacity-100 transition-opacity" />
<input type="file" accept="image/*" className="hidden" onChange={async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const form = new FormData();
form.append('file', file);
try {
const uploadRes = await fetch('/api/v1/files/upload', { method: 'POST', body: form });
const uploadData = await uploadRes.json();
if (uploadData.success) {
await employeeService.updateProfileImage(employee.empId, uploadData.data);
const empRes = await employeeService.getMe();
if (empRes.success && empRes.data) setEmployee(empRes.data as EmployeeWithId);
}
} catch { alert('이미지 업로드에 실패했습니다.'); }
}} />
</label>
</div>
{/* Info Grid */}
+4
View File
@@ -83,6 +83,10 @@ const employeeService = {
deleteEmergencyContact(empId: number, contactId: number) {
return apiClient.delete<ApiResponse<void>>(`/employees/${empId}/emergency-contacts/${contactId}`);
},
updateProfileImage(empId: number, profileImage: string) {
return apiClient.put<ApiResponse<Employee>>(`/employees/${empId}/profile-image`, { profileImage });
},
};
export interface PortfolioEntry {
+1
View File
@@ -60,6 +60,7 @@ export interface Employee {
major: string | null;
graduationDate: string | null;
residence: string | null;
profileImage: string | null;
emergencyContacts?: EmergencyContact[];
}