init: SR System — Helpdesk & ITAM Portal

This commit is contained in:
pray4skylark
2026-06-02 13:50:39 +09:00
commit 832c9f096f
262 changed files with 30470 additions and 0 deletions
+739
View File
@@ -0,0 +1,739 @@
# SR System — Feature Implementation Plan
> Analysis date: 2026-05-26
> Scope: Calendar, Notifications, User Profile, ADMIN/MANAGER Management
---
## Executive Summary
| Feature | Existing Backend | New Backend | Frontend | Priority | Effort |
|---------|:---:|:---:|:---:|:---:|:---:|
| Calendar | **None** | Calendar aggregation endpoint | CalendarPage + CalendarWidget | P2 | 5d |
| Notifications | **Full** (read, mark-read, unread count) | Add `link_url` field to Notification entity | NotificationDropdown | P1 | 2d |
| User Profile | **Partial** (getMe, portfolio, dispatch-history) | ChangePassword endpoint + Extended EmployeeSummary | ProfilePage | P1 | 4d |
| ADMIN/MANAGER | **Partial** (employee CRUD, report listing) | Dispatch CRUD, CommonCode CRUD, Report summary aggregations | EmployeeEditModal, DispatchManager, ReportDashboard, SettingsPage | P1 (Reports), P2 (Dispatch), P2 (Settings) | 8d total |
**Total estimated effort: 19 days** (full-time, single dev). Parallel work possible — Admin features and Profile can be developed concurrently with Calendar and Notifications.
---
## 1. Calendar Feature
### 1.1 Current State Analysis
| Aspect | Status |
|--------|--------|
| Header Calendar icon | Route: `/issues` (no calendar relevance) |
| Backend calendar endpoint | **Does not exist** |
| Data with dates | `Ticket.dueDate`, `Report.targetDate`, `Project.startDate/endDate`, `DispatchHistory.startDate/endDate` |
| Frontend calendar page | **Does not exist** |
The Header component (line 65):
```tsx
<button onClick={() => router.push('/issues')} ...>
<Calendar className="w-5 h-5" />
</button>
```
### 1.2 What's Missing
1. **Backend: Calendar aggregation endpoint** — No single endpoint to fetch events across domains. Need:
- `GET /api/v1/calendar?start=YYYY-MM-DD&end=YYYY-MM-DD` — returns unified events
- Events aggregated from: Ticket due dates, Report target dates, Dispatch start/end dates
2. **Backend: Calendar event entity** — No dedicated schedule/schedule_type entity. Two options:
- **Option A (Recommended):** Aggregation endpoint that queries existing entities (Ticket, Report, DispatchHistory) and returns a unified DTO. Zero schema change.
- **Option B:** New `Schedule` entity with schedule_type (MEETING, DEADLINE, MILESTONE) — more flexible but adds complexity.
3. **Frontend: Calendar page** (`/app/(main)/calendar/page.tsx`) — Full calendar view with month/week/day modes.
4. **Frontend: Calendar event types** — Ticket due dates, report deadlines, dispatch milestones.
### 1.3 Design Proposal
#### 1.3.1 Backend: Calendar Aggregation Endpoint
**New DTO: `CalendarEventDto`**
```java
public record CalendarEventDto(
String eventType, // TICKET_DUE, REPORT_DUE, DISPATCH_START, DISPATCH_END
Long sourceId, // ticketId / reportId / dispatchId
String title,
String description,
LocalDate eventDate,
String color, // semantic color per type
String linkUrl // navigation URL (/issues/123, /work-reports/456, etc.)
) {}
```
**New Controller Method (in a new `CalendarController` or added to an existing controller):**
```java
@GetMapping("/api/v1/calendar")
@PreAuthorize("hasAnyRole('EMPLOYEE','MANAGER','ADMIN')")
ResponseEntity<ApiResponse<List<CalendarEventDto>>> getCalendar(
@RequestParam @DateTimeFormat(iso=ISO.DATE) LocalDate start,
@RequestParam @DateTimeFormat(iso=ISO.DATE) LocalDate end,
@CurrentUser Employee currentUser)
```
**New Service: `CalendarService`**
- Queries `TicketRepository.findByDueDateBetweenAndRequester_EmpId(start, end, empId)` (for employee) or all tickets (for manager/admin)
- Queries `ReportRepository.findByTargetDateBetweenAndUser_EmpId(start, end, empId)` or all
- Queries `DispatchHistoryRepository.findByStartDateBetween(start, end)` (only active, DISPATCHED)
- Merges results into `List<CalendarEventDto>`
**Event color mapping:**
| Event Type | Color | Icon |
|-----------|-------|------|
| TICKET_DUE | `#3B82F6` (blue) | Ticket |
| REPORT_DUE | `#10B981` (green) | FileText |
| DISPATCH_START | `#F59E0B` (amber) | Play |
| DISPATCH_END | `#EF4444` (red) | Stop |
#### 1.3.2 Frontend: Calendar Page
**Route:** `/calendar`
**Design:**
```
┌─────────────────────────────────────────────────────────────────┐
│ Calendar ◀ 2026.05 ▶ Today │
├──┬────┬────┬────┬────┬────┬────┐ │
│S │ M │ T │ W │ T │ F │ S │ │
├──┼────┼────┼────┼────┼────┼────┤ │
│ │ │ │ │ │ 1 │ 2 │ │
│ │ │ │ │ │ ● │ │ │
│ │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ │
│ │ ● │ ● │ │ ■ │ │ │ │
│ │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │ │
│ │ │ ▲ │ ● │ │ │ ■ │ │
│ │ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ │
│ │ │ │ ● │ │ │ │ │
│ │ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ │
│ │ │ │ ■ │ │ │ │ │
├──┴────┴────┴────┴────┴────┴────┤ │
│ Legend: ● Report Due ■ Dispatch ▲ Ticket Due │
└─────────────────────────────────────────────────────────────────┘
```
**Components to create:**
- `src/app/(main)/calendar/page.tsx` — Main calendar page
- `src/components/calendar/CalendarGrid.tsx` — Month grid view (no external library — custom CSS Grid, 7-column)
- `src/components/calendar/CalendarEvent.tsx` — Event chip with color dot + title (truncated)
- `src/components/calendar/EventDetailPanel.tsx` — Slide-over or modal showing event details
- `src/components/calendar/CalendarWidget.tsx` — Mini calendar widget for dashboard (optional, P3)
**Added to sidebar:**
```tsx
{ name: 'Calendar', href: '/calendar', icon: Calendar }
```
### 1.4 Implementation Plan — Calendar
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Create `CalendarEventDto` record | `backend/.../dto/CalendarEventDto.java` | 0.3d |
| 2 | Create `CalendarService` with aggregation logic | `backend/.../service/CalendarService.java` | 0.7d |
| 3 | Create `CalendarController` endpoint | `backend/.../controller/CalendarController.java` | 0.3d |
| 4 | Add `calendarService` to frontend services | `src/services/calendarService.ts` | 0.3d |
| 5 | Add `CalendarEvent` type to frontend types | `src/types/index.ts` | 0.2d |
| 6 | Build `CalendarGrid` component | `src/components/calendar/CalendarGrid.tsx` | 1.5d |
| 7 | Build `CalendarEvent` chip component | `src/components/calendar/CalendarEvent.tsx` | 0.5d |
| 8 | Build `EventDetailPanel` slide-over | `src/components/calendar/EventDetailPanel.tsx` | 0.5d |
| 9 | Build `/calendar` page | `src/app/(main)/calendar/page.tsx` | 0.5d |
| 10 | Update Header Calendar icon to `/calendar` | `src/components/layout/Header.tsx` | 0.1d |
| 11 | Add Calendar to Sidebar | `src/components/layout/Sidebar.tsx` | 0.1d |
| **Total** | | | **5 days** |
---
## 2. Notification System
### 2.1 Current State Analysis
| Aspect | Status |
|--------|--------|
| Backend API | ✅ Full: `GET /notifications`, `GET /notifications/unread`, `PUT /notifications/{id}/read` |
| Frontend notificationStore | ✅ Polls unread count (30s interval), fetches list, marks as read |
| Notification entity fields | `notificationId, type, title, message, isRead, createdAt` |
| Bell icon behavior | Calls `fetchNotifications()` on click but **no dropdown/modal renders** |
| Missing | `link_url` field — no way to navigate to related entity from notification |
### 2.2 What's Missing
1. **Frontend: NotificationDropdown component** — Bell click should show a dropdown listing recent notifications.
2. **Frontend: Click-to-navigate** — Clicking a notification should navigate to the relevant page (ticket detail, report, etc.).
3. **Backend: `linkUrl` field** — Notification entity has `type` but no link to the source entity.
### 2.3 Design Proposal
#### 2.3.1 Backend: Add linkUrl to Notification Entity
**Migration:**
```sql
ALTER TABLE tb_notification ADD COLUMN link_url VARCHAR(500);
```
**Entity update (`Notification.java`):**
```java
@Column(name = "link_url", length = 500)
private String linkUrl;
```
**Service update** — When creating notifications, set `linkUrl`:
| Type | linkUrl Example |
|------|----------------|
| TICKET_ASSIGNED | `/issues/123` |
| TICKET_RESOLVED | `/issues/123` |
| REPORT_APPROVED | `/work-reports/456` |
| REPORT_REJECTED | `/work-reports/456` |
| SLA_WARNING | `/issues/789` |
| EQUIPMENT_ASSIGNED | `/itam/123` |
#### 2.3.2 Frontend: Notification Dropdown
**Component: `NotificationDropdown.tsx`**
```
┌─────────────────────────────────┐
│ Notifications [x] │ ← Header: "Notifications" + close button
├─────────────────────────────────┤
│ 🔵 Ticket #123 assigned to you │ ← Unread: blue left border
│ 2 minutes ago → │
├─────────────────────────────────┤
│ ✅ Report #456 approved │ ← Read: no left border
│ 1 hour ago → │
├─────────────────────────────────┤
│ ⚠️ SLA Warning: #789 Overdue │ ← Critical: red left border
│ 3 hours ago → │
├─────────────────────────────────┤
│ │
│ View All ↗ │ ← Link to full notification page
└─────────────────────────────────┘
```
**Behavior:**
- Dropdown slides down from bell icon (absolute positioned)
- Shows latest 10 notifications
- Click notification → `markAsRead(id)` + navigate to `linkUrl`
- Click "View All" → `/notifications` page (optional, P3)
- Click outside → close dropdown
- Escape key → close dropdown
**Layout:**
- Max height: 400px, overflow-y: auto
- Each item: 12px padding, border-bottom, hover bg-gray-50
- Width: 360px
- Unread items: `bg-indigo-50/30`, blue dot (6px) left
- Categories: Icon + title (bold 13px) + time ago (gray 11px)
### 2.4 Implementation Plan — Notifications
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Add `linkUrl` column to Notification entity | `Notification.java` + migration | 0.3d |
| 2 | Update NotificationListener to set linkUrl | `backend/.../listener/NotificationListener.java` | 0.3d |
| 3 | Add `linkUrl` to frontend Notification type | `src/types/index.ts` | 0.1d |
| 4 | Build `NotificationDropdown` component | `src/components/layout/NotificationDropdown.tsx` | 1.0d |
| 5 | Integrate into Header (replace bell onClick) | `src/components/layout/Header.tsx` | 0.2d |
| 6 | Add mark-all-as-read endpoint (optional) | Backend + service | 0.2d |
| **Total** | | | **2 days** |
---
## 3. User Profile Management
### 3.1 Current State Analysis
| Aspect | Status |
|--------|--------|
| `GET /api/v1/employees/me` | ✅ Returns `EmployeeSummary` (empId, name, email, roleCode, dispatchStatus) |
| `GET /api/v1/employees/{empId}/dispatch-histories` | ✅ Returns `DispatchHistory[]` |
| `GET /api/v1/employees/{empId}/portfolio` | ✅ Returns `PortfolioResponse[]` (with reports + resolved tickets) |
| `PUT /api/v1/employees/{empId}` | ✅ Admin-only update endpoint |
| Change password endpoint | **Missing** |
| Extended profile info (phone, empStatus, joinedDate) | **Missing** from `/me` (EmployeeSummary is minimal) |
| `/profile` page | **Does not exist** |
### 3.2 What's Missing
1. **Backend: `PUT /api/v1/employees/me/password`** — Change password endpoint for current user.
2. **Backend: Extended `/me` response** — `EmployeeSummary` is too minimal. Need phone, empStatus, joinedDate, dispatchStatus label.
3. **Frontend: `/profile` page** — View/edit profile, change password, view dispatch history, view portfolio.
4. **Frontend: Profile editing** — Allow user to edit their own name, email, phone (or restrict to admin-only for certain fields).
### 3.3 Design Proposal
#### 3.3.1 Backend: Change Password Endpoint
**New DTO: `ChangePasswordRequest`**
```java
public record ChangePasswordRequest(
@NotBlank String currentPassword,
@NotBlank @Size(min=8) String newPassword
) {}
```
**New Endpoint in AuthController or EmployeeController:**
```java
@PutMapping("/api/v1/employees/me/password")
@PreAuthorize("hasAnyRole('EMPLOYEE','MANAGER','ADMIN')")
ResponseEntity<ApiResponse<Void>> changePassword(
@Valid @RequestBody ChangePasswordRequest request,
@CurrentUser Employee currentUser)
```
**Logic:**
1. Verify `currentPassword` matches stored hash
2. Encode `newPassword` and save
3. Invalidate existing tokens (optional — add `token_version` field)
#### 3.3.2 Backend: Extended EmployeeSummary
**Update `EmployeeSummary` to include:**
```java
public record EmployeeSummary(
Long empId,
String loginId,
String name,
String email,
String phone, // NEW
RoleCode roleCode,
EmpStatus empStatus, // NEW
DispatchStatus dispatchStatus,
LocalDate joinedDate, // NEW
LocalDate leftDate // NEW (nullable)
) {}
```
#### 3.3.3 Frontend: Profile Page Design
**URL:** `/profile`
**For EMPLOYEE (own profile):**
```
┌─────────────────────────────────────────────────────┐
│ [Avatar] 김민수 │
│ 파견직원 · 재직중 · 파견중 │
│ │
│ ┌─ Personal Info ─────────────────────────────────┐ │
│ │ Login ID: minsu.kim │ │
│ │ Name: 김민수 │ │
│ │ Email: minsu@example.com │ │
│ │ Phone: 010-1234-5678 │ │
│ │ Joined: 2024-03-01 │ │
│ │ Status: ACTIVE (DISPATCHED) │ │
│ │ [Edit Info] [Change Password] │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ Dispatch History ──────────────────────────────┐ │
│ │ Timeline with projects, dates, roles │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌─ Portfolio Summary ─────────────────────────────┐ │
│ │ Aggregated stats: projects, tickets, reports │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
**For MANAGER/ADMIN viewing another employee:**
- URL: `/profile/[empId]`
- Same layout but with employee selector/dropdown at top
- Manager can view but not edit (unless admin)
- Admin can edit fields
#### 3.3.4 Components to Create
| Component | Description |
|-----------|-------------|
| `src/app/(main)/profile/page.tsx` | Own profile view |
| `src/app/(main)/profile/[empId]/page.tsx` | View other employee's profile (MANAGER/ADMIN) |
| `src/components/profile/ProfileCard.tsx` | Personal info card with edit/change-password buttons |
| `src/components/profile/EditProfileModal.tsx` | Modal for editing personal info (admin only or self?) |
| `src/components/profile/ChangePasswordModal.tsx` | Modal with current/new/confirm password fields |
| `src/components/profile/DispatchTimeline.tsx` | Reuse timeline from /dispatch page |
| `src/components/profile/PortfolioStats.tsx` | Summary stats cards (total projects, total tickets resolved, etc.) |
### 3.4 Implementation Plan — User Profile
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Create `ChangePasswordRequest` DTO | Backend dto | 0.1d |
| 2 | Add `changePassword` to AuthService | `AuthService.java` | 0.3d |
| 3 | Add `PUT /employees/me/password` endpoint | `AuthController.java` or `EmployeeController.java` | 0.2d |
| 4 | Extend `EmployeeSummary` to include phone, empStatus, joinedDate | `EmployeeSummary.java` + `EmployeeService.toSummary()` | 0.2d |
| 5 | Add `getMeFull()` and `changePassword()` to employeeService | `src/services/employeeService.ts` | 0.2d |
| 6 | Build `ProfileCard` component | `src/components/profile/ProfileCard.tsx` | 0.5d |
| 7 | Build `ChangePasswordModal` component | `src/components/profile/ChangePasswordModal.tsx` | 0.5d |
| 8 | Build `EditProfileModal` component (admin-only for role/status changes; self-service for email/phone) | `src/components/profile/EditProfileModal.tsx` | 0.5d |
| 9 | Build `PortfolioStats` component | `src/components/profile/PortfolioStats.tsx` | 0.3d |
| 10 | Build `/profile` page | `src/app/(main)/profile/page.tsx` | 0.8d |
| 11 | Build `/profile/[empId]` page (manager/admin view) | `src/app/(main)/profile/[empId]/page.tsx` | 0.4d |
| 12 | Make user avatar/name in Header clickable → `/profile` | `src/components/layout/Header.tsx` | 0.1d |
| **Total** | | | **4 days** |
---
## 4. ADMIN/MANAGER Management Features
### 4.1 Current State Analysis
#### 4.1.1 Employee CRUD
| Operation | Backend | Frontend |
|-----------|:---:|:---:|
| List/View | ✅ `GET /employees` (MANAGER/ADMIN) | ✅ `/employees` page with table view |
| Create | ✅ `POST /employees` (ADMIN) | ✅ Modal in `/employees` page |
| Update | ✅ `PUT /employees/{id}` (ADMIN) | ❌ Only `MoreHorizontal` button with no handler |
| Delete | ✅ `DELETE /employees/{id}` (ADMIN) | ❌ No delete UI |
| Search | ✅ `GET /employees/search` | ✅ Search bar in `/employees` |
**Gap:** Edit and delete functionality missing from frontend. The `MoreHorizontal` button (line 454-456 in employees/page.tsx) is a blank `<button>` with no click handler.
#### 4.1.2 Dispatch Assignment
| Operation | Backend | Frontend |
|-----------|:---:|:---:|
| Create dispatch | ❌ No endpoint | ❌ No page |
| List dispatch by employee | ✅ `GET /employees/{id}/dispatch-histories` | ❌ Only shown in employee's own /dispatch page |
| List all dispatches | ❌ No endpoint | ❌ No management view |
| Link employee to project | ❌ No endpoint | ❌ |
**Gap:** There is NO controller/service method for creating a DispatchHistory. The DispatchHistoryService exists but only has repository calls, no create/update methods. MANAGER/ADMIN have no way to assign employees to projects.
#### 4.1.3 Report Monitoring Dashboard
| Operation | Backend | Frontend |
|-----------|:---:|:---:|
| List all reports | ✅ ReportController (MANAGER/ADMIN see all) | ✅ Works via work-reports page |
| Report review/approval | ✅ `PUT /reports/{id}/review` | ❌ No approval UI for managers |
| Pending count | ❌ No aggregate endpoint | Dashboard fetches `totalElements` from page=1 query (approximate) |
| Report statistics | ❌ No stats endpoint | ❌ |
**Gap:** Manager needs a dedicated report monitoring page showing all submitted reports in a table with quick approve/reject actions. Dashboard count is a workaround.
#### 4.1.4 System Settings (Common Codes)
| Operation | Backend | Frontend |
|-----------|:---:|:---:|
| List codes by group | ✅ `GET /common-codes/{groupCode}` | ❌ No page |
| Create/Update/Delete codes | ❌ No endpoints | ❌ |
**Gap:** Common code management is a pure ADMIN function. There are no CRUD endpoints for common codes and no settings page.
### 4.2 Design Proposals
#### 4.2.1 Employee Edit/Delete
**Frontend: `EmployeeEditModal.tsx`**
- Opens on `MoreHorizontal` click
- Edit: name, email, roleCode, empStatus, dispatchStatus, joinedDate
- Delete: Confirm dialog → soft delete
- After save → refresh employee list
**Add to `/employees` page:**
```tsx
// Replace blank MoreHorizontal button:
<button onClick={() => setEditingEmployee(emp)}>
<MoreHorizontal className="w-4 h-4" />
</button>
```
#### 4.2.2 Dispatch Management
**Backend needs:**
**New Controller: `DispatchHistoryController.java`**
```java
@PostMapping("/api/v1/dispatch-histories")
@PreAuthorize("hasAnyRole('MANAGER','ADMIN')")
ResponseEntity<ApiResponse<DispatchHistory>> create(CreateDispatchRequest request)
@PutMapping("/api/v1/dispatch-histories/{dispatchId}")
@PreAuthorize("hasAnyRole('MANAGER','ADMIN')")
ResponseEntity<ApiResponse<DispatchHistory>> update(Long dispatchId, UpdateDispatchRequest request)
@GetMapping("/api/v1/dispatch-histories")
@PreAuthorize("hasAnyRole('MANAGER','ADMIN')")
ResponseEntity<ApiResponse<Page<DispatchHistory>>> findAll(Pageable pageable, @RequestParam(required=false) Long empId, @RequestParam(required=false) Long projectId)
```
**New DTOs:**
```java
public record CreateDispatchRequest(
@NotNull Long empId,
@NotNull Long projectId,
@NotNull LocalDate startDate,
LocalDate endDate,
@NotBlank String roleTitle,
@NotNull Integer contributionRate,
@NotBlank String individualTech,
@NotBlank String performedTasks
) {}
```
**Frontend: Dispatch Management Page**
**URL:** `/dispatch/manage` (MANAGER/ADMIN only)
```
┌─────────────────────────────────────────────────────┐
│ Dispatch Management [+ New Dispatch] │
├─────────┬──────────┬─────────┬─────────┬────────────┤
│ Employee│ Project │ Role │ Period │ Actions │
├─────────┼──────────┼─────────┼─────────┼────────────┤
│ 김민수 │ Samsung │ Backend │ 24.03~ │ [Edit][Del]│
│ 이영희 │ LG CNS │ Frontend│ 24.06~ │ [Edit][Del]│
│ ... │ ... │ ... │ ... │ ... │
└─────────┴──────────┴─────────┴─────────┴────────────┘
```
**Create Dispatch modal:**
- Employee selector (autocomplete search)
- Project selector (dropdown from `/projects/my` or all projects)
- Date range picker
- Role title, contribution rate, tech stack, tasks
#### 4.2.3 Report Monitoring Dashboard
**URL:** `/work-reports/review` (MANAGER/ADMIN only)
```
┌─────────────────────────────────────────────────────┐
│ Report Review Dashboard │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Submitted │ │Pending │ │Approved │ │Rejected │ │
│ │ 12 │ │ 5 │ │ 45 │ │ 3 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────┤
│ [Filter: SUBMITTED ▼] [Date: 2026-05 ▼] [Search...]│
├─────────┬─────────┬──────────┬─────────┬────────────┤
│ Employee│ Type │ Date │ Status │ Action │
├─────────┼─────────┼──────────┼─────────┼────────────┤
│ 김민수 │ DAILY │ 05-26 │ SUBMIT │[✅][❌][👁] │
│ 이영희 │ WEEKLY │ 05-20 │ SUBMIT │[✅][❌][👁] │
│ 박철수 │ MONTHLY │ 05-01 │ SUBMIT │[✅][❌][👁] │
└─────────┴─────────┴──────────┴─────────┴────────────┘
```
**Action buttons:**
- ✅ Approve → `PUT /reports/{id}/review { status: "APPROVED" }`
- ❌ Reject → Opens rejection modal with required feedback text
- 👁 View → Navigate to report detail
**New Backend Endpoint (optional):**
```java
@GetMapping("/api/v1/reports/summary")
@PreAuthorize("hasAnyRole('MANAGER','ADMIN')")
ResponseEntity<ApiResponse<ReportSummary>> getSummary()
// Returns counts by status: { submitted, approved, rejected, draft }
```
#### 4.2.4 System Settings (Common Codes)
**Backend needs:**
**New endpoints in `CommonCodeController.java`:**
```java
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
ResponseEntity<ApiResponse<CommonCode>> create(CommonCode code)
@PutMapping("/{codeId}")
@PreAuthorize("hasRole('ADMIN')")
ResponseEntity<ApiResponse<CommonCode>> update(Long codeId, CommonCode code)
@DeleteMapping("/{codeId}")
@PreAuthorize("hasRole('ADMIN')")
ResponseEntity<ApiResponse<Void>> delete(Long codeId)
```
**Frontend: Settings Page**
**URL:** `/settings` (ADMIN only)
```
┌─────────────────────────────────────────────────────┐
│ System Settings │
│ ┌──────────────────────┬───────────────────────────┐ │
│ │ Common Codes │ Code Group: [Role ▼] │ │
│ │ ├ Group List │ ┌──────┬──────┬────┬────┐ │ │
│ │ │ • Role │ │ Code │ Name │Sort│ │ │ │
│ │ │ • Report Type │ ├──────┼──────┼────┼────┤ │ │
│ │ │ • Ticket Category │ │EMPLOY│파견직원│ 1 │[✏️][🗑]│ │
│ │ │ • Equipment Type │ │MANAG│본사관리│ 2 │[✏️][🗑]│ │
│ │ │ • ... │ │ADMIN│최고 │ 3 │[✏️][🗑]│ │
│ │ │ │ └──────┴──────┴────┴────┘ │ │
│ │ │ │ [+ Add Code] │ │
│ └──────────────────────┴───────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Other Settings │ │
│ │ □ Enable email notifications │ │
│ │ □ Enable Slack notifications │ │
│ │ Default SLA hours: [48 ▼] │ │
│ │ [Save] │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
### 4.3 Implementation Plan — ADMIN/MANAGER Features
#### Phase A: Report Monitoring Dashboard (P1, ~2 days)
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Add `ReportSummary` DTO + `GET /reports/summary` endpoint | Backend | 0.3d |
| 2 | Add `reportService.getSummary()` to frontend services | `src/services/reportService.ts` | 0.1d |
| 3 | Build report review dashboard page | `src/app/(main)/work-reports/review/page.tsx` | 1.0d |
| 4 | Add approve/reject inline actions | Same as above + modal | 0.4d |
| 5 | Add navigation from dashboard "Pending Reports" card to review page | `src/app/(main)/page.tsx` | 0.1d |
| 6 | Add sidebar link for MANAGER/ADMIN (conditional render) | `src/components/layout/Sidebar.tsx` | 0.1d |
#### Phase B: Employee Edit/Delete (P1, ~1.5 days, already mostly built on backend)
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Build `EmployeeEditModal` component | `src/components/employees/EmployeeEditModal.tsx` | 0.5d |
| 2 | Add delete confirmation dialog | Same component | 0.2d |
| 3 | Wire `MoreHorizontal` button to open edit modal | `src/app/(main)/employees/page.tsx` | 0.3d |
| 4 | Refresh list after edit/delete | Same page | 0.2d |
| 5 | Add employee detail view (linking to `/profile/[empId]`) | Same page | 0.3d |
#### Phase C: Dispatch Management (P2, ~3 days)
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Create `CreateDispatchRequest` + `UpdateDispatchRequest` DTOs | Backend | 0.2d |
| 2 | Create `DispatchHistoryController` with CRUD endpoints | Backend | 0.5d |
| 3 | Add `dispatchHistoryService.getAll()`, `.create()`, `.update()`, `.delete()` to frontend | `src/services/dispatchHistoryService.ts` (new) | 0.3d |
| 4 | Build dispatch management page | `src/app/(main)/dispatch/manage/page.tsx` | 1.0d |
| 5 | Build create/edit dispatch modal with employee search + project select | Same + modal component | 0.8d |
| 6 | Add sidebar link (MANAGER/ADMIN only) | `src/components/layout/Sidebar.tsx` | 0.1d |
#### Phase D: System Settings (P2, ~1.5 days)
| Step | Task | File(s) | Effort |
|------|------|---------|--------|
| 1 | Add POST/PUT/DELETE to CommonCodeController | Backend | 0.3d |
| 2 | Add create/update/delete to `commonCodeService` | `src/services/commonCodeService.ts` (new) | 0.2d |
| 3 | Build settings page with code group list + editor | `src/app/(main)/settings/page.tsx` | 1.0d |
| 4 | Add sidebar link (ADMIN only) | `src/components/layout/Sidebar.tsx` | 0.1d |
---
## 5. Consolidated File List
### 5.1 Backend — Files to Create
| File | Feature | Description |
|------|---------|-------------|
| `dto/CalendarEventDto.java` | Calendar | Calendar event aggregation DTO |
| `service/CalendarService.java` | Calendar | Aggregation logic from Ticket, Report, Dispatch |
| `controller/CalendarController.java` | Calendar | `GET /api/v1/calendar` endpoint |
| `dto/ChangePasswordRequest.java` | Profile | Password change request DTO |
| `dto/CreateDispatchRequest.java` | Dispatch | Dispatch creation request DTO |
| `dto/UpdateDispatchRequest.java` | Dispatch | Dispatch update request DTO |
| `controller/DispatchHistoryController.java` | Dispatch | Full CRUD for dispatch histories |
| `service/DispatchManagementService.java` | Dispatch | Management-level dispatch operations |
### 5.2 Backend — Files to Modify
| File | Feature | Change |
|------|---------|--------|
| `entity/Notification.java` | Notifications | Add `linkUrl` column |
| `dto/EmployeeSummary.java` | Profile | Add phone, empStatus, joinedDate, leftDate fields |
| `service/AuthService.java` | Profile | Add `changePassword()` method |
| `controller/AuthController.java` or `EmployeeController.java` | Profile | Add `PUT /employees/me/password` endpoint |
| `service/EmployeeService.java` | Profile | Update `toSummary()` to include new fields |
| `controller/ReportController.java` | Reports | Add `GET /reports/summary` endpoint |
| `controller/CommonCodeController.java` | Settings | Add POST, PUT, DELETE endpoints |
| `service/CommonCodeService.java` | Settings | Add create, update, delete methods |
### 5.3 Frontend — Files to Create
| File | Feature | Description |
|------|---------|-------------|
| `src/services/calendarService.ts` | Calendar | `GET /calendar?start=&end=` |
| `src/services/dispatchHistoryService.ts` | Dispatch | Full CRUD for dispatch histories |
| `src/services/commonCodeService.ts` | Settings | CRUD for common codes |
| `src/components/calendar/CalendarGrid.tsx` | Calendar | Monthly calendar grid component |
| `src/components/calendar/CalendarEvent.tsx` | Calendar | Event chip component |
| `src/components/calendar/EventDetailPanel.tsx` | Calendar | Event detail modal |
| `src/components/layout/NotificationDropdown.tsx` | Notifications | Bell dropdown component |
| `src/components/profile/ProfileCard.tsx` | Profile | Personal info display/edit card |
| `src/components/profile/ChangePasswordModal.tsx` | Profile | Password change modal |
| `src/components/profile/EditProfileModal.tsx` | Profile | Profile edit modal |
| `src/components/profile/PortfolioStats.tsx` | Profile | Portfolio summary stats |
| `src/components/employees/EmployeeEditModal.tsx` | Admin | Employee edit + delete modal |
| `src/components/dispatch/DispatchFormModal.tsx` | Admin | Create/edit dispatch form |
| `src/app/(main)/calendar/page.tsx` | Calendar | Calendar page |
| `src/app/(main)/profile/page.tsx` | Profile | Own profile page |
| `src/app/(main)/profile/[empId]/page.tsx` | Profile | Employee profile (manager view) |
| `src/app/(main)/work-reports/review/page.tsx` | Reports | Report review dashboard |
| `src/app/(main)/dispatch/manage/page.tsx` | Dispatch | Dispatch management page |
| `src/app/(main)/settings/page.tsx` | Settings | System settings page |
### 5.4 Frontend — Files to Modify
| File | Feature | Change |
|------|---------|--------|
| `src/types/index.ts` | All | Add `Notification.linkUrl`, `CalendarEvent`, `ReportSummary`, `CalendarEvent` types |
| `src/services/index.ts` | All | Export new services |
| `src/services/employeeService.ts` | Profile | Add `getMeFull()`, `changePassword()` |
| `src/services/reportService.ts` | Reports | Add `getSummary()` |
| `src/components/layout/Header.tsx` | Calendar + Profile + Notifications | Calendar → `/calendar`, integrate NotificationDropdown, avatar → `/profile` |
| `src/components/layout/Sidebar.tsx` | All | Add Calendar, Report Review, Settings links (with RBAC) |
| `src/app/(main)/page.tsx` | Reports | Link "Pending Reports" card to `/work-reports/review` |
| `src/app/(main)/employees/page.tsx` | Admin | Wire MoreHorizontal to edit modal |
---
## 6. Recommended Implementation Order
```
Week 1: Quick Wins
Day 1-2: Notifications (Dropdown UI + linkUrl) ← P1, ~2d
Day 3-5: User Profile (ProfilePage + ChangePassword) ← P1, ~4d
Week 2: Calendar + Report Management
Day 1-3: Employee Edit/Delete (EmployeeEditModal) ← P1, ~1.5d
Day 3-5: Report Monitoring Dashboard ← P1, ~2d
Week 3: Calendar + Dispatch
Day 1-5: Calendar (Backend aggregation + Full Calendar page) ← P2, ~5d
Week 4: Admin Features
Day 1-3: Dispatch Management (CRUD endpoints + page) ← P2, ~3d
Day 4-5: System Settings (CommonCode CRUD + Settings page) ← P2, ~1.5d
```
**Alternative — parallel by developer:**
- Dev A: Calendar (5d) + Settings (1.5d) = 6.5d
- Dev B: Notifications (2d) + Profile (4d) = 6d
- Dev C: Employee Edit (1.5d) + Report Dashboard (2d) + Dispatch (3d) = 6.5d
- **Total calendar time: ~1.5 weeks with 3 developers**
---
## 7. Risk Assessment
| Risk | Impact | Mitigation |
|------|--------|------------|
| Calendar aggregation performance with large datasets | Medium | Add DB indexes on `due_date`, `target_date`; paginate results |
| Notification polling load | Low | Already 30s interval — acceptable. Could go to 60s after 30min idle |
| Password change breaks existing sessions | Medium | Option A: Invalidate all tokens on password change. Option B: Add `tokenVersion` to user and check on JWT validation |
| DispatchHistory creation without project exists validation | Medium | Backend must validate project exists and is not completed/hold |
| Common code changes break existing dropdowns | High | Never hard-delete common codes — add `is_active` toggle instead. Frontend caches codes on load |
| Concurrent employee edits by multiple admins | Low | Add `@Version` optimistic lock to Employee entity |
---
## 8. Quick Wins (Low Effort, High Impact)
If you only have 1 day, do these:
1. **Notification Dropdown** (~2h) — Make the bell icon functional. Just build the dropdown UI, no backend changes needed. Notifications already have type/title/message.
2. **Header Calendar fix** (~5min) — Change `onClick={() => router.push('/issues')}` to `onClick={() => router.push('/calendar')}` even before building the calendar page — at least the button has a logical destination.
3. **Header avatar → /profile** (~5min) — Wrap the avatar/name in a Link to `/profile`.
4. **Employee MoreHorizontal → Edit** (~2h) — Wire the existing `PUT /employees/{id}` backend to a simple edit form.