refactor: 로딩 스피너 표준화 및 프로젝트 헬스 개선

- LoadingSpinner 컴포넌트 5가지 변형 구현
  - LoadingSpinner (인라인/버튼용)
  - ContentLoadingSpinner (상세/수정 페이지)
  - PageLoadingSpinner (페이지 전환)
  - TableLoadingSpinner (테이블/리스트)
  - ButtonSpinner (버튼 내부)
- 18개+ 페이지 로딩 UI 표준화
  - HR 페이지 (사원, 휴가, 부서, 급여, 근태)
  - 영업 페이지 (견적, 거래처)
  - 게시판, 팝업관리, 품목기준정보
- API 키 보안 개선 (NEXT_PUBLIC_API_KEY → API_KEY)
- Textarea 다크모드 스타일 개선
- DropdownField Radix UI Select 버그 수정 (key prop)
- 프로젝트 헬스 개선 계획서 문서화

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-20 14:33:11 +09:00
parent c6b605200d
commit d7f491fa84
50 changed files with 666 additions and 246 deletions

View File

@@ -57,6 +57,10 @@ const initialFormData: EmployeeFormData = {
confirmPassword: '',
role: 'user',
accountStatus: 'active',
clockInLocation: '',
clockOutLocation: '',
resignationDate: '',
resignationReason: '',
};
export function EmployeeDialog({
@@ -103,6 +107,10 @@ export function EmployeeDialog({
confirmPassword: '',
role: employee.userInfo?.role || 'user',
accountStatus: employee.userInfo?.accountStatus || 'active',
clockInLocation: employee.clockInLocation || '',
clockOutLocation: employee.clockOutLocation || '',
resignationDate: employee.resignationDate || '',
resignationReason: employee.resignationReason || '',
});
} else if (open && mode === 'create') {
setFormData(initialFormData);

View File

@@ -125,6 +125,8 @@ export interface Employee {
clockOutLocation?: string; // 퇴근 위치
resignationDate?: string; // 퇴사일
resignationReason?: string; // 퇴직사유
concurrentPosition?: string; // 겸직 직위
concurrentReason?: string; // 겸직 사유
// 사용자 정보 (시스템 계정)
userInfo?: UserInfo;

View File

@@ -19,7 +19,7 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import type { VacationUsageRecord, VacationAdjustment, VacationType } from './types';
import type { VacationUsageRecord, VacationAdjustment, AdjustableVacationType } from './types';
import { VACATION_TYPE_LABELS } from './types';
interface VacationAdjustDialogProps {
@@ -62,7 +62,7 @@ export function VacationAdjustDialog({
}, [open]);
// 조정값 증가
const handleIncrease = (type: VacationType) => {
const handleIncrease = (type: AdjustableVacationType) => {
setAdjustments(prev => ({
...prev,
[type]: prev[type] + 1,
@@ -70,7 +70,7 @@ export function VacationAdjustDialog({
};
// 조정값 감소
const handleDecrease = (type: VacationType) => {
const handleDecrease = (type: AdjustableVacationType) => {
setAdjustments(prev => ({
...prev,
[type]: prev[type] - 1,
@@ -78,7 +78,7 @@ export function VacationAdjustDialog({
};
// 조정값 직접 입력
const handleInputChange = (type: VacationType, value: string) => {
const handleInputChange = (type: AdjustableVacationType, value: string) => {
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
setAdjustments(prev => ({
@@ -97,7 +97,7 @@ export function VacationAdjustDialog({
const handleSave = () => {
const adjustmentList: VacationAdjustment[] = [];
(Object.keys(adjustments) as VacationType[]).forEach((type) => {
(Object.keys(adjustments) as AdjustableVacationType[]).forEach((type) => {
if (adjustments[type] !== 0) {
adjustmentList.push({
vacationType: type,
@@ -116,7 +116,7 @@ export function VacationAdjustDialog({
if (!record) return null;
const vacationTypes: VacationType[] = ['annual', 'monthly', 'reward', 'other'];
const vacationTypes: AdjustableVacationType[] = ['annual', 'monthly', 'reward', 'other'];
return (
<Dialog open={open} onOpenChange={onOpenChange}>

View File

@@ -9,6 +9,9 @@ export type MainTabType = 'usage' | 'grant' | 'request';
// 휴가 유형
export type VacationType = 'annual' | 'monthly' | 'reward' | 'condolence' | 'other';
// 조정 가능한 휴가 유형 (VacationAdjustDialog에서 사용)
export type AdjustableVacationType = 'annual' | 'monthly' | 'reward' | 'other';
// 필터 옵션
export type FilterOption = 'all' | 'hasVacation' | 'noVacation';