Content is user-generated and unverified.

구글 시트 기반 주간 계획표 웹앱 제작 가이드

📋 개요

구글 시트의 데이터를 실시간으로 연동하여 세련된 주간 계획표를 표시하는 웹앱을 구글 Apps Script로 제작하는 방법입니다.

🎯 완성된 기능들

  • 주간 캘린더 뷰: 일요일~토요일 7개 컬럼으로 일정 표시
  • 실시간 데이터 연동: 구글 시트 수정 시 즉시 반영
  • 마크다운 지원: 볼드, 이탤릭, 리스트, 링크 등 풍부한 텍스트 표현
  • 주간 네비게이션: 지난주/이번주/다음주 버튼으로 쉬운 탐색
  • 색상 시스템: 구글 시트 셀 배경색을 카테고리별 구분으로 활용
  • 반응형 디자인: 모바일부터 데스크톱까지 완벽 대응
  • 모던 UI: 글래스모피즘, 그라데이션, 부드러운 애니메이션

🗂️ 구글 시트 준비

1. 새 스프레드시트 생성

  • 구글 드라이브에서 새 스프레드시트 생성
  • 시트명을 **"schedule"**로 변경

2. 데이터 구조 설정

A열: date (날짜)

  • 형식: 2025-06-09, 2025-06-10 등
  • 또는 구글 시트 날짜 형식으로 입력

B열: title (제목)

  • 일정 제목 입력
  • 중요: 셀 배경색을 카테고리별로 다르게 설정
  • 예: 회의(파란색), 업무(초록색), 개인일정(노란색)

C열: content (내용)

  • 마크다운 문법으로 상세 내용 작성
  • 지원 문법: **볼드**, *이탤릭*, - 리스트, [링크](URL)

3. 예시 데이터

A1: date          | B1: title        | C1: content
A2: 2025-06-09    | B2: 팀 회의      | C2: ## 안건\n- 프로젝트 진행상황\n- **예산 검토**
A3: 2025-06-10    | B3: 개발 업무    | C3: ### 할 일\n1. 코드 리뷰\n2. 테스트 작성

🛠️ Apps Script 설정

1. Apps Script 프로젝트 생성

  1. script.google.com 접속
  2. "새 프로젝트" 클릭
  3. 프로젝트명을 "주간 계획표"로 변경

2. 파일 구조

  • Code.gs: 백엔드 로직
  • index.html: 프론트엔드 (HTML + CSS + JavaScript 통합)

💾 코드 구현

Code.gs (백엔드)

javascript
function doGet() {
  return HtmlService.createTemplateFromFile('index')
    .evaluate()
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}

function getScheduleData() {
  const SHEET_ID = '여기에_스프레드시트_ID_입력'; // 중요: 실제 시트 ID로 변경
  const SHEET_NAME = 'schedule';
  
  try {
    let sheet;
    try {
      sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
    } catch (e) {
      sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
    }
    
    if (!sheet) {
      console.error('시트를 찾을 수 없습니다.');
      return [];
    }
    
    const lastRow = sheet.getLastRow();
    if (lastRow < 2) return [];
    
    const dataRange = sheet.getRange(2, 1, lastRow - 1, 3);
    const values = dataRange.getValues();
    const backgrounds = dataRange.getBackgrounds();
    
    const scheduleData = [];
    
    for (let i = 0; i < values.length; i++) {
      const [date, title, content] = values[i];
      const titleBackgroundColor = backgrounds[i][1];
      
      if (!date && !title) continue;
      
      let formattedDate;
      
      // 다양한 날짜 형식 처리
      if (Object.prototype.toString.call(date) === '[object Date]') {
        formattedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
      } else if (typeof date === 'string') {
        const parsedDate = new Date(date);
        if (!isNaN(parsedDate.getTime())) {
          formattedDate = new Date(parsedDate.getFullYear(), parsedDate.getMonth(), parsedDate.getDate());
        } else {
          continue;
        }
      } else if (date && typeof date === 'object' && typeof date.getFullYear === 'function') {
        try {
          formattedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
        } catch (e) {
          continue;
        }
      } else {
        try {
          const attemptDate = new Date(date);
          if (!isNaN(attemptDate.getTime())) {
            formattedDate = new Date(attemptDate.getFullYear(), attemptDate.getMonth(), attemptDate.getDate());
          } else {
            continue;
          }
        } catch (e) {
          continue;
        }
      }
      
      if (!formattedDate || isNaN(formattedDate.getTime())) continue;
      
      const year = formattedDate.getFullYear();
      const month = String(formattedDate.getMonth() + 1).padStart(2, '0');
      const day = String(formattedDate.getDate()).padStart(2, '0');
      const dateString = `${year}-${month}-${day}`;
      
      scheduleData.push({
        date: dateString,
        title: title || '제목 없음',
        content: content || '',
        backgroundColor: titleBackgroundColor || '#ffffff'
      });
    }
    
    return scheduleData;
    
  } catch (error) {
    console.error('getScheduleData 오류:', error.toString());
    return { error: true, message: error.toString() };
  }
}

function getCurrentWeekDates(weekOffset = 0) {
  try {
    const today = new Date();
    const currentDay = today.getDay();
    
    const weekDates = [];
    const startOfWeek = new Date(today);
    startOfWeek.setDate(today.getDate() - currentDay + (weekOffset * 7));
    
    const dayNames = ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'];
    
    for (let i = 0; i < 7; i++) {
      const date = new Date(startOfWeek);
      date.setDate(startOfWeek.getDate() + i);
      
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      const dateString = `${year}-${month}-${day}`;
      
      weekDates.push({
        date: dateString,
        month: date.getMonth() + 1,
        day: date.getDate(),
        dayName: dayNames[i],
        dateString: `${date.getMonth() + 1}월 ${date.getDate()}일`,
        isToday: date.toDateString() === today.toDateString()
      });
    }
    
    return weekDates;
    
  } catch (error) {
    console.error('getCurrentWeekDates 오류:', error.toString());
    return { error: true, message: error.toString() };
  }
}

function getWeekNumber(weekOffset = 0) {
  try {
    const today = new Date();
    const targetDate = new Date(today);
    targetDate.setDate(today.getDate() + (weekOffset * 7));
    
    const firstDayOfMonth = new Date(targetDate.getFullYear(), targetDate.getMonth(), 1);
    const firstWeekDay = firstDayOfMonth.getDay();
    
    const weekNumber = Math.ceil((targetDate.getDate() + firstWeekDay) / 7);
    return weekNumber;
    
  } catch (error) {
    console.error('getWeekNumber 오류:', error.toString());
    return 1;
  }
}

index.html (프론트엔드)

매우 긴 코드이므로 주요 부분만 설명:

  1. HTML 구조:
    • 제목 + 주간 네비게이션 버튼
    • 7개 컬럼 그리드 레이아웃
  2. CSS 스타일링:
    • 그라데이션 배경
    • 글래스모피즘 효과
    • 반응형 그리드 시스템
  3. JavaScript 기능:
    • 마크다운 파싱 함수
    • 주간 네비게이션 로직
    • 구글 시트 데이터 로딩

🚀 배포 과정

1. 스프레드시트 ID 설정

  1. 구글 시트 URL에서 ID 복사
    • 예: https://docs.google.com/spreadsheets/d/1sl4YJPJd4hk4MdXHQ8tPls_ei0BJe5b5rr-zW0YYb0I/edit
    • ID: 1sl4YJPJd4hk4MdXHQ8tPls_ei0BJe5b5rr-zW0YYb0I
  2. Code.gs의 SHEET_ID 변수에 붙여넣기

2. 웹앱 배포

  1. Apps Script에서 "배포" > "새배포" 클릭
  2. 유형: "웹앱" 선택
  3. 설정:
    • 설명: "주간 계획표 v1.0"
    • 실행 계정: "나"
    • 액세스 권한: "모든 사용자"
  4. "배포" 클릭
  5. 웹앱 URL 복사

3. 권한 승인

  • 처음 실행 시 구글 시트 접근 권한 승인 필요
  • "고급" > "안전하지 않음(주간 계획표)" > "허용" 클릭

🎨 커스터마이징 옵션

1. 색상 테마 변경

CSS에서 다음 변수들 수정:

css
/* 배경 그라데이션 */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

/* 기본 카테고리 색상 */
--item-color: #667eea;

2. 마크다운 문법 확장

parseMarkdown() 함수에 새로운 정규식 추가:

javascript
// 하이라이트: ==텍스트==
html = html.replace(/==(.*?)==/g, '<mark>$1</mark>');

3. 레이아웃 조정

  • 컬럼 개수: .weekly-grid { grid-template-columns: repeat(7, 1fr); }
  • 카드 높이: .day-column { min-height: 500px; }

🔧 문제 해결

데이터가 표시되지 않는 경우

  1. 스프레드시트 ID 확인
  2. 시트명이 "schedule"인지 확인
  3. 날짜 형식 확인 (YYYY-MM-DD 권장)
  4. Apps Script 로그 확인 (console.log 출력)

날짜가 하루씩 틀리는 경우

  • 시간대 문제일 가능성 높음
  • 구글 시트에서 날짜를 텍스트가 아닌 날짜 형식으로 입력

마크다운이 제대로 표시되지 않는 경우

  • content 열에 특수문자가 있는지 확인
  • HTML 태그가 섞여있지 않은지 확인

📱 모바일 최적화

  • 자동으로 반응형 디자인 적용됨
  • 모바일에서는 2개 컬럼, 태블릿에서는 4개 컬럼으로 자동 조정
  • 터치 친화적인 버튼 크기

🎯 활용 예시

  1. 개인 일정 관리: 개인 스케줄 및 할 일 목록
  2. 팀 프로젝트 관리: 팀원별 업무 배정 및 진행상황
  3. 이벤트 일정표: 행사 프로그램 및 타임테이블
  4. 수업 시간표: 강의 일정 및 과제 마감일
  5. 가족 일정 관리: 가족 구성원별 일정 공유

💡 추가 개발 아이디어

  • 알림 기능: 이메일 또는 슬랙 연동
  • 필터링: 카테고리별 일정 필터
  • 검색 기능: 제목 또는 내용 검색
  • 통계 대시보드: 월별/주별 일정 분석
  • PDF 내보내기: 주간 계획표 인쇄용 변환

이 가이드를 따라하면 전문가 수준의 주간 계획표 웹앱을 만들 수 있습니다! 🎉

Content is user-generated and unverified.
    구글 시트 기반 주간 계획표 웹앱 제작 가이드 | Claude