feat: [leave] 잔여연차 탭 도움말 기능 추가

- 휴가관리가이드.md 마크다운 콘텐츠 작성 (연차 산출 방식, 촉진 제도 등)
- 잔여연차 탭 헤더에 도움말(?) 버튼 추가
- help-modal.blade.php 생성 (sales 패턴 재사용)
- LeaveController에 helpGuide() 메서드 추가
- 도움말 라우트 등록
This commit is contained in:
김보곤
2026-02-27 10:42:21 +09:00
parent 5e61d20231
commit bbdad75468
5 changed files with 360 additions and 0 deletions

View File

@@ -7,6 +7,7 @@
use App\Services\HR\LeaveService;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
class LeaveController extends Controller
{
@@ -32,4 +33,21 @@ public function index(\Illuminate\Http\Request $request): View|Response
'statusMap' => $statusMap,
]);
}
/**
* 휴가관리 가이드 도움말 모달
*/
public function helpGuide(): View
{
$guidePath = resource_path('markdown/휴가관리가이드.md');
if (file_exists($guidePath)) {
$markdown = file_get_contents($guidePath);
$htmlContent = Str::markdown($markdown);
} else {
$htmlContent = '<p class="text-gray-500">가이드를 찾을 수 없습니다.</p>';
}
return view('hr.leaves.partials.help-modal', compact('htmlContent'));
}
}

View File

@@ -0,0 +1,115 @@
# 휴가관리 가이드
## 1. 연차 산출 방식
### 1.1 기본 연차 부여 규칙 (근로기준법 제60조)
- **입사 1년 미만**: 1개월 만근(개근) 시 **1일** 발생 (최대 11일)
- **1년 이상 근무**: **15일** 부여
- **매 2년 근속** 시 **+1일** 가산
- **최대 한도**: 25일
### 1.2 연차발생일수 테이블
| 근속년수 | 연차일수 | 근속년수 | 연차일수 |
|:--------:|:--------:|:--------:|:--------:|
| 1년 미만 | 최대 11일 | 1년 | 15일 |
| 3년 | 16일 | 5년 | 17일 |
| 7년 | 18일 | 9년 | 19일 |
| 11년 | 20일 | 13년 | 21일 |
| 15년 | 22일 | 17년 | 23일 |
| 19년 | 24일 | 21년~ | 25일 |
> **참고**: 매 2년 근속 시 1일이 가산됩니다. (예: 3년차 16일, 5년차 17일, ...)
### 1.3 회계일 기준 vs 입사일 기준
| 구분 | 회계일 기준 | 입사일 기준 |
|------|-----------|-----------|
| 1년차 (당해 입사) | 1달 만근 시 1일씩 발생 | 입사일 기준 11일 |
| 2년차 | 1달 만근분 + 1년차 비례분 | 15일 |
| 3년차~ | 15일 + 가산일 | 15일 + 가산일 |
---
## 2. 연차 사용 규칙
### 2.1 휴가 유형별 차감
| 유형 | 차감일수 | 비고 |
|------|:--------:|------|
| 연차 | 1일 | 하루 전체 |
| 오전반차 | 0.5일 | 오전 근무 면제 |
| 오후반차 | 0.5일 | 오후 근무 면제 |
| 병가 | 차감 없음 | 진단서 필요 |
| 경조사 | 차감 없음 | 경조사 규정 적용 |
| 출산/육아 | 차감 없음 | 법정 휴가 |
| 공가 | 차감 없음 | 예비군/민방위 등 |
### 2.2 계산 기준
- **영업일 기준** 계산 (주말·공휴일 제외)
- 연차/반차만 잔여연차에서 차감
- 병가, 경조사, 출산, 공가 등은 별도 관리
---
## 3. 잔여연차 화면 설명
### 3.1 주요 항목
| 항목 | 설명 |
|------|------|
| **부여** | 연초 배정된 총 연차일수 |
| **사용** | 승인 완료된 연차/반차 합계 |
| **잔여** | 부여 - 사용 |
| **소진율** | (사용 / 부여) x 100% |
### 3.2 연차관리대장 항목
- **사번/성명/부서/직급**: 사원 기본 정보
- **입사일**: 연차 산출의 기준일
- **근속개월수/근속년수**: 입사일로부터 자동 계산
- **연차발생일수**: 근속년수에 따라 자동 산출
- **사용연차합계**: 승인된 연차/반차 누적
- **잔여연차**: 연차발생일수 - 사용연차합계
---
## 4. 연차촉진 제도 (근로기준법 제61조)
### 4.1 개요
연차 소멸 전 사용을 촉진하여 미사용 수당 부담을 줄이는 법적 절차입니다.
### 4.2 촉진 절차
| 단계 | 시기 | 내용 |
|------|------|------|
| **1차 촉진** | 소멸 6개월 전 | 미사용 일수를 서면 통보하고 10일 이내 사용 시기 지정 요청 |
| **2차 촉진** | 1차 미응답 시 | 회사가 직접 사용 시기를 지정하여 서면 통보 |
### 4.3 효과
- 1차·2차 촉진을 모두 이행한 경우 → **미사용 수당 지급 의무 면제**
- 촉진 미이행 시 → 미사용 연차에 대해 **수당 지급 의무** 발생
> **주의**: 촉진 통보는 반드시 서면(이메일, 문서)으로 하여 기록을 남겨야 합니다.
---
## 5. 휴가 신청·승인 프로세스
### 5.1 흐름
1. **관리자가 사원 선택** 후 휴가 유형/기간/사유 입력
2. **신청 등록** → 상태: 대기
3. **승인** → 연차 자동 차감 + 근태현황 반영
4. **반려** → 반려 사유 기재, 연차 미차감
5. **취소** → 승인 후라도 취소 가능, 연차 자동 복원
### 5.2 주의사항
- 잔여연차가 부족하면 연차/반차 신청 불가
- 반차 신청 시 시작일·종료일이 동일해야 함
- 승인된 휴가 취소 시 연차가 자동 복원됨

View File

@@ -135,6 +135,18 @@ class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:
<option value="{{ $y }}">{{ $y }}</option>
@endfor
</select>
{{-- 도움말 버튼 --}}
<button type="button"
hx-get="{{ route('hr.leaves.help') }}"
hx-target="#leave-help-modal-container"
hx-swap="innerHTML"
class="ml-auto inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 hover:bg-blue-200 transition-colors"
title="휴가관리 도움말">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
</div>
<div id="balance-container">
<div class="flex justify-center items-center p-12">
@@ -240,6 +252,9 @@ class="px-4 py-2 text-sm text-white bg-blue-600 hover:bg-blue-700 rounded-lg tra
</div>
</div>
{{-- 도움말 모달 컨테이너 --}}
<div id="leave-help-modal-container"></div>
{{-- 반려 사유 모달 --}}
<div id="rejectModal" class="fixed inset-0 z-50 hidden">
<div class="fixed inset-0 bg-black/40" onclick="closeRejectModal()"></div>

View File

@@ -0,0 +1,211 @@
{{-- 휴가관리 가이드 도움말 모달 --}}
<div x-data="{ open: true }"
x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 overflow-y-auto"
aria-labelledby="leave-help-modal-title"
role="dialog"
aria-modal="true">
{{-- 배경 오버레이 --}}
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" @click="open = false; setTimeout(() => { document.getElementById('leave-help-modal-container').innerHTML = '' }, 200)"></div>
{{-- 모달 컨테이너 --}}
<div class="flex min-h-full items-center justify-center p-4">
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="relative transform overflow-hidden rounded-xl bg-white shadow-2xl transition-all w-full max-w-6xl max-h-[90vh] flex flex-col">
{{-- 헤더 --}}
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 bg-gradient-to-r from-blue-600 to-blue-700 flex-shrink-0">
<div class="flex items-center gap-3">
<div class="p-2 bg-white/20 rounded-lg">
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<h3 class="text-xl font-bold text-white" id="leave-help-modal-title">휴가관리 가이드</h3>
</div>
<button type="button"
@click="open = false; setTimeout(() => { document.getElementById('leave-help-modal-container').innerHTML = '' }, 200)"
class="p-2 text-white/80 hover:text-white hover:bg-white/20 rounded-lg transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{{-- 콘텐츠 영역 --}}
<div class="flex-1 overflow-y-auto p-6">
<div class="help-content">
{!! $htmlContent !!}
</div>
</div>
{{-- 푸터 --}}
<div class="flex items-center justify-end px-6 py-4 border-t border-gray-200 bg-gray-50 flex-shrink-0">
<button type="button"
@click="open = false; setTimeout(() => { document.getElementById('leave-help-modal-container').innerHTML = '' }, 200)"
class="px-6 py-2.5 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
닫기
</button>
</div>
</div>
</div>
</div>
<style>
.help-content {
color: #374151;
line-height: 1.75;
}
.help-content h1 {
font-size: 2rem;
font-weight: 700;
color: #111827;
border-bottom: 2px solid #e5e7eb;
padding-bottom: 1rem;
margin-bottom: 1.5rem;
}
.help-content h2 {
font-size: 1.5rem;
font-weight: 700;
color: #1d4ed8;
margin-top: 2.5rem;
margin-bottom: 1rem;
padding-left: 1rem;
border-left: 4px solid #3b82f6;
}
.help-content h3 {
font-size: 1.25rem;
font-weight: 600;
color: #1f2937;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
.help-content h4 {
font-size: 1.125rem;
font-weight: 500;
color: #374151;
margin-top: 1rem;
margin-bottom: 0.5rem;
}
.help-content p {
margin-bottom: 1rem;
color: #4b5563;
}
.help-content a {
color: #2563eb;
text-decoration: none;
}
.help-content a:hover {
text-decoration: underline;
}
.help-content strong {
font-weight: 600;
color: #111827;
}
.help-content code {
background-color: #f3f4f6;
color: #db2777;
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-family: ui-monospace, monospace;
}
.help-content pre {
background-color: #1f2937;
color: #f3f4f6;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin: 1rem 0;
}
.help-content pre code {
background: none;
color: inherit;
padding: 0;
}
.help-content blockquote {
border-left: 4px solid #60a5fa;
background-color: #eff6ff;
padding: 0.75rem 1rem;
margin: 1rem 0;
border-radius: 0 0.5rem 0.5rem 0;
color: #1f2937;
}
.help-content blockquote p {
margin: 0;
}
.help-content table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.help-content th {
background-color: #f3f4f6;
border: 1px solid #d1d5db;
padding: 0.75rem 1rem;
text-align: left;
font-weight: 600;
color: #374151;
}
.help-content td {
border: 1px solid #d1d5db;
padding: 0.75rem 1rem;
color: #4b5563;
}
.help-content tr:nth-child(even) {
background-color: #f9fafb;
}
.help-content ul {
list-style-type: disc;
padding-left: 1.5rem;
margin: 1rem 0;
}
.help-content ol {
list-style-type: decimal;
padding-left: 1.5rem;
margin: 1rem 0;
}
.help-content li {
margin-bottom: 0.5rem;
color: #4b5563;
}
.help-content hr {
border: none;
border-top: 1px solid #e5e7eb;
margin: 2rem 0;
}
</style>

View File

@@ -909,6 +909,7 @@
// 휴가관리
Route::prefix('leaves')->name('leaves.')->group(function () {
Route::get('/', [\App\Http\Controllers\HR\LeaveController::class, 'index'])->name('index');
Route::get('/help', [\App\Http\Controllers\HR\LeaveController::class, 'helpGuide'])->name('help');
});
// 급여관리