getCookie 제거

This commit is contained in:
2026-01-10 21:59:11 +09:00
parent ef7914d5c6
commit 1b8cd8577e
30 changed files with 195 additions and 145 deletions

View File

@@ -0,0 +1,49 @@
<template>
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100;">
<div
v-for="toast in toasts"
:key="toast.id"
class="toast show"
role="alert"
>
<div class="toast-header" :class="headerClass(toast.type)">
<i :class="iconClass(toast.type)" class="me-2"></i>
<strong class="me-auto">{{ toast.title }}</strong>
<button type="button" class="btn-close btn-close-white" @click="remove(toast.id)"></button>
</div>
<div class="toast-body">
{{ toast.message }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { toasts, remove } = useToast()
function headerClass(type: string) {
const classes: Record<string, string> = {
success: 'bg-success text-white',
error: 'bg-danger text-white',
warning: 'bg-warning text-dark',
info: 'bg-primary text-white'
}
return classes[type] || classes.info
}
function iconClass(type: string) {
const icons: Record<string, string> = {
success: 'bi bi-check-circle-fill',
error: 'bi bi-x-circle-fill',
warning: 'bi bi-exclamation-triangle-fill',
info: 'bi bi-info-circle-fill'
}
return icons[type] || icons.info
}
</script>
<style scoped>
.toast {
min-width: 300px;
}
</style>

View File

@@ -3,7 +3,7 @@
<div class="container-fluid">
<NuxtLink class="navbar-brand" to="/">
<i class="bi bi-clipboard-check me-2"></i>
주간업무보고
업무관리프로그램
</NuxtLink>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">

View File

@@ -0,0 +1,60 @@
/**
* Bootstrap Toast Composable
* 전역 토스트 메시지 관리
*/
interface ToastMessage {
id: number
type: 'success' | 'error' | 'warning' | 'info'
title?: string
message: string
duration?: number
}
const toasts = ref<ToastMessage[]>([])
let toastId = 0
export function useToast() {
function show(message: string, type: ToastMessage['type'] = 'info', title?: string, duration = 3000) {
const id = ++toastId
toasts.value.push({ id, type, title, message, duration })
if (duration > 0) {
setTimeout(() => {
remove(id)
}, duration)
}
}
function success(message: string, title?: string, duration = 3000) {
show(message, 'success', title || '성공', duration)
}
function error(message: string, title?: string, duration = 5000) {
show(message, 'error', title || '오류', duration)
}
function warning(message: string, title?: string, duration = 4000) {
show(message, 'warning', title || '경고', duration)
}
function info(message: string, title?: string, duration = 3000) {
show(message, 'info', title || '알림', duration)
}
function remove(id: number) {
const idx = toasts.value.findIndex(t => t.id === id)
if (idx > -1) {
toasts.value.splice(idx, 1)
}
}
return {
toasts,
show,
success,
error,
warning,
info,
remove
}
}

View File

@@ -457,6 +457,7 @@ const { fetchCurrentUser } = useAuth()
const route = useRoute()
const { getWeekInfo, getWeekDates, getLastWeekInfo, getActualCurrentWeekInfo, getMonday, changeWeek: calcChangeWeek } = useWeekCalc()
const router = useRouter()
const toast = useToast()
interface TaskItem {
projectId: number
@@ -656,7 +657,7 @@ async function setDefaultWeek(userId: number) {
const isFutureWeek = year > currentWeek.year || (year === currentWeek.year && week > currentWeek.week)
if (isFutureWeek) {
alert('작성할 수 없는 주차입니다.')
toast.warning('작성할 수 없는 주차입니다.')
router.replace('/report/weekly')
return false
}
@@ -860,7 +861,7 @@ function formatHoursDisplay(hours: number): string {
async function handleSubmit() {
const validTasks = form.value.tasks.filter(t => t.description.trim())
if (validTasks.length === 0) {
alert('최소 1개 이상의 Task를 입력해주세요.')
toast.warning('최소 1개 이상의 Task를 입력해주세요.')
return
}
@@ -885,10 +886,10 @@ async function handleSubmit() {
remarkDescription: form.value.remarkDescription
}
})
alert('주간보고가 작성되었습니다.')
toast.success('주간보고가 작성되었습니다.')
router.push('/report/weekly')
} catch (e: any) {
alert(e.data?.message || '저장에 실패했습니다.')
toast.error(e.data?.message || '저장에 실패했습니다.')
} finally {
isSaving.value = false
}
@@ -1000,11 +1001,11 @@ async function runAiParse() {
}
aiStep.value = 'matching'
} else {
alert('분석된 내용이 없습니다.')
toast.warning('분석된 내용이 없습니다.')
}
} catch (e: any) {
console.error('=== AI 분석 에러 ===', e)
alert(e.data?.message || 'AI 분석에 실패했습니다.')
toast.error(e.data?.message || 'AI 분석에 실패했습니다.')
} finally {
isAiParsing.value = false
}