fix: TypeScript 타입 오류 수정 및 설정 페이지 추가
- BOMItem Omit 타입 시그니처 통일 (useTemplateManagement, SectionsTab, ItemMasterContext) - HeadersInit → Record<string, string> 타입 변경 - Zustand useShallow 마이그레이션 (zustand/react/shallow) - DataTable, ListPageTemplate 제네릭 타입 제약 추가 - 설정 관리 페이지 추가 (직급, 직책, 휴가정책, 근무일정, 권한) - HR 관리 페이지 추가 (급여, 휴가) - 단가관리 페이지 리팩토링 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
286
src/components/settings/WorkScheduleManagement/index.tsx
Normal file
286
src/components/settings/WorkScheduleManagement/index.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { PageLayout } from '@/components/organisms/PageLayout';
|
||||
import { PageHeader } from '@/components/organisms/PageHeader';
|
||||
import { Clock, Save } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { TimePicker } from '@/components/ui/time-picker';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { toast } from 'sonner';
|
||||
import type {
|
||||
WorkScheduleSettings,
|
||||
EmploymentType,
|
||||
DayOfWeek,
|
||||
} from './types';
|
||||
import {
|
||||
DEFAULT_WORK_SCHEDULE,
|
||||
EMPLOYMENT_TYPE_LABELS,
|
||||
DAY_OF_WEEK_LABELS,
|
||||
} from './types';
|
||||
|
||||
// 고용 형태별 기본 설정
|
||||
const EMPLOYMENT_TYPE_DEFAULTS: Record<EmploymentType, Partial<WorkScheduleSettings>> = {
|
||||
regular: {
|
||||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: '09:00',
|
||||
workEndTime: '18:00',
|
||||
weeklyWorkHours: 40,
|
||||
weeklyOvertimeHours: 12,
|
||||
},
|
||||
contract: {
|
||||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: '09:00',
|
||||
workEndTime: '18:00',
|
||||
weeklyWorkHours: 40,
|
||||
weeklyOvertimeHours: 12,
|
||||
},
|
||||
dispatch: {
|
||||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: '09:00',
|
||||
workEndTime: '18:00',
|
||||
weeklyWorkHours: 40,
|
||||
weeklyOvertimeHours: 12,
|
||||
},
|
||||
outsourcing: {
|
||||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: '09:00',
|
||||
workEndTime: '18:00',
|
||||
weeklyWorkHours: 40,
|
||||
weeklyOvertimeHours: 12,
|
||||
},
|
||||
partTime: {
|
||||
workDays: ['mon', 'tue', 'wed'],
|
||||
workStartTime: '10:00',
|
||||
workEndTime: '15:00',
|
||||
weeklyWorkHours: 15,
|
||||
weeklyOvertimeHours: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function WorkScheduleManagement() {
|
||||
// 현재 선택된 고용 형태
|
||||
const [selectedEmploymentType, setSelectedEmploymentType] = useState<EmploymentType>('regular');
|
||||
|
||||
// 근무 설정
|
||||
const [settings, setSettings] = useState<WorkScheduleSettings>(DEFAULT_WORK_SCHEDULE);
|
||||
|
||||
// 고용 형태 변경 시 기본값 로드
|
||||
useEffect(() => {
|
||||
const defaults = EMPLOYMENT_TYPE_DEFAULTS[selectedEmploymentType];
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
employmentType: selectedEmploymentType,
|
||||
...defaults,
|
||||
}));
|
||||
}, [selectedEmploymentType]);
|
||||
|
||||
// 근무일 토글
|
||||
const toggleWorkDay = (day: DayOfWeek) => {
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
workDays: prev.workDays.includes(day)
|
||||
? prev.workDays.filter(d => d !== day)
|
||||
: [...prev.workDays, day],
|
||||
}));
|
||||
};
|
||||
|
||||
// 저장
|
||||
const handleSave = () => {
|
||||
// 실제로는 API 호출
|
||||
console.log('저장할 설정:', settings);
|
||||
toast.success(`${EMPLOYMENT_TYPE_LABELS[selectedEmploymentType]} 근무 설정이 저장되었습니다.`);
|
||||
};
|
||||
|
||||
const ALL_DAYS: DayOfWeek[] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<PageHeader
|
||||
title="근무관리"
|
||||
description="고용 형태별 근무 시간을 설정합니다."
|
||||
icon={Clock}
|
||||
/>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* 고용 형태 선택 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">고용 형태 선택</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="employment-type">고용 형태</Label>
|
||||
<Select
|
||||
value={selectedEmploymentType}
|
||||
onValueChange={(value: EmploymentType) => setSelectedEmploymentType(value)}
|
||||
>
|
||||
<SelectTrigger className="w-64">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(EMPLOYMENT_TYPE_LABELS).map(([key, label]) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 주간 근무일 설정 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">주간 근무일</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{ALL_DAYS.map((day) => (
|
||||
<label
|
||||
key={day}
|
||||
className="flex items-center gap-2 cursor-pointer"
|
||||
>
|
||||
<Checkbox
|
||||
checked={settings.workDays.includes(day)}
|
||||
onCheckedChange={() => toggleWorkDay(day)}
|
||||
/>
|
||||
<span className="text-sm font-medium">
|
||||
{DAY_OF_WEEK_LABELS[day]}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 1일 기준 근로시간 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">1일 기준 근로시간</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>출근 시간</Label>
|
||||
<TimePicker
|
||||
value={settings.workStartTime}
|
||||
onChange={(value) => setSettings(prev => ({ ...prev, workStartTime: value }))}
|
||||
className="w-40"
|
||||
minuteStep={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>퇴근 시간</Label>
|
||||
<TimePicker
|
||||
value={settings.workEndTime}
|
||||
onChange={(value) => setSettings(prev => ({ ...prev, workEndTime: value }))}
|
||||
className="w-40"
|
||||
minuteStep={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 주당 근로시간 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">주당 근로시간</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="weekly-hours">주당 기준 근로시간</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id="weekly-hours"
|
||||
type="number"
|
||||
min={0}
|
||||
max={52}
|
||||
value={settings.weeklyWorkHours}
|
||||
onChange={(e) =>
|
||||
setSettings(prev => ({ ...prev, weeklyWorkHours: parseInt(e.target.value) || 0 }))
|
||||
}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">시간</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="overtime-hours">주당 연장 근로시간</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id="overtime-hours"
|
||||
type="number"
|
||||
min={0}
|
||||
max={52}
|
||||
value={settings.weeklyOvertimeHours}
|
||||
onChange={(e) =>
|
||||
setSettings(prev => ({ ...prev, weeklyOvertimeHours: parseInt(e.target.value) || 0 }))
|
||||
}
|
||||
className="w-24"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">시간</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 1일 기준 휴게시간 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg">1일 기준 휴게시간</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label>휴게 시작</Label>
|
||||
<TimePicker
|
||||
value={settings.breakStartTime}
|
||||
onChange={(value) => setSettings(prev => ({ ...prev, breakStartTime: value }))}
|
||||
className="w-40"
|
||||
minuteStep={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>휴게 종료</Label>
|
||||
<TimePicker
|
||||
value={settings.breakEndTime}
|
||||
onChange={(value) => setSettings(prev => ({ ...prev, breakEndTime: value }))}
|
||||
className="w-40"
|
||||
minuteStep={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 저장 버튼 */}
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSave} size="lg">
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 안내 문구 */}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
※ 근무 설정은 고용 형태별로 저장됩니다. 설정 변경 후 반드시 저장 버튼을 클릭하세요.
|
||||
</p>
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
51
src/components/settings/WorkScheduleManagement/types.ts
Normal file
51
src/components/settings/WorkScheduleManagement/types.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 근무관리 타입 정의 (PDF 56페이지 기준)
|
||||
*/
|
||||
|
||||
// 고용 형태
|
||||
export type EmploymentType = 'regular' | 'contract' | 'dispatch' | 'outsourcing' | 'partTime';
|
||||
|
||||
export const EMPLOYMENT_TYPE_LABELS: Record<EmploymentType, string> = {
|
||||
regular: '정규직',
|
||||
contract: '계약직',
|
||||
dispatch: '파견직',
|
||||
outsourcing: '용역직',
|
||||
partTime: '시간제 근로자',
|
||||
};
|
||||
|
||||
// 요일
|
||||
export type DayOfWeek = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
||||
|
||||
export const DAY_OF_WEEK_LABELS: Record<DayOfWeek, string> = {
|
||||
mon: '월',
|
||||
tue: '화',
|
||||
wed: '수',
|
||||
thu: '목',
|
||||
fri: '금',
|
||||
sat: '토',
|
||||
sun: '일',
|
||||
};
|
||||
|
||||
// 근무 설정
|
||||
export interface WorkScheduleSettings {
|
||||
employmentType: EmploymentType;
|
||||
workDays: DayOfWeek[]; // 주간 근무일
|
||||
workStartTime: string; // 출근 시간 (HH:mm)
|
||||
workEndTime: string; // 퇴근 시간 (HH:mm)
|
||||
weeklyWorkHours: number; // 주당 기준 근로시간
|
||||
weeklyOvertimeHours: number; // 주당 연장 근로시간
|
||||
breakStartTime: string; // 휴게 시작 시간 (HH:mm)
|
||||
breakEndTime: string; // 휴게 종료 시간 (HH:mm)
|
||||
}
|
||||
|
||||
// 기본 설정값
|
||||
export const DEFAULT_WORK_SCHEDULE: WorkScheduleSettings = {
|
||||
employmentType: 'regular',
|
||||
workDays: ['mon', 'tue', 'wed', 'thu', 'fri'],
|
||||
workStartTime: '09:00',
|
||||
workEndTime: '18:00',
|
||||
weeklyWorkHours: 40,
|
||||
weeklyOvertimeHours: 12,
|
||||
breakStartTime: '12:00',
|
||||
breakEndTime: '13:00',
|
||||
};
|
||||
Reference in New Issue
Block a user