Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions articles/2026-02-04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
**[2026λ…„ 02μ›” 04일] μΊ˜λ¦°λ” λ“œλž˜κ·Έμ•€λ“œλ‘­ κΈ°λŠ₯ μ™„μ„± 및 API 연동을 μœ„ν•œ 인프라 ꡬ좕**

νŒ€ Calio의 개발 μΌμ§€μž…λ‹ˆλ‹€. μ˜€λŠ˜μ€ 두 개의 핡심 브랜치 μž‘μ—…μ΄ `develop`에 ν†΅ν•©λ˜λ©΄μ„œ ν”„λ‘œμ νŠΈμ˜ κΈ°λŠ₯적 완성도와 μ•„ν‚€ν…μ²˜ 기반이 크게 κ°•ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ£Όμš” μ„±κ³ΌλŠ” μΊ˜λ¦°λ” 이벀트의 λ“œλž˜κ·Έ μ•€ λ“œλ‘­ 및 리사이징 κΈ°λŠ₯ κ΅¬ν˜„ μ™„λ£Œμ™€ λ”λΆˆμ–΄, ν–₯ν›„ λ°±μ—”λ“œ API 연동을 μœ„ν•œ React Query, Axios, 그리고 Suspense/Error Boundary μ‹œμŠ€ν…œμ˜ κΈ°λ³Έ μ„ΈνŒ…μ„ 마친 κ²ƒμž…λ‹ˆλ‹€. 이λ₯Ό 톡해 ν”„λ‘ νŠΈμ—”λ“œ 개발의 λ‹€μŒ 단계인 μ‹€μ œ 데이터 연동 μ€€λΉ„λ₯Ό μ™„λ£Œν–ˆμŠ΅λ‹ˆλ‹€.

---

## 🌿 Branch: develop

### [Yeonjin Kim] Merge pull request #18 from 2026-Capstone-Project/auto-daily-log

Yeonjin Kim κ°œλ°œμžλŠ” μžλ™ 일지 생성 μ‹œμŠ€ν…œμ˜ 정기적인 μ—…λ°μ΄νŠΈλ₯Ό λ³‘ν•©ν–ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 2μ›” 3일자 개발 아티클을 μƒμ„±ν•˜λŠ” λ‹¨μˆœ μœ μ§€λ³΄μˆ˜ μž‘μ—…μž…λ‹ˆλ‹€.

```diff
πŸ“ [Calio] 였늘의 개발 아티클이 λ„μ°©ν–ˆμŠ΅λ‹ˆλ‹€! (2026-02-03)
- **articles/2026-02-03.md** λ³€κ²½:
```diff
@@ -0,0 +1 @@
+μ˜€λŠ˜μ€ μ—…λ°μ΄νŠΈλœ μž‘μ—…μ΄ μ—†μŠ΅λ‹ˆλ‹€. 내일 더 νŒŒμ΄νŒ…ν•΄λ΄μš”! πŸš€
\ No newline at end of file
```

### [Yeonjin Kim] Merge pull request #13 from 2026-Capstone-Project/feature/#12-이벀트-λ“œλž˜κ·Έλ“œλ‘­-λ³€κ²½λœ-μŠ€νƒ€μΌ-반영

Yeonjin Kim κ°œλ°œμžλŠ” μΊ˜λ¦°λ”μ˜ 핡심 κΈ°λŠ₯인 이벀트 λ“œλž˜κ·Έ μ•€ λ“œλ‘­(Drag and Drop, D&D) 및 리사이징 κΈ°λŠ₯을 ν†΅ν•©ν•˜κ³ , λ³€κ²½λœ λ””μžμΈμ„ μ „λ°˜μ μœΌλ‘œ λ°˜μ˜ν–ˆμŠ΅λ‹ˆλ‹€. 이 μž‘μ—…μ€ `react-big-calendar`의 D&D μ• λ“œμ˜¨μ„ ν™œμš©ν•˜λŠ” λ™μ‹œμ—, 특히 Day Viewμ—μ„œ μ»€μŠ€ν…€ D&D λ‘œμ§μ„ κ΅¬ν˜„ν•˜λŠ” 데 쀑점을 λ‘μ—ˆμŠ΅λ‹ˆλ‹€.

**μ£Όμš” 기술 뢄석:**

1. **D&D 톡합 및 νƒ€μž… ν™•μž₯:**
* `react-big-calendar/lib/addons/dragAndDrop`이 `CustomCalendar.tsx`에 μ μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
* `CalendarEvent` νƒ€μž…μ— `type: 'todo' | 'schedule'`κ³Ό `isDone?: boolean` ν•„λ“œκ°€ μΆ”κ°€λ˜μ–΄ To-do ν•­λͺ© κ΄€λ¦¬μ˜ 기반이 λ§ˆλ ¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/features/Calendar/domain/types.ts
export interface CalendarEvent {
id: number | string
title: string
start: stringOrDate
end: stringOrDate
allDay?: boolean
type: 'todo' | 'schedule'
isDone?: boolean
color: EventColorType
// ...
}
```

2. **μ»€μŠ€ν…€ Day View D&D 둜직 κ΅¬ν˜„:**
* Day View λ‚΄μ—μ„œ μ •λ°€ν•œ μ‹œκ°„ λ‹¨μœ„μ˜ 이벀트 이동 및 크기 μ‘°μ ˆμ„ μœ„ν•΄ `dayView/dragHandlers.ts`와 `dayView/timeHelpers.ts`에 포인터 이벀트 기반의 μ»€μŠ€ν…€ 둜직이 λŒ€κ±° μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. `getMinutesPerPixel` ν•¨μˆ˜λ₯Ό 톡해 ν”½μ…€ μ΄λ™λŸ‰μ„ μ‹œκ°„ λ³€ν™”λŸ‰μœΌλ‘œ λ³€ν™˜ν•˜λŠ” 핡심 둜직이 ν¬ν•¨λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/features/Calendar/components/CustomView/dayView/dragHandlers.ts
export type DragMode = 'move' | 'resize'
export type DragState = {
event: CalendarEvent
startClientY: number
startDate: Date
endDate: Date
deltaMinutes: number
pointerId: number
target: EventTarget | null
mode:
// ... (이후 λ“œλž˜κ·Έ μƒνƒœ 관리 및 ν•Έλ“€λŸ¬ κ΅¬ν˜„)
```

3. **이벀트 μ»΄ν¬λ„ŒνŠΈ μ—…λ°μ΄νŠΈ 및 To-do 반영:**
* Month/Week 이벀트 μ»΄ν¬λ„ŒνŠΈ(`CustomMonthEvent.tsx`, `CustomWeekEvent.tsx`)에 To-do νƒ€μž… 이벀트λ₯Ό μœ„ν•œ μ²΄ν¬λ°•μŠ€ μŠ€νƒ€μΌ(`TodoCheckbox`)이 μΆ”κ°€λ˜μ—ˆμœΌλ©°, `onToggleTodo` ν•Έλ“€λŸ¬λ₯Ό 받을 수 μžˆλ„λ‘ ν™•μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/features/Calendar/components/CustomEvent/CustomEvent.style.ts
export const TodoCheckbox = styled.input`
max-width: 10px;
max-height: 10px;
width: 10px;
height: 10px;
aspect-ratio: 1 / 1;
appearance: none;
border: 1px
// ...
`
```

4. **λͺ¨λ‹¬ 및 ν›… 뢄리:**
* μΊ˜λ¦°λ” νŽ˜μ΄μ§€μ˜ λ³΅μž‘μ„±μ„ 쀄이기 μœ„ν•΄ λͺ¨λ‹¬ κ΄€λ ¨ 둜직이 `CalendarModals.tsx`둜 λΆ„λ¦¬λ˜μ—ˆκ³ , μƒˆλ‘œμš΄ ν›…λ“€(`useCalendarPortals`, `useCalendarProps`, `useStoredCalendarView` λ“±)이 λ„μž…λ˜μ–΄ μƒνƒœ 관리와 λ°˜μ‘ν˜• μ²˜λ¦¬κ°€ λͺ¨λ“ˆν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

5. **UI/μŠ€νƒ€μΌ μ‘°μ •:**
* `AddModalLayout`의 λ„ˆλΉ„κ°€ 420pxμ—μ„œ 400px둜 μ‘°μ •λ˜μ—ˆκ³ , `TerminationPanel`의 μΊ˜λ¦°λ” νŒμ˜€λ²„ z-indexκ°€ 3000μ—μ„œ 20000으둜 상ν–₯λ˜μ–΄ λ‹€λ₯Έ λͺ¨λ‹¬κ³Όμ˜ κ²ΉμΉ¨ 문제λ₯Ό ν•΄κ²°ν–ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/shared/ui/modal/AddModalLayout/AddModalLayout.style.ts
export const ModalWrapper = styled.div<{ mode: 'modal' | 'inline' }>`
width: 100%;
max-width: 90vw;
width: 400px;
// ...
`
// src/shared/ui/modal/EditConfirmModal/EditConfirmModal.tsx
// 이벀트 μˆ˜μ • λ²„νŠΌ ν…μŠ€νŠΈκ°€ '적용'으둜 λ³€κ²½λ˜μ–΄ μ‚¬μš©μžμ˜ μ˜λ„λ₯Ό λͺ…ν™•νžˆ ν–ˆμŠ΅λ‹ˆλ‹€.
// ...
// >
// 적용
// </S.EditButton>
// ...
```

---

## 🌿 Branch: feature/#19-api-연결을-μœ„ν•œ-κΈ°λ³Έ-μ„ΈνŒ…

### [Yeonjin Kim] feat: λΌμš°ν„°μ— 404 μΆ”κ°€, Suspenseμœ„ν•œ μ€€λΉ„

Yeonjin Kim κ°œλ°œμžλŠ” API 연동을 μœ„ν•œ 인프라 ꡬ좕 μž‘μ—…μ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. 이 μ»€λ°‹μ—μ„œλŠ” React Router에 404 μ—λŸ¬ νŽ˜μ΄μ§€λ₯Ό μΆ”κ°€ν•˜κ³ , 데이터 λ‘œλ”© 및 μ—λŸ¬ 처리λ₯Ό μœ„ν•œ κΈ°λ³Έ μ»΄ν¬λ„ŒνŠΈμ™€ 훅을 μ •μ˜ν–ˆμŠ΅λ‹ˆλ‹€.

**μ£Όμš” 기술 뢄석:**

1. **λΌμš°νŒ… 및 μ—λŸ¬ νŽ˜μ΄μ§€:**
* `Router.tsx`에 μ™€μΌλ“œμΉ΄λ“œ 경둜(`*`)λ₯Ό μΆ”κ°€ν•˜μ—¬ μ •μ˜λ˜μ§€ μ•Šμ€ 경둜 μ ‘κ·Ό μ‹œ `ErrorPage`λ₯Ό λ Œλ”λ§ν•˜λ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/routes/Router.tsx
export const router = createBrowserRouter([
AuthRoutes,
MainRoutes,
{
path: '*',
element: <ErrorPage />,
},
])
```

2. **React Query μ»€μŠ€ν…€ ν›… μ •μ˜:**
* `customQuery.ts`에 `useQuery`, `useSuspenseQuery`, `useMutation` λ“± React Query의 핡심 훅듀을 κ°μ‹ΈλŠ” μ»€μŠ€ν…€ 훅이 μ •μ˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 전역적인 `staleTime` (3λΆ„) 및 `retry` 섀정을 μΌκ΄€λ˜κ²Œ μ μš©ν•˜κΈ° μœ„ν•¨μž…λ‹ˆλ‹€.

```typescript
// src/shared/hooks/customQuery.ts
const STALE_TIME = 1000 * 60 * 3
const RETRY = 1
const MUTATION_RETRY = 1

// ... (useQuery, useSuspenseQuery λ“± μ»€μŠ€ν…€ ν›… μ •μ˜)
```

3. **Async Boundary κ΅¬ν˜„:**
* `AsyncBoundary.tsx`에 `ErrorBoundary`와 `Suspense`λ₯Ό κ²°ν•©ν•œ μ»΄ν¬λ„ŒνŠΈκ°€ κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 데이터 페칭 쀑 λ°œμƒν•˜λŠ” λ‘œλ”© μƒνƒœμ™€ μ—λŸ¬ μƒνƒœλ₯Ό μ„ μ–Έμ μœΌλ‘œ 관리할 수 있게 ν•˜λ©°, μ»΄ν¬λ„ŒνŠΈ 트리의 μ•ˆμ •μ„±μ„ λ†’μž…λ‹ˆλ‹€.

```typescript
// src/shared/ui/common/AsyncBoundary/AsyncBoundary.tsx
class ErrorBoundary extends Component<
// ... (Error Boundary κ΅¬ν˜„)
const AsyncBoundary = ({ children, fallback, errorFallback, resetKeys }: AsyncBoundaryProps) => {
// ... (Suspense와 ErrorBoundary μ‘°ν•©)
}
```

### [Yeonjin Kim] feat: axios μ„ΈνŒ…

API 톡신을 μœ„ν•œ κΈ°λ³Έ HTTP ν΄λΌμ΄μ–ΈνŠΈμΈ Axios 섀정이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.

**μ£Όμš” 기술 뢄석:**

* `axiosInstance`λ₯Ό μƒμ„±ν•˜κ³  ν™˜κ²½ λ³€μˆ˜(`VITE_SERVER_URL`)λ₯Ό μ‚¬μš©ν•˜μ—¬ `baseURL`을 μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.
* CORS ν™˜κ²½μ—μ„œ 인증 정보λ₯Ό μ „λ‹¬ν•˜κΈ° μœ„ν•΄ `withCredentials: true` μ˜΅μ…˜μ„ ν™œμ„±ν™”ν–ˆμŠ΅λ‹ˆλ‹€.
* 응닡 μΈν„°μ…‰ν„°μ˜ κΈ°λ³Έ ꡬ쑰λ₯Ό λ§ˆλ ¨ν•˜μ—¬, ν–₯ν›„ 인증 토큰 λ§Œλ£Œλ‚˜ μ „μ—­ μ—λŸ¬ 처리 λ‘œμ§μ„ μΆ”κ°€ν•  수 μžˆλ„λ‘ μ€€λΉ„ν–ˆμŠ΅λ‹ˆλ‹€.

```typescript
// src/shared/api/axios.ts
import axios from 'axios'

export const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_SERVER_URL,
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
})

//TODO: @yujin5959 λ‚˜μ€‘μ— 이뢀뢄 ν•΄μ£Όμ„Έμš©~
axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
return Promise.reject(error)
},
)

export default axiosInstance
```