추가
This commit is contained in:
60
frontend/composables/useApi.ts
Normal file
60
frontend/composables/useApi.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* API 호출 유틸리티 composable
|
||||
*/
|
||||
|
||||
interface ApiOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
body?: any
|
||||
query?: Record<string, any>
|
||||
}
|
||||
|
||||
export function useApi() {
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
/**
|
||||
* API 호출 래퍼
|
||||
*/
|
||||
async function call<T>(url: string, options: ApiOptions = {}): Promise<T> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const response = await $fetch<T>(url, {
|
||||
method: options.method || 'GET',
|
||||
body: options.body,
|
||||
query: options.query
|
||||
})
|
||||
return response
|
||||
} catch (err: any) {
|
||||
const message = err.data?.message || err.message || '요청 처리 중 오류가 발생했습니다.'
|
||||
error.value = message
|
||||
throw new Error(message)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 편의 메서드들
|
||||
const get = <T>(url: string, query?: Record<string, any>) =>
|
||||
call<T>(url, { method: 'GET', query })
|
||||
|
||||
const post = <T>(url: string, body?: any) =>
|
||||
call<T>(url, { method: 'POST', body })
|
||||
|
||||
const put = <T>(url: string, body?: any) =>
|
||||
call<T>(url, { method: 'PUT', body })
|
||||
|
||||
const del = <T>(url: string) =>
|
||||
call<T>(url, { method: 'DELETE' })
|
||||
|
||||
return {
|
||||
isLoading: readonly(isLoading),
|
||||
error: readonly(error),
|
||||
call,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del
|
||||
}
|
||||
}
|
||||
89
frontend/composables/useAuth.ts
Normal file
89
frontend/composables/useAuth.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 인증 상태 관리 composable
|
||||
*/
|
||||
|
||||
interface User {
|
||||
employeeId: number
|
||||
employeeName: string
|
||||
employeeEmail: string
|
||||
employeePosition: string | null
|
||||
}
|
||||
|
||||
// 전역 상태
|
||||
const currentUser = ref<User | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
export function useAuth() {
|
||||
/**
|
||||
* 현재 로그인 사용자 조회
|
||||
*/
|
||||
async function fetchCurrentUser(): Promise<User | null> {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const response = await $fetch<{ user: User | null }>('/api/auth/current-user')
|
||||
currentUser.value = response.user
|
||||
return response.user
|
||||
} catch (error) {
|
||||
currentUser.value = null
|
||||
return null
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일+이름으로 로그인
|
||||
*/
|
||||
async function login(email: string, name: string): Promise<User> {
|
||||
const response = await $fetch<{ user: User }>('/api/auth/login', {
|
||||
method: 'POST',
|
||||
body: { email, name }
|
||||
})
|
||||
currentUser.value = response.user
|
||||
return response.user
|
||||
}
|
||||
|
||||
/**
|
||||
* 기존 사용자 선택 로그인
|
||||
*/
|
||||
async function selectUser(employeeId: number): Promise<User> {
|
||||
const response = await $fetch<{ user: User }>('/api/auth/select-user', {
|
||||
method: 'POST',
|
||||
body: { employeeId }
|
||||
})
|
||||
currentUser.value = response.user
|
||||
return response.user
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근 로그인 사용자 목록
|
||||
*/
|
||||
async function getRecentUsers(): Promise<User[]> {
|
||||
const response = await $fetch<{ users: User[] }>('/api/auth/recent-users')
|
||||
return response.users
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃
|
||||
*/
|
||||
async function logout(): Promise<void> {
|
||||
await $fetch('/api/auth/logout', { method: 'POST' })
|
||||
currentUser.value = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 여부 확인
|
||||
*/
|
||||
const isLoggedIn = computed(() => currentUser.value !== null)
|
||||
|
||||
return {
|
||||
currentUser: readonly(currentUser),
|
||||
isLoading: readonly(isLoading),
|
||||
isLoggedIn,
|
||||
fetchCurrentUser,
|
||||
login,
|
||||
selectUser,
|
||||
getRecentUsers,
|
||||
logout
|
||||
}
|
||||
}
|
||||
119
frontend/composables/useWeekCalc.ts
Normal file
119
frontend/composables/useWeekCalc.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* ISO 8601 주차 계산 composable
|
||||
*/
|
||||
|
||||
interface WeekInfo {
|
||||
year: number
|
||||
week: number
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
startDateStr: string
|
||||
endDateStr: string
|
||||
weekString: string // "2026-W01" 형식
|
||||
}
|
||||
|
||||
export function useWeekCalc() {
|
||||
/**
|
||||
* 특정 날짜의 ISO 주차 정보 반환
|
||||
*/
|
||||
function getWeekInfo(date: Date = new Date()): WeekInfo {
|
||||
const target = new Date(date)
|
||||
target.setHours(0, 0, 0, 0)
|
||||
|
||||
// 목요일 기준으로 연도 판단 (ISO 규칙)
|
||||
const thursday = new Date(target)
|
||||
thursday.setDate(target.getDate() - ((target.getDay() + 6) % 7) + 3)
|
||||
|
||||
const year = thursday.getFullYear()
|
||||
const firstThursday = new Date(year, 0, 4)
|
||||
firstThursday.setDate(firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3)
|
||||
|
||||
const week = Math.ceil((thursday.getTime() - firstThursday.getTime()) / (7 * 24 * 60 * 60 * 1000)) + 1
|
||||
|
||||
// 해당 주의 월요일
|
||||
const monday = new Date(target)
|
||||
monday.setDate(target.getDate() - ((target.getDay() + 6) % 7))
|
||||
|
||||
// 해당 주의 일요일
|
||||
const sunday = new Date(monday)
|
||||
sunday.setDate(monday.getDate() + 6)
|
||||
|
||||
return {
|
||||
year,
|
||||
week,
|
||||
startDate: monday,
|
||||
endDate: sunday,
|
||||
startDateStr: formatDate(monday),
|
||||
endDateStr: formatDate(sunday),
|
||||
weekString: `${year}-W${week.toString().padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이번 주 정보
|
||||
*/
|
||||
function getCurrentWeekInfo(): WeekInfo {
|
||||
return getWeekInfo(new Date())
|
||||
}
|
||||
|
||||
/**
|
||||
* 지난 주 정보
|
||||
*/
|
||||
function getLastWeekInfo(): WeekInfo {
|
||||
const lastWeek = new Date()
|
||||
lastWeek.setDate(lastWeek.getDate() - 7)
|
||||
return getWeekInfo(lastWeek)
|
||||
}
|
||||
|
||||
/**
|
||||
* 날짜 포맷 (YYYY-MM-DD)
|
||||
*/
|
||||
function formatDate(date: Date): string {
|
||||
return date.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* 주차 문자열 파싱
|
||||
*/
|
||||
function parseWeekString(weekStr: string): { year: number; week: number } | null {
|
||||
const match = weekStr.match(/^(\d{4})-W(\d{2})$/)
|
||||
if (!match) return null
|
||||
return { year: parseInt(match[1]), week: parseInt(match[2]) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 주차별 날짜 범위 텍스트
|
||||
*/
|
||||
function getWeekRangeText(year: number, week: number): string {
|
||||
// 해당 연도 첫 번째 목요일 찾기
|
||||
const jan4 = new Date(year, 0, 4)
|
||||
const firstThursday = new Date(jan4)
|
||||
firstThursday.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7) + 3)
|
||||
|
||||
// 해당 주차의 월요일
|
||||
const monday = new Date(firstThursday)
|
||||
monday.setDate(firstThursday.getDate() - 3 + (week - 1) * 7)
|
||||
|
||||
const sunday = new Date(monday)
|
||||
sunday.setDate(monday.getDate() + 6)
|
||||
|
||||
return `${formatDateKr(monday)} ~ ${formatDateKr(sunday)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 한국어 날짜 포맷 (M월 D일)
|
||||
*/
|
||||
function formatDateKr(date: Date): string {
|
||||
return `${date.getMonth() + 1}월 ${date.getDate()}일`
|
||||
}
|
||||
|
||||
return {
|
||||
getWeekInfo,
|
||||
getCurrentWeekInfo,
|
||||
getLastWeekInfo,
|
||||
formatDate,
|
||||
parseWeekString,
|
||||
getWeekRangeText,
|
||||
formatDateKr
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user