- 📱 PWA 지원: 홈화면 추가 가능한 Progressive Web App - 🎨 M-Project 색상 스키마: 하늘색, 주황색, 회색, 흰색 일관된 디자인 - 📊 대시보드: 데스크톱 캘린더 뷰 + 모바일 일일 뷰 반응형 디자인 - 📥 분류 센터: Gmail 스타일 받은편지함으로 스마트 분류 시스템 - 🤖 AI 분류 제안: 키워드 기반 자동 분류 제안 및 일괄 처리 - 📷 업로드 모달: 데스크톱(파일 선택) + 모바일(카메라/갤러리) 최적화 - 🏷️ 3가지 분류: Todo(시작일), 캘린더(마감일), 체크리스트(무기한) - 📋 체크리스트: 진행률 표시 및 완료 토글 기능 - 🔄 시놀로지 연동 준비: 메일플러스 연동을 위한 구조 설계 - 📱 반응형 UI: 모든 페이지 모바일 최적화 완료
938 lines
36 KiB
HTML
938 lines
36 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>대시보드 - Todo Project</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<style>
|
|
:root {
|
|
--primary: #3b82f6; /* 하늘색 */
|
|
--primary-dark: #2563eb; /* 진한 하늘색 */
|
|
--success: #10b981; /* 초록색 */
|
|
--warning: #f59e0b; /* 주황색 */
|
|
--danger: #ef4444; /* 빨간색 */
|
|
--gray-50: #f9fafb; /* 연한 회색 */
|
|
--gray-100: #f3f4f6; /* 회색 */
|
|
--gray-200: #e5e7eb; /* 중간 회색 */
|
|
--gray-300: #d1d5db; /* 진한 회색 */
|
|
}
|
|
|
|
body {
|
|
background-color: var(--gray-50);
|
|
}
|
|
|
|
.btn-primary {
|
|
background-color: var(--primary);
|
|
color: white;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background-color: var(--primary-dark);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
|
}
|
|
|
|
/* 캘린더 스타일 */
|
|
.calendar-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 1px;
|
|
background-color: var(--gray-200);
|
|
border-radius: 0.5rem;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.calendar-day {
|
|
background-color: white;
|
|
min-height: 120px;
|
|
padding: 8px;
|
|
position: relative;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.calendar-day:hover {
|
|
background-color: var(--gray-50);
|
|
}
|
|
|
|
.calendar-day.other-month {
|
|
background-color: #fafafa;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.calendar-day.today {
|
|
background-color: #eff6ff;
|
|
border: 2px solid var(--primary);
|
|
}
|
|
|
|
.calendar-header {
|
|
background-color: var(--gray-100);
|
|
padding: 12px 8px;
|
|
text-align: center;
|
|
font-weight: 600;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.day-number {
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.day-items {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.day-item {
|
|
font-size: 10px;
|
|
padding: 2px 4px;
|
|
border-radius: 3px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.day-item.todo {
|
|
background-color: #dbeafe;
|
|
color: #1e40af;
|
|
border-left: 3px solid var(--primary);
|
|
}
|
|
|
|
.day-item.calendar {
|
|
background-color: #fef3c7;
|
|
color: #92400e;
|
|
border-left: 3px solid var(--warning);
|
|
}
|
|
|
|
/* 모바일 일일 뷰 */
|
|
.daily-view {
|
|
display: none;
|
|
}
|
|
|
|
.daily-item {
|
|
background: white;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.2s;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.daily-item:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.time-indicator {
|
|
width: 4px;
|
|
height: 100%;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.time-indicator.todo {
|
|
background-color: var(--primary);
|
|
}
|
|
|
|
.time-indicator.calendar {
|
|
background-color: var(--warning);
|
|
}
|
|
|
|
/* 체크리스트 스타일 */
|
|
.checklist-item {
|
|
background: white;
|
|
border-radius: 0.5rem;
|
|
padding: 12px;
|
|
margin-bottom: 8px;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.checklist-item:hover {
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.checklist-item.completed {
|
|
opacity: 0.6;
|
|
background-color: #f9fafb;
|
|
}
|
|
|
|
.checkbox-custom {
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 2px solid #d1d5db;
|
|
border-radius: 3px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.checkbox-custom.checked {
|
|
background-color: var(--success);
|
|
border-color: var(--success);
|
|
color: white;
|
|
}
|
|
|
|
/* 반응형 디자인 */
|
|
@media (max-width: 768px) {
|
|
.desktop-view {
|
|
display: none !important;
|
|
}
|
|
|
|
.daily-view {
|
|
display: block !important;
|
|
}
|
|
|
|
.calendar-day {
|
|
min-height: 80px;
|
|
padding: 4px;
|
|
}
|
|
|
|
.day-item {
|
|
font-size: 9px;
|
|
padding: 1px 3px;
|
|
}
|
|
|
|
/* 모바일 업로드 모달 */
|
|
.desktop-upload {
|
|
display: none !important;
|
|
}
|
|
|
|
.mobile-upload {
|
|
display: block !important;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 769px) {
|
|
/* 데스크톱 업로드 모달 */
|
|
.mobile-upload {
|
|
display: none !important;
|
|
}
|
|
|
|
.desktop-upload {
|
|
display: block !important;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.calendar-day {
|
|
min-height: 60px;
|
|
padding: 2px;
|
|
}
|
|
|
|
.day-number {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="min-h-screen">
|
|
<!-- 헤더 -->
|
|
<header class="bg-white shadow-sm border-b">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between items-center h-16">
|
|
<div class="flex items-center">
|
|
<button onclick="goBack()" class="mr-4 text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-arrow-left text-xl"></i>
|
|
</button>
|
|
<i class="fas fa-chart-line text-2xl text-blue-500 mr-3"></i>
|
|
<h1 class="text-xl font-semibold text-gray-800">대시보드</h1>
|
|
<span class="ml-3 text-sm text-gray-500" id="currentDate"></span>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-4">
|
|
<button onclick="openUploadModal()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
|
<i class="fas fa-plus mr-1"></i>새 항목
|
|
</button>
|
|
<button onclick="goToClassify()" class="text-purple-600 hover:text-purple-800 font-medium text-sm">
|
|
<i class="fas fa-inbox mr-1"></i>분류 센터
|
|
<span class="ml-1 px-2 py-1 bg-red-100 text-red-800 text-xs rounded-full">3</span>
|
|
</button>
|
|
<button onclick="goToToday()" class="text-sm text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-calendar-day mr-1"></i>오늘
|
|
</button>
|
|
<span class="text-sm text-gray-600" id="currentUser"></span>
|
|
<button onclick="logout()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- 메인 컨텐츠 -->
|
|
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<!-- 데스크톱 뷰 -->
|
|
<div class="desktop-view">
|
|
<!-- 캘린더 네비게이션 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center space-x-4">
|
|
<button onclick="previousMonth()" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</button>
|
|
<h2 class="text-2xl font-bold text-gray-800" id="currentMonth"></h2>
|
|
<button onclick="nextMonth()" class="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-4">
|
|
<div class="flex items-center space-x-2 text-sm">
|
|
<div class="w-3 h-3 bg-blue-200 border-l-4 border-blue-500 rounded-sm"></div>
|
|
<span class="text-gray-600">Todo</span>
|
|
</div>
|
|
<div class="flex items-center space-x-2 text-sm">
|
|
<div class="w-3 h-3 bg-yellow-200 border-l-4 border-yellow-500 rounded-sm"></div>
|
|
<span class="text-gray-600">캘린더</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 캘린더 그리드 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
|
|
<div class="calendar-grid">
|
|
<!-- 요일 헤더 -->
|
|
<div class="calendar-header">일</div>
|
|
<div class="calendar-header">월</div>
|
|
<div class="calendar-header">화</div>
|
|
<div class="calendar-header">수</div>
|
|
<div class="calendar-header">목</div>
|
|
<div class="calendar-header">금</div>
|
|
<div class="calendar-header">토</div>
|
|
|
|
<!-- 캘린더 날짜들 -->
|
|
<div id="calendarDays"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 체크리스트 섹션 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h3 class="text-lg font-semibold text-gray-800">
|
|
<i class="fas fa-check-square text-green-500 mr-2"></i>체크리스트
|
|
</h3>
|
|
<div class="text-sm text-gray-600">
|
|
<span id="checklistProgress">0/0 완료</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="checklistItems" class="max-h-96 overflow-y-auto">
|
|
<!-- 체크리스트 항목들이 여기에 추가됩니다 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 모바일 뷰 -->
|
|
<div class="daily-view">
|
|
<!-- 오늘 날짜 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
|
<div class="text-center">
|
|
<h2 class="text-2xl font-bold text-gray-800" id="todayDate"></h2>
|
|
<p class="text-gray-600" id="todayWeekday"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 오늘의 일정 -->
|
|
<div class="mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-4">
|
|
<i class="fas fa-calendar-day text-blue-500 mr-2"></i>오늘의 일정
|
|
</h3>
|
|
<div id="todayItems">
|
|
<!-- 오늘의 항목들이 여기에 추가됩니다 -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 모바일 체크리스트 -->
|
|
<div class="bg-white rounded-xl shadow-sm p-6">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800">
|
|
<i class="fas fa-check-square text-green-500 mr-2"></i>체크리스트
|
|
</h3>
|
|
<div class="text-sm text-gray-600">
|
|
<span id="mobileChecklistProgress">0/0</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="mobileChecklistItems">
|
|
<!-- 모바일 체크리스트 항목들 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- 업로드 모달 -->
|
|
<div id="uploadModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
<div class="bg-white rounded-xl shadow-xl max-w-md w-full max-h-[90vh] overflow-y-auto">
|
|
<!-- 모달 헤더 -->
|
|
<div class="flex justify-between items-center p-6 border-b">
|
|
<h3 class="text-lg font-semibold text-gray-800">
|
|
<i class="fas fa-plus-circle text-blue-500 mr-2"></i>새 항목 등록
|
|
</h3>
|
|
<button onclick="closeUploadModal()" class="text-gray-400 hover:text-gray-600">
|
|
<i class="fas fa-times text-xl"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 모달 내용 -->
|
|
<div class="p-6">
|
|
<form id="uploadForm" class="space-y-4">
|
|
<!-- 메모 입력 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">메모</label>
|
|
<input type="text" id="uploadContent" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
placeholder="메모를 입력하세요..." required>
|
|
</div>
|
|
|
|
<!-- 사진 업로드 영역 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">사진 (선택사항)</label>
|
|
|
|
<!-- 데스크톱용 파일 선택 -->
|
|
<div class="desktop-upload">
|
|
<div class="border-2 border-dashed border-gray-200 rounded-lg p-6 text-center hover:border-blue-300 transition-colors">
|
|
<i class="fas fa-cloud-upload-alt text-3xl text-gray-400 mb-3"></i>
|
|
<p class="text-gray-600 mb-2">파일을 선택하거나 드래그하여 업로드</p>
|
|
<button type="button" onclick="selectFile()" class="text-blue-600 hover:text-blue-800 font-medium">
|
|
파일 선택
|
|
</button>
|
|
<input type="file" id="desktopFileInput" accept="image/*" class="hidden">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 모바일용 카메라/갤러리 선택 -->
|
|
<div class="mobile-upload hidden">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<button type="button" onclick="openCamera()" class="border-2 border-dashed border-gray-200 rounded-lg p-4 text-center hover:border-blue-300 transition-colors">
|
|
<i class="fas fa-camera text-2xl text-gray-400 mb-2"></i>
|
|
<p class="text-sm text-gray-600">카메라</p>
|
|
</button>
|
|
<button type="button" onclick="openGallery()" class="border-2 border-dashed border-gray-200 rounded-lg p-4 text-center hover:border-blue-300 transition-colors">
|
|
<i class="fas fa-images text-2xl text-gray-400 mb-2"></i>
|
|
<p class="text-sm text-gray-600">갤러리</p>
|
|
</button>
|
|
</div>
|
|
<input type="file" id="cameraInput" accept="image/*" capture="camera" class="hidden">
|
|
<input type="file" id="galleryInput" accept="image/*" class="hidden">
|
|
</div>
|
|
|
|
<!-- 사진 미리보기 -->
|
|
<div id="photoPreview" class="hidden mt-4">
|
|
<div class="relative">
|
|
<img id="previewImage" class="w-full h-48 object-cover rounded-lg" alt="미리보기">
|
|
<button type="button" onclick="removePhoto()" class="absolute top-2 right-2 bg-red-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-red-600">
|
|
<i class="fas fa-times text-sm"></i>
|
|
</button>
|
|
</div>
|
|
<div class="mt-2 text-sm text-gray-600" id="photoInfo"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 버튼 -->
|
|
<div class="flex space-x-3 pt-4">
|
|
<button type="submit" class="btn-primary flex-1 py-3 px-4 rounded-lg font-medium">
|
|
<i class="fas fa-plus mr-2"></i>등록하기
|
|
</button>
|
|
<button type="button" onclick="closeUploadModal()" class="px-4 py-3 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
|
|
취소
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로딩 오버레이 -->
|
|
<div id="loadingOverlay" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-60">
|
|
<div class="bg-white rounded-lg p-6 flex items-center space-x-3">
|
|
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
|
<span class="text-gray-700">처리 중...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JavaScript -->
|
|
<script src="static/js/auth.js"></script>
|
|
<script src="static/js/image-utils.js"></script>
|
|
<script>
|
|
let currentDate = new Date();
|
|
let calendarData = {};
|
|
let checklistData = [];
|
|
|
|
// 페이지 초기화
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
checkAuthStatus();
|
|
initializeDashboard();
|
|
});
|
|
|
|
// 대시보드 초기화
|
|
function initializeDashboard() {
|
|
updateCurrentDate();
|
|
loadCalendarData();
|
|
loadChecklistData();
|
|
renderCalendar();
|
|
renderDailyView();
|
|
renderChecklist();
|
|
}
|
|
|
|
// 현재 날짜 업데이트
|
|
function updateCurrentDate() {
|
|
const now = new Date();
|
|
const options = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' };
|
|
|
|
document.getElementById('currentDate').textContent = now.toLocaleDateString('ko-KR', {
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
|
|
document.getElementById('currentMonth').textContent = currentDate.toLocaleDateString('ko-KR', {
|
|
year: 'numeric',
|
|
month: 'long'
|
|
});
|
|
|
|
document.getElementById('todayDate').textContent = now.toLocaleDateString('ko-KR', {
|
|
month: 'long',
|
|
day: 'numeric'
|
|
});
|
|
|
|
document.getElementById('todayWeekday').textContent = now.toLocaleDateString('ko-KR', {
|
|
weekday: 'long'
|
|
});
|
|
}
|
|
|
|
// 캘린더 데이터 로드
|
|
function loadCalendarData() {
|
|
// 임시 데이터
|
|
calendarData = {
|
|
'2024-01-20': [
|
|
{ type: 'todo', title: '프로젝트 시작', time: '09:00' },
|
|
{ type: 'calendar', title: '회의 준비', time: '14:00' }
|
|
],
|
|
'2024-01-22': [
|
|
{ type: 'calendar', title: '보고서 제출', time: '17:00' }
|
|
],
|
|
'2024-01-25': [
|
|
{ type: 'todo', title: '문서 검토', time: '10:00' },
|
|
{ type: 'todo', title: '팀 미팅', time: '15:00' },
|
|
{ type: 'calendar', title: '월말 마감', time: '18:00' }
|
|
]
|
|
};
|
|
}
|
|
|
|
// 체크리스트 데이터 로드
|
|
function loadChecklistData() {
|
|
checklistData = [
|
|
{ id: 1, title: '책상 정리하기', completed: false },
|
|
{ id: 2, title: '운동 계획 세우기', completed: true },
|
|
{ id: 3, title: '독서 목록 만들기', completed: false },
|
|
{ id: 4, title: '이메일 정리', completed: false },
|
|
{ id: 5, title: '비타민 구매', completed: true }
|
|
];
|
|
}
|
|
|
|
// 캘린더 렌더링
|
|
function renderCalendar() {
|
|
const calendarDays = document.getElementById('calendarDays');
|
|
if (!calendarDays) return;
|
|
|
|
const year = currentDate.getFullYear();
|
|
const month = currentDate.getMonth();
|
|
|
|
// 월의 첫 번째 날과 마지막 날
|
|
const firstDay = new Date(year, month, 1);
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
|
|
// 캘린더 시작일 (이전 달의 마지막 주 포함)
|
|
const startDate = new Date(firstDay);
|
|
startDate.setDate(startDate.getDate() - firstDay.getDay());
|
|
|
|
// 캘린더 종료일 (다음 달의 첫 주 포함)
|
|
const endDate = new Date(lastDay);
|
|
endDate.setDate(endDate.getDate() + (6 - lastDay.getDay()));
|
|
|
|
let html = '';
|
|
const today = new Date();
|
|
|
|
for (let date = new Date(startDate); date <= endDate; date.setDate(date.getDate() + 1)) {
|
|
const dateStr = date.toISOString().split('T')[0];
|
|
const isCurrentMonth = date.getMonth() === month;
|
|
const isToday = date.toDateString() === today.toDateString();
|
|
const dayData = calendarData[dateStr] || [];
|
|
|
|
html += `
|
|
<div class="calendar-day ${!isCurrentMonth ? 'other-month' : ''} ${isToday ? 'today' : ''}"
|
|
onclick="selectDate('${dateStr}')">
|
|
<div class="day-number">${date.getDate()}</div>
|
|
<div class="day-items">
|
|
${dayData.map(item => `
|
|
<div class="day-item ${item.type}" title="${item.title} (${item.time})">
|
|
${item.title}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
calendarDays.innerHTML = html;
|
|
}
|
|
|
|
// 일일 뷰 렌더링 (모바일)
|
|
function renderDailyView() {
|
|
const todayItems = document.getElementById('todayItems');
|
|
if (!todayItems) return;
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const todayData = calendarData[today] || [];
|
|
|
|
if (todayData.length === 0) {
|
|
todayItems.innerHTML = `
|
|
<div class="text-center py-8 text-gray-500">
|
|
<i class="fas fa-calendar-day text-3xl mb-3 opacity-50"></i>
|
|
<p>오늘 예정된 일정이 없습니다.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
todayItems.innerHTML = todayData.map(item => `
|
|
<div class="daily-item p-4">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="time-indicator ${item.type}"></div>
|
|
<div class="flex-1">
|
|
<h4 class="font-medium text-gray-900">${item.title}</h4>
|
|
<p class="text-sm text-gray-600">
|
|
<i class="fas fa-clock mr-1"></i>${item.time}
|
|
</p>
|
|
</div>
|
|
<div class="text-xs px-2 py-1 rounded-full ${item.type === 'todo' ? 'bg-blue-100 text-blue-800' : 'bg-yellow-100 text-yellow-800'}">
|
|
${item.type === 'todo' ? 'Todo' : '캘린더'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// 체크리스트 렌더링
|
|
function renderChecklist() {
|
|
const checklistItems = document.getElementById('checklistItems');
|
|
const mobileChecklistItems = document.getElementById('mobileChecklistItems');
|
|
const checklistProgress = document.getElementById('checklistProgress');
|
|
const mobileChecklistProgress = document.getElementById('mobileChecklistProgress');
|
|
|
|
const completed = checklistData.filter(item => item.completed).length;
|
|
const total = checklistData.length;
|
|
const progressText = `${completed}/${total} 완료`;
|
|
|
|
if (checklistProgress) checklistProgress.textContent = progressText;
|
|
if (mobileChecklistProgress) mobileChecklistProgress.textContent = `${completed}/${total}`;
|
|
|
|
const html = checklistData.map(item => `
|
|
<div class="checklist-item ${item.completed ? 'completed' : ''}">
|
|
<div class="flex items-center space-x-3">
|
|
<div class="checkbox-custom ${item.completed ? 'checked' : ''}"
|
|
onclick="toggleChecklistItem(${item.id})">
|
|
${item.completed ? '<i class="fas fa-check text-xs"></i>' : ''}
|
|
</div>
|
|
<span class="flex-1 ${item.completed ? 'line-through text-gray-500' : 'text-gray-900'}">${item.title}</span>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
if (checklistItems) checklistItems.innerHTML = html;
|
|
if (mobileChecklistItems) mobileChecklistItems.innerHTML = html;
|
|
}
|
|
|
|
// 체크리스트 항목 토글
|
|
function toggleChecklistItem(id) {
|
|
const item = checklistData.find(item => item.id === id);
|
|
if (item) {
|
|
item.completed = !item.completed;
|
|
renderChecklist();
|
|
// TODO: API 호출
|
|
}
|
|
}
|
|
|
|
// 이전 달
|
|
function previousMonth() {
|
|
currentDate.setMonth(currentDate.getMonth() - 1);
|
|
updateCurrentDate();
|
|
renderCalendar();
|
|
}
|
|
|
|
// 다음 달
|
|
function nextMonth() {
|
|
currentDate.setMonth(currentDate.getMonth() + 1);
|
|
updateCurrentDate();
|
|
renderCalendar();
|
|
}
|
|
|
|
// 오늘로 이동
|
|
function goToToday() {
|
|
currentDate = new Date();
|
|
updateCurrentDate();
|
|
renderCalendar();
|
|
renderDailyView();
|
|
}
|
|
|
|
// 날짜 선택
|
|
function selectDate(dateStr) {
|
|
console.log('선택된 날짜:', dateStr);
|
|
// TODO: 선택된 날짜의 상세 정보 표시
|
|
}
|
|
|
|
// 뒤로 가기
|
|
function goBack() {
|
|
window.location.href = 'index.html';
|
|
}
|
|
|
|
// 분류 센터로 이동
|
|
function goToClassify() {
|
|
window.location.href = 'classify.html';
|
|
}
|
|
|
|
// 업로드 모달 관련 변수
|
|
let currentPhoto = null;
|
|
|
|
// 업로드 모달 열기
|
|
function openUploadModal() {
|
|
document.getElementById('uploadModal').classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
// 업로드 모달 닫기
|
|
function closeUploadModal() {
|
|
document.getElementById('uploadModal').classList.add('hidden');
|
|
document.body.style.overflow = 'auto';
|
|
clearUploadForm();
|
|
}
|
|
|
|
// 업로드 폼 초기화
|
|
function clearUploadForm() {
|
|
document.getElementById('uploadForm').reset();
|
|
removePhoto();
|
|
}
|
|
|
|
// 파일 선택 (데스크톱)
|
|
function selectFile() {
|
|
document.getElementById('desktopFileInput').click();
|
|
}
|
|
|
|
// 카메라 열기 (모바일)
|
|
function openCamera() {
|
|
document.getElementById('cameraInput').click();
|
|
}
|
|
|
|
// 갤러리 열기 (모바일)
|
|
function openGallery() {
|
|
document.getElementById('galleryInput').click();
|
|
}
|
|
|
|
// 사진 제거
|
|
function removePhoto() {
|
|
currentPhoto = null;
|
|
const previewContainer = document.getElementById('photoPreview');
|
|
const previewImage = document.getElementById('previewImage');
|
|
|
|
if (previewContainer) {
|
|
previewContainer.classList.add('hidden');
|
|
}
|
|
|
|
if (previewImage) {
|
|
previewImage.src = '';
|
|
}
|
|
|
|
// 파일 입력 초기화
|
|
const inputs = ['desktopFileInput', 'cameraInput', 'galleryInput'];
|
|
inputs.forEach(id => {
|
|
const input = document.getElementById(id);
|
|
if (input) input.value = '';
|
|
});
|
|
}
|
|
|
|
// 사진 업로드 처리
|
|
async function handlePhotoUpload(event) {
|
|
const files = event.target.files;
|
|
if (!files || files.length === 0) return;
|
|
|
|
const file = files[0];
|
|
|
|
try {
|
|
showLoading(true);
|
|
|
|
// 이미지 압축 (ImageUtils가 있는 경우)
|
|
let processedImage;
|
|
if (window.ImageUtils) {
|
|
processedImage = await ImageUtils.compressImage(file, {
|
|
maxWidth: 800,
|
|
maxHeight: 600,
|
|
quality: 0.8
|
|
});
|
|
} else {
|
|
// 기본 처리
|
|
processedImage = await fileToBase64(file);
|
|
}
|
|
|
|
currentPhoto = processedImage;
|
|
|
|
// 미리보기 표시
|
|
const previewContainer = document.getElementById('photoPreview');
|
|
const previewImage = document.getElementById('previewImage');
|
|
const photoInfo = document.getElementById('photoInfo');
|
|
|
|
if (previewContainer && previewImage) {
|
|
previewImage.src = processedImage;
|
|
previewContainer.classList.remove('hidden');
|
|
}
|
|
|
|
if (photoInfo) {
|
|
const fileSize = Math.round(file.size / 1024);
|
|
photoInfo.textContent = `${file.name} (${fileSize}KB)`;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('이미지 처리 실패:', error);
|
|
alert('이미지 처리에 실패했습니다.');
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
// 파일을 Base64로 변환
|
|
function fileToBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => resolve(reader.result);
|
|
reader.onerror = reject;
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|
|
|
|
// 업로드 폼 제출
|
|
async function handleUploadSubmit(event) {
|
|
event.preventDefault();
|
|
|
|
const content = document.getElementById('uploadContent').value.trim();
|
|
if (!content) {
|
|
alert('메모를 입력해주세요.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
showLoading(true);
|
|
|
|
const itemData = {
|
|
content: content,
|
|
photo: currentPhoto,
|
|
created_at: new Date().toISOString()
|
|
};
|
|
|
|
// TODO: API 호출하여 항목 저장
|
|
console.log('새 항목 등록:', itemData);
|
|
|
|
// 성공 메시지
|
|
alert('항목이 등록되었습니다!');
|
|
|
|
// 모달 닫기 및 데이터 새로고침
|
|
closeUploadModal();
|
|
initializeDashboard();
|
|
|
|
} catch (error) {
|
|
console.error('항목 등록 실패:', error);
|
|
alert('항목 등록에 실패했습니다.');
|
|
} finally {
|
|
showLoading(false);
|
|
}
|
|
}
|
|
|
|
// 로딩 표시
|
|
function showLoading(show) {
|
|
const overlay = document.getElementById('loadingOverlay');
|
|
if (overlay) {
|
|
if (show) {
|
|
overlay.classList.remove('hidden');
|
|
} else {
|
|
overlay.classList.add('hidden');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 이벤트 리스너 설정
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// 파일 입력 이벤트 리스너
|
|
const fileInputs = ['desktopFileInput', 'cameraInput', 'galleryInput'];
|
|
fileInputs.forEach(id => {
|
|
const input = document.getElementById(id);
|
|
if (input) {
|
|
input.addEventListener('change', handlePhotoUpload);
|
|
}
|
|
});
|
|
|
|
// 업로드 폼 이벤트 리스너
|
|
const uploadForm = document.getElementById('uploadForm');
|
|
if (uploadForm) {
|
|
uploadForm.addEventListener('submit', handleUploadSubmit);
|
|
}
|
|
|
|
// 드래그 앤 드롭 (데스크톱)
|
|
const desktopUpload = document.querySelector('.desktop-upload');
|
|
if (desktopUpload) {
|
|
desktopUpload.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
e.currentTarget.classList.add('border-blue-300');
|
|
});
|
|
|
|
desktopUpload.addEventListener('dragleave', (e) => {
|
|
e.preventDefault();
|
|
e.currentTarget.classList.remove('border-blue-300');
|
|
});
|
|
|
|
desktopUpload.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
e.currentTarget.classList.remove('border-blue-300');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
const input = document.getElementById('desktopFileInput');
|
|
if (input) {
|
|
input.files = files;
|
|
handlePhotoUpload({ target: input });
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 모달 외부 클릭 시 닫기
|
|
const modal = document.getElementById('uploadModal');
|
|
if (modal) {
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
closeUploadModal();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// 전역 함수 등록
|
|
window.previousMonth = previousMonth;
|
|
window.nextMonth = nextMonth;
|
|
window.goToToday = goToToday;
|
|
window.selectDate = selectDate;
|
|
window.toggleChecklistItem = toggleChecklistItem;
|
|
window.goBack = goBack;
|
|
window.openUploadModal = openUploadModal;
|
|
window.closeUploadModal = closeUploadModal;
|
|
window.selectFile = selectFile;
|
|
window.openCamera = openCamera;
|
|
window.openGallery = openGallery;
|
|
window.removePhoto = removePhoto;
|
|
window.goToClassify = goToClassify;
|
|
</script>
|
|
</body>
|
|
</html>
|