From f5899aec46df23bfd16b91bb9078db7f1d128248 Mon Sep 17 00:00:00 2001 From: pray4skylark Date: Tue, 2 Jun 2026 15:14:51 +0900 Subject: [PATCH] feat: manager/admin can view/edit any employee's portfolio --- src/app/(main)/profile/portfolio/page.tsx | 93 ++++++++++++++++++++--- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/src/app/(main)/profile/portfolio/page.tsx b/src/app/(main)/profile/portfolio/page.tsx index 0785c45..92836a9 100644 --- a/src/app/(main)/profile/portfolio/page.tsx +++ b/src/app/(main)/profile/portfolio/page.tsx @@ -2,9 +2,10 @@ import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { ArrowLeft, Building2, BadgeCheck, Calendar, Wrench, Ticket, FileText, Edit3, Save, X, Loader2 } from 'lucide-react'; +import { ArrowLeft, Building2, BadgeCheck, Calendar, Ticket, FileText, Edit3, Save, X, Loader2, Search, User } from 'lucide-react'; import { useAuthStore } from '@/stores'; import employeeService, { type PortfolioEntry } from '@/services/employeeService'; +import type { EmployeeSearchResult } from '@/types'; const initialColor = (name: string) => { const colors = ['from-emerald-400 to-emerald-500', 'from-orange-400 to-orange-500', 'from-blue-400 to-blue-500', 'from-pink-400 to-pink-500', 'from-purple-400 to-purple-500']; @@ -16,6 +17,13 @@ const initialColor = (name: string) => { export default function PortfolioPage() { const router = useRouter(); const user = useAuthStore((s) => s.user); + const isMgmt = user?.roleCode === 'MANAGER' || user?.roleCode === 'ADMIN'; + const [targetEmpId, setTargetEmpId] = useState(null); + const [targetName, setTargetName] = useState(''); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searchResults, setSearchResults] = useState([]); + const [showSearch, setShowSearch] = useState(false); + const [searchTimeout, setSearchTimeout] = useState(null); const [portfolio, setPortfolio] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); @@ -23,16 +31,45 @@ export default function PortfolioPage() { const [editText, setEditText] = useState(''); const [saving, setSaving] = useState(false); - useEffect(() => { - if (!user) return; + const loadPortfolio = (empId: number) => { setLoading(true); - employeeService.getPortfolio(user.empId) + setError(''); + employeeService.getPortfolio(empId) .then((res) => { setPortfolio(Array.isArray(res.data) ? res.data : []); }) .catch(() => setError('포트폴리오를 불러오는데 실패했습니다.')) .finally(() => setLoading(false)); - }, [user]); + }; + + useEffect(() => { + if (!user) return; + const id = targetEmpId || user.empId; + setTargetName(targetEmpId ? targetName : user.name); + loadPortfolio(id); + }, [user, targetEmpId]); + + // Employee search (MANAGER/ADMIN only) + const handleSearch = (keyword: string) => { + setSearchKeyword(keyword); + if (searchTimeout) clearTimeout(searchTimeout); + if (!keyword.trim()) { setSearchResults([]); return; } + const t = setTimeout(async () => { + try { + const res = await employeeService.search(keyword); + if (res.success) setSearchResults(res.data.slice(0, 10)); + } catch { setSearchResults([]); } + }, 300); + setSearchTimeout(t); + }; + + const selectEmployee = (emp: EmployeeSearchResult) => { + setTargetEmpId(emp.empId); + setTargetName(emp.name); + setShowSearch(false); + setSearchKeyword(''); + setSearchResults([]); + }; const handleEditAchievements = (entry: PortfolioEntry) => { setEditingId(entry.dispatchId); @@ -40,10 +77,11 @@ export default function PortfolioPage() { }; const handleSaveAchievements = async () => { - if (!user || !editingId) return; + const empId = targetEmpId || user?.empId; + if (!empId || !editingId) return; setSaving(true); try { - await employeeService.updateAchievements(user.empId, editingId, editText); + await employeeService.updateAchievements(empId, editingId, editText); setPortfolio((prev) => prev.map((p) => p.dispatchId === editingId ? { ...p, achievements: editText } : p)); setEditingId(null); } catch { @@ -65,14 +103,49 @@ export default function PortfolioPage() {
{/* Header */} -
+
-
+

경력 기술서

-

{user.name}님의 포트폴리오

+

{targetName || user.name}님의 포트폴리오

+ {isMgmt && ( +
+ + {showSearch && ( +
+
+ handleSearch(e.target.value)} + placeholder="이름 또는 ID를 입력하세요..." + className="w-full px-3 py-2 border border-border rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary/20" + autoFocus /> +
+ {searchResults.length > 0 && ( +
+ {searchResults.map((emp) => ( + + ))} +
+ )} + {searchKeyword && searchResults.length === 0 && ( +

검색 결과가 없습니다.

+ )} +
+ )} +
+ )}
{/* Summary */}