AppSheet × Googleカレンダーで実現する家庭DX
こんにちは、もるです。
「春休みの家庭学習を10秒で見える化するアプリ」、サクカレに興味を持っていただきありがとうございます!
このマニュアルでは、プログラミングの知識がなくても「コピペとクリックだけ」で自分専用のカレンダー登録アプリを完成させる手順を解説します。
準備はいいですか? それではいきましょう!
まずは、アプリの「設計図」を自分のアカウントに取り込みます。
アプリはコピーできましたが、中身がテンプレートの設定になっています。これをあなたのご家族用に直しましょう。
Admin、その他は User
ここが一番の山場ですが、「コピペしてボタンを押すだけ」 です!
/**
* サクカレ同期システム (AppSheet連携 / テンプレート配布対応版)
*/
/**
* 【重要】トリガーから呼び出すのはこの関数だけです
*/
function onAppSheetChange(e) {
syncToCalendar();
}
/**
* 同期処理のメインロジック
*/
function syncToCalendar() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Schedule');
const settingsSheet = ss.getSheetByName('Settings');
if (!sheet) return;
// 設定情報の読み込み(配布対応)
let ownerConfig = {};
if (settingsSheet) {
const data = settingsSheet.getDataRange().getValues();
// 1行目は見出し [表示名, カレンダーID, 記号]
for (let i = 1; i < data.length; i++) {
const name = data[i][0];
const calId = data[i][1];
const symbol = data[i][2];
if (name) {
ownerConfig[name] = { id: calId, symbol: symbol || '●' };
}
}
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const range = sheet.getRange(2, 1, lastRow - 1, 10); // A列〜J列まで
const values = range.getValues();
values.forEach((row, index) => {
const rowNum = index + 2;
// カラム定義
const categoryMid = row[2]; // C
const subject = row[3]; // D
const date = row[4]; // E
const startTimeStr = row[5];// F
const minutes = row[6]; // G
const details = row[7]; // H
const status = row[8]; // I
const email = row[9]; // J
// 同期対象チェック: Statusが「送信待ち」の行のみ処理
if (status !== '送信待ち') return;
// 所要時間の計算ロジック (G列: Minutes)
// 1. まず「数字のみ」の項目を足し算する (Base)
// 2. 「x」や「×」を含む数字を掛け算する (Multiplier)
const minutesValue = row[6] ? row[6].toString() : "0";
const parts = minutesValue.split(/[,、]/).map(p => p.trim());
let baseMinutes = 0;
let multiplier = 1;
parts.forEach(p => {
const num = parseInt(p.replace(/[^0-9]/g, ''));
if (isNaN(num)) return;
if (p.includes('x') || p.includes('×') || p.includes('X') || p.includes('*')) {
multiplier *= num;
} else {
baseMinutes += num;
}
});
const totalMinutes = baseMinutes * multiplier;
if (!email || !date) return;
try {
// Email (J列) をキーに設定情報を取得
const config = ownerConfig[email] || { id: email, symbol: '●' };
const calendarId = config.id || email || Session.getEffectiveUser().getEmail();
const symbol = config.symbol || '●';
const calendar = CalendarApp.getCalendarById(calendarId);
if (!calendar) throw new Error('カレンダーにアクセスできません: ' + calendarId);
// 日時結合
const startDateTime = combineDateAndTime(date, startTimeStr);
if (!startDateTime) throw new Error('日時の形式不正');
// 所要時間計算
// let totalMinutes = calculateTotalMinutes(minutes); // Old calculation
const endDateTime = new Date(startDateTime.getTime() + (totalMinutes * 60 * 1000));
// タイトル生成(記号+中分類 or 小分類)
const eventTitle = `${symbol}${categoryMid || subject}`;
const eventDescription = `【${subject}】\n${details || ""}`;
// 登録
const event = calendar.createEvent(eventTitle, startDateTime, endDateTime, {
description: eventDescription
});
// 色設定
setEventColorBySubject(event, subject);
// 同期完了マーク(I列=9列目)
sheet.getRange(rowNum, 9).setValue('送信完了');
console.log(`成功: ${email} (${calendarId}) に登録完了`);
} catch (error) {
console.error(`Row ${rowNum} エラー: ${error.message}`);
}
});
}
/**
* 日付と時間を結合してJS Dateオブジェクトを返す
*/
function combineDateAndTime(dateValue, timeValue) {
if (!dateValue) return null;
let date = new Date(dateValue);
if (isNaN(date.getTime())) return null;
let hours = 0;
let minutes = 0;
if (timeValue instanceof Date) {
const timeStr = Utilities.formatDate(timeValue, Session.getScriptTimeZone() || "JST", "HH:mm");
const parts = timeStr.split(':');
hours = parseInt(parts[0], 10);
minutes = parseInt(parts[1], 10);
} else if (timeValue) {
const timeStr = String(timeValue);
const match = timeStr.match(/(\d{1,2}):(\d{1,2})/);
if (match) {
hours = parseInt(match[1], 10);
minutes = parseInt(match[2], 10);
}
}
date.setHours(hours, minutes, 0, 0);
return date;
}
/**
* 小分類(Subject)に応じてカレンダーイベントの色を設定する
* (GoogleカレンダーのColor ID: 1〜11 を使用)
*/
function setEventColorBySubject(event, subject) {
if (!event || !subject) return;
// --- 色設定のカスタマイズ ---
const colorMap = {
'国語': '11', // トマト(赤)
'算数': '7', // ピーコック(ターコイズ)
'英語': '4', // フラミンゴ(薄赤)
'理科': '10', // バジル(緑)
'社会': '5', // バナナ(黄)
'4教科': '6', // ミカン(橙)
'ピアノ': '9', // ブルー(青)
'授業': '2', // セージ(薄緑)
'その他': '8' // グラファイト(グレー)
};
// -------------------------
const colorId = colorMap[subject];
if (colorId) event.setColor(colorId);
}
/**
* 【参考】Googleカレンダー Color ID と色の対応
* 1: ラベンダー (薄紫), 2: セージ (薄緑), 3: ブドウ (紫)
* 4: フラミンゴ (薄赤), 5: バナナ (黄), 6: ミカン (橙)
* 7: ピーコック (水色), 8: グラファイト (グレー), 9: コバルト (青)
* 10: バジル (緑), 11: トマト (赤)
*/
アプリからデータが届いた瞬間に、このコードが動くように設定します。
onAppSheetChange
Status 列が「送信待ち」のまま止まっていないか確認してください。
これであなたの家庭専用・爆速カレンダー登録環境は完成です!
最初は設定に少し手間がかかりますが、一度作ってしまえば日々の運用は驚くほど楽になります。
もし分からないことがあれば、NoteのコメントやX(旧Twitter)でお気軽にご相談くださいね。
それでは、素敵な家庭DXライフを!🎮✨