アプリ開発の実践

第IV部:教育現場で役立つ具体的なアプリケーション開発例

12. 小学校向けアプリ開発例

小学校の教育現場で役立つGASアプリケーションの開発例を紹介します。小学生の発達段階に合わせた工夫点や実装例について解説します。

低学年向けUIの工夫

小学校低学年の児童が使用することを想定したUIデザインの工夫点について説明します。

低学年向けUIのポイント
  • シンプルでわかりやすいデザイン:必要最小限の要素で構成し、認知負荷を減らす
  • 大きなボタンと文字:操作しやすい大きさのUIコンポーネント
  • カラフルで視認性の高い配色:興味を引きやすく、区別しやすい色使い
  • イラストやアイコンの活用:文字だけでなく視覚的な手がかりを提供
  • 音声フィードバック:操作の結果を音で知らせる工夫
低学年向けCSSの例
/* 低学年向けCSSスタイルの例 */ /* 全体的なスタイル */ body { font-family: 'Comic Sans MS', 'Kosugi Maru', sans-serif; font-size: 18px; line-height: 1.5; background-color: #f0f9ff; color: #333333; margin: 0; padding: 20px; } /* 見出し */ h1 { font-size: 28px; color: #0066cc; text-align: center; margin-bottom: 20px; padding: 10px; background-color: #e6f2ff; border-radius: 15px; border: 3px solid #0066cc; } /* 大きなボタン */ .big-button { display: block; width: 80%; max-width: 300px; margin: 15px auto; padding: 20px; font-size: 22px; text-align: center; background-color: #4CAF50; color: white; border: none; border-radius: 20px; cursor: pointer; box-shadow: 0 5px #388E3C; transition: all 0.2s; } .big-button:active { box-shadow: 0 2px #388E3C; transform: translateY(3px); } /* アイコン付きボタン */ .icon-button { display: flex; align-items: center; justify-content: center; } .icon-button img { width: 30px; height: 30px; margin-right: 10px; } /* フィードバックメッセージ */ .feedback { font-size: 24px; text-align: center; padding: 15px; margin: 15px 0; border-radius: 15px; } .success { background-color: #E8F5E9; color: #2E7D32; border: 2px solid #2E7D32; } .error { background-color: #FFEBEE; color: #C62828; border: 2px solid #C62828; }

具体的な実装例

読書記録アプリの例

目的:児童の読書活動を促進し、読んだ本の記録を管理するアプリ

主な機能

  • 本のタイトル、著者、ページ数、読了日の記録
  • 絵文字やスタンプでの感想入力
  • 読書の目標設定と達成状況の可視化
  • 教師による閲覧と励ましコメントの入力
読書記録アプリのCode.gs(一部)
// データベース操作の基本関数 function getBookListForStudent(studentId) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('読書記録'); var data = sheet.getDataRange().getValues(); var bookList = []; // ヘッダー行をスキップして2行目から処理 for (var i = 1; i < data.length; i++) { // 0列目が生徒ID、1列目が本のタイトルなど if (data[i][0] == studentId) { bookList.push({ title: data[i][1], author: data[i][2], pages: data[i][3], date: new Date(data[i][4]).toLocaleDateString(), feedback: data[i][5] }); } } return bookList; } // 本の記録を追加する関数 function addBookRecord(studentId, bookData) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('読書記録'); // 新しい行を追加 sheet.appendRow([ studentId, bookData.title, bookData.author, bookData.pages, new Date(), bookData.feedback ]); // 読書メダルの更新 updateReadingMedals(studentId); return {success: true, message: "記録しました!"}; } // 読書メダルの更新(目標達成の管理) function updateReadingMedals(studentId) { var bookList = getBookListForStudent(studentId); var totalBooks = bookList.length; var medals = []; // 5冊読むごとにメダルを獲得 if (totalBooks >= 5) medals.push("銅のメダル"); if (totalBooks >= 10) medals.push("銀のメダル"); if (totalBooks >= 20) medals.push("金のメダル"); if (totalBooks >= 30) medals.push("プラチナメダル"); // メダル情報の更新 var ss = SpreadsheetApp.openById('スプレッドシートID'); var medalSheet = ss.getSheetByName('メダル'); var medalData = medalSheet.getDataRange().getValues(); var found = false; for (var i = 1; i < medalData.length; i++) { if (medalData[i][0] == studentId) { // 既存の行を更新 medalSheet.getRange(i+1, 2).setValue(totalBooks); medalSheet.getRange(i+1, 3).setValue(medals.join(", ")); found = true; break; } } if (!found) { // 新しい行を追加 medalSheet.appendRow([studentId, totalBooks, medals.join(", ")]); } return medals; }
読書記録アプリのHTML(一部)
<!-- 読書記録入力フォーム --> <div class="book-form"> <h2>どんな本を読んだかな?</h2> <div class="form-group"> <label for="bookTitle">本のタイトル</label> <input type="text" id="bookTitle" class="big-input" placeholder="本のタイトルを入れてね"> </div> <div class="form-group"> <label for="bookAuthor">作者の名前</label> <input type="text" id="bookAuthor" class="big-input" placeholder="だれが書いた本?"> </div> <div class="form-group"> <label for="bookPages">ページ数</label> <input type="number" id="bookPages" class="big-input" placeholder="何ページあった?"> </div> <div class="form-group"> <label>どうだった?</label> <div class="emoji-feedback"> <button type="button" class="emoji-btn" data-value="とても楽しかった">😄</button> <button type="button" class="emoji-btn" data-value="楽しかった">🙂</button> <button type="button" class="emoji-btn" data-value="ふつう">😐</button> <button type="button" class="emoji-btn" data-value="むずかしかった">🤔</button> <button type="button" class="emoji-btn" data-value="こわかった">😨</button> </div> <input type="hidden" id="bookFeedback" value=""> </div> <button id="saveBook" class="big-button"> <img src="data:image/svg+xml;base64,..." alt=""> 保存する </button> </div> <div id="medalDisplay" class="medal-container"> <h2>きみのメダル</h2> <div id="medalIcons" class="medal-icons"></div> <p>今までに <span id="totalBooks">0</span>さつ よみました!</p> </div> <script> // 絵文字ボタンの処理 document.querySelectorAll('.emoji-btn').forEach(function(btn) { btn.addEventListener('click', function() { // 選択された絵文字のフィードバックを保存 document.getElementById('bookFeedback').value = this.dataset.value; // 選択状態のスタイル更新 document.querySelectorAll('.emoji-btn').forEach(function(b) { b.classList.remove('selected'); }); this.classList.add('selected'); }); }); // 保存ボタンの処理 document.getElementById('saveBook').addEventListener('click', function() { var title = document.getElementById('bookTitle').value; var author = document.getElementById('bookAuthor').value; var pages = document.getElementById('bookPages').value; var feedback = document.getElementById('bookFeedback').value; // 入力検証 if (!title) { showMessage('本のタイトルを入れてね', 'error'); return; } // データ送信 google.script.run .withSuccessHandler(function(result) { if (result.success) { // 成功時の処理 showMessage(result.message, 'success'); playSound('success.mp3'); clearForm(); loadMedals(); } else { // エラー時の処理 showMessage('エラーが起きたよ。もう一度試してね', 'error'); } }) .withFailureHandler(function(error) { showMessage('エラーが起きたよ。もう一度試してね', 'error'); }) .addBookRecord(studentId, { title: title, author: author, pages: pages || 0, feedback: feedback }); }); // メッセージ表示関数 function showMessage(message, type) { var messageDiv = document.getElementById('message'); messageDiv.textContent = message; messageDiv.className = 'feedback ' + type; messageDiv.style.display = 'block'; // 3秒後に消える setTimeout(function() { messageDiv.style.display = 'none'; }, 3000); } // フォームをクリアする関数 function clearForm() { document.getElementById('bookTitle').value = ''; document.getElementById('bookAuthor').value = ''; document.getElementById('bookPages').value = ''; document.getElementById('bookFeedback').value = ''; document.querySelectorAll('.emoji-btn').forEach(function(btn) { btn.classList.remove('selected'); }); } // サウンド再生関数 function playSound(soundFile) { var audio = new Audio(soundFile); audio.play(); } // メダル情報を読み込む関数 function loadMedals() { google.script.run .withSuccessHandler(function(medals) { var medalIcons = document.getElementById('medalIcons'); medalIcons.innerHTML = ''; if (medals.length === 0) { medalIcons.innerHTML = '<p>まだメダルがありません。本をよんでメダルをゲットしよう!</p>'; } else { medals.forEach(function(medal) { var icon = ''; if (medal === "銅のメダル") icon = '🥉'; else if (medal === "銀のメダル") icon = '🥈'; else if (medal === "金のメダル") icon = '🥇'; else if (medal === "プラチナメダル") icon = '💎'; var medalSpan = document.createElement('span'); medalSpan.className = 'medal'; medalSpan.textContent = icon + ' ' + medal; medalIcons.appendChild(medalSpan); }); } }) .withFailureHandler(function(error) { console.error('メダル情報の取得に失敗しました:', error); }) .getMedals(studentId); } // 初期ロード時 window.onload = function() { loadMedals(); }; </script>

デモアプリの紹介

その他の小学校向けアプリアイデア
  • 漢字練習アプリ:学年別の漢字リストから問題を自動生成し、書き取り練習の成果を記録
  • 計算ドリルジェネレーター:レベルに応じた計算問題を自動生成し、解答・採点をサポート
  • 音読記録アプリ:音読カードのデジタル版。音読の記録と保護者の確認機能付き
  • 理科観察日記:植物の成長や天気の変化などを写真と文章で記録し、時系列で表示
  • お楽しみタイマー:授業や活動の時間管理を楽しくするカウントダウンタイマー
開発のポイント:小学生向けアプリでは、視覚的な分かりやすさとインタラクティブな要素が重要です。また、文字入力が難しい低学年の児童でも操作できるよう、選択式のUIやボタン操作を中心に設計するとよいでしょう。

13. 中高校向けアプリ開発例

中学校・高校の教育現場で役立つGASアプリケーションの開発例を紹介します。より複雑なデータ管理や分析機能を持つアプリケーションを解説します。

成績管理システムの実装

テスト結果や課題の評価など、成績データを総合的に管理するシステムの実装例を紹介します。

成績管理システムの主な機能
  • 科目ごとのテスト結果入力と記録
  • 個人別・クラス別の成績集計と分析
  • 偏差値や順位の自動計算
  • 成績推移のグラフ表示
  • 所見の入力と管理
  • 成績通知書の自動生成
成績管理システムのCode.gs(抜粋)
// 各種計算と統計処理を行う関数群 // クラス平均点の計算 function calculateClassAverage(className, subjectName, examType) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('成績データ'); var data = sheet.getDataRange().getValues(); var scores = []; for (var i = 1; i < data.length; i++) { // 列のインデックスは例示。実際のデータ構造に合わせて調整 if (data[i][1] === className && data[i][2] === subjectName && data[i][3] === examType) { scores.push(data[i][4]); // スコア列 } } if (scores.length === 0) return 0; // 平均点の計算 var sum = scores.reduce(function(a, b) { return a + b; }, 0); return sum / scores.length; } // 標準偏差の計算 function calculateStandardDeviation(scores) { var avg = scores.reduce(function(a, b) { return a + b; }, 0) / scores.length; var squareDiffs = scores.map(function(value) { var diff = value - avg; return diff * diff; }); var avgSquareDiff = squareDiffs.reduce(function(a, b) { return a + b; }, 0) / squareDiffs.length; return Math.sqrt(avgSquareDiff); } // 偏差値の計算 function calculateDeviationScore(score, avg, stdDev) { if (stdDev === 0) return 50; // 標準偏差が0の場合 return ((score - avg) / stdDev * 10) + 50; } // 生徒の成績データを取得 function getStudentGrades(studentId) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('成績データ'); var data = sheet.getDataRange().getValues(); var grades = []; for (var i = 1; i < data.length; i++) { if (data[i][0] === studentId) { // 0列目が生徒ID // 科目ごとの成績データをオブジェクトとして作成 var gradeInfo = { subject: data[i][2], examType: data[i][3], score: data[i][4], maxScore: data[i][5], date: new Date(data[i][6]), classAvg: calculateClassAverage(data[i][1], data[i][2], data[i][3]) }; // 同じ試験タイプの全スコアを取得して偏差値を計算 var allScores = []; for (var j = 1; j < data.length; j++) { if (data[j][1] === data[i][1] && data[j][2] === data[i][2] && data[j][3] === data[i][3]) { allScores.push(data[j][4]); } } var stdDev = calculateStandardDeviation(allScores); gradeInfo.deviationScore = calculateDeviationScore(data[i][4], gradeInfo.classAvg, stdDev); grades.push(gradeInfo); } } return grades; } // 成績通知書の自動生成 function generateGradeReport(studentId, term) { var studentInfo = getStudentInfo(studentId); var grades = getStudentGrades(studentId).filter(function(grade) { return grade.examType.includes(term); // 学期でフィルタリング }); // 生徒情報と成績データから通知書を作成 var doc = DocumentApp.create(studentInfo.name + '_' + term + '_成績通知書'); var body = doc.getBody(); // 通知書のヘッダー body.appendParagraph(term + ' 成績通知書') .setHeading(DocumentApp.ParagraphHeading.HEADING1) .setAlignment(DocumentApp.HorizontalAlignment.CENTER); // 生徒情報 body.appendParagraph('氏名: ' + studentInfo.name) .setHeading(DocumentApp.ParagraphHeading.HEADING2); body.appendParagraph('クラス: ' + studentInfo.className); body.appendParagraph('出席番号: ' + studentInfo.studentNumber); // 成績テーブル var table = body.appendTable([['科目', '点数', '平均点', '偏差値']]); grades.forEach(function(grade) { table.appendTableRow([ grade.subject, grade.score.toString(), grade.classAvg.toFixed(1), grade.deviationScore.toFixed(1) ]); }); // 総合評価と所見 body.appendParagraph('総合評価') .setHeading(DocumentApp.ParagraphHeading.HEADING2); // 平均点と偏差値の総合計算 var totalScore = grades.reduce(function(sum, grade) { return sum + grade.score; }, 0); var avgScore = totalScore / grades.length; body.appendParagraph('平均点: ' + avgScore.toFixed(1)); // 所見欄 body.appendParagraph('所見') .setHeading(DocumentApp.ParagraphHeading.HEADING2); body.appendParagraph('ここに所見を入力してください。'); // 実際には教師が後で編集 // ドキュメントのURLを返す return doc.getUrl(); }

出席管理システムの実装

日々の出席状況を記録・分析するシステムの実装例を紹介します。

出席管理システムの主な機能
  • 日付と時間ごとの出席記録入力(出席、欠席、遅刻、早退)
  • 欠席理由の記録(病欠、事故欠、忌引き等)
  • 出席率の自動計算と可視化
  • 長期欠席者の自動検出とアラート
  • 出席簿の印刷用データ出力
  • 保護者への連絡機能
出席管理システムのフロントエンド(一部)
<!-- 出席入力フォーム --> <div class="attendance-form"> <h2>出席入力</h2> <div class="form-controls"> <div class="form-group"> <label for="classSelect">クラス選択</label> <select id="classSelect" class="form-control"> <option value="">クラスを選択</option> <option value="1-A">1年A組</option> <option value="1-B">1年B組</option> <option value="2-A">2年A組</option> <option value="2-B">2年B組</option> <option value="3-A">3年A組</option> <option value="3-B">3年B組</option> </select> </div> <div class="form-group"> <label for="datePicker">日付</label> <input type="date" id="datePicker" class="form-control" value="

デモアプリの紹介

その他の中高校向けアプリアイデア
  • 進路指導支援システム:志望校情報の管理、模試結果の記録、志望校判定、面談記録など
  • 部活動管理アプリ:部員名簿、練習スケジュール、大会記録、備品管理など
  • 定期テスト作成支援ツール:問題バンク、過去問の分析、難易度調整、解答用紙自動生成など
  • 補習・補講管理システム:対象生徒の抽出、日程調整、教室割り当て、出席管理など
  • 生徒指導記録システム:面談記録、問題行動の記録、指導経過の共有など
開発のポイント:中高校向けアプリでは、データの分析機能や可視化機能が重要です。また、教員間での情報共有や連携をサポートする機能も効果的です。発達段階に応じて、生徒自身が使用するインターフェースと教員向けの管理インターフェースを分けて設計するとよいでしょう。

14. 教員向け校務効率化アプリ例

教員の校務作業を効率化するためのGASアプリケーション開発例を紹介します。書類作成や情報共有など、教員の業務負担を軽減するアプリを解説します。

会議資料管理システム

会議に関する資料を一元管理し、情報共有を効率化するシステムの実装例です。

会議資料管理システムの主な機能
  • 会議の種類・日時・場所・参加者の登録
  • 議題と資料のアップロード・管理
  • 議事録の作成・編集・共有
  • タスクの割り当てと進捗管理
  • 過去の会議記録の検索・参照
  • 会議の自動リマインダー通知
会議管理アプリのCode.gs(一部)
// 会議情報の取得 function getMeetings() { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('会議'); var data = sheet.getDataRange().getValues(); var meetings = []; // ヘッダー行をスキップ for (var i = 1; i < data.length; i++) { meetings.push({ id: data[i][0], type: data[i][1], title: data[i][2], date: new Date(data[i][3]), location: data[i][4], participants: data[i][5].split(','), status: data[i][6] }); } // 日付でソート(近い順) meetings.sort(function(a, b) { return a.date - b.date; }); return meetings; } // 新しい会議の作成 function createMeeting(meetingData) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('会議'); // 新しいIDを生成 var id = Utilities.getUuid(); // 日時の処理 var meetingDate = new Date(meetingData.date); // 参加者リストを文字列に変換 var participants = meetingData.participants.join(','); // データ追加 sheet.appendRow([ id, meetingData.type, meetingData.title, meetingDate, meetingData.location, participants, 'Scheduled' // 初期ステータス ]); // Google Calendarに会議を追加 if (meetingData.addToCalendar) { addMeetingToCalendar( meetingData.title, meetingDate, new Date(meetingDate.getTime() + (meetingData.duration || 60) * 60000), meetingData.location, meetingData.description ); } // 参加者にメール通知 if (meetingData.sendNotification) { sendMeetingNotification( meetingData.title, meetingDate, meetingData.location, meetingData.description, meetingData.participants ); } return { success: true, message: '会議が正常に作成されました', meetingId: id }; } // 会議に関連する資料を取得 function getMeetingDocuments(meetingId) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('会議資料'); var data = sheet.getDataRange().getValues(); var documents = []; // ヘッダー行をスキップ for (var i = 1; i < data.length; i++) { if (data[i][1] === meetingId) { // 1列目が会議ID documents.push({ id: data[i][0], meetingId: data[i][1], title: data[i][2], type: data[i][3], fileId: data[i][4], uploadDate: new Date(data[i][5]), uploadedBy: data[i][6] }); } } return documents; } // 資料をGoogle Driveにアップロードし、会議に関連付ける function uploadDocumentForMeeting(meetingId, documentData, fileBlob) { // Google Driveに保存するフォルダのID var folderId = 'GoogleドライブフォルダID'; var folder = DriveApp.getFolderById(folderId); // ファイルをDriveにアップロード var file = folder.createFile(fileBlob); // 資料情報をスプレッドシートに記録 var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('会議資料'); // 新しいIDを生成 var id = Utilities.getUuid(); // アクティブユーザーのメールアドレスを取得 var uploadedBy = Session.getActiveUser().getEmail(); // データ追加 sheet.appendRow([ id, meetingId, documentData.title, documentData.type, file.getId(), new Date(), uploadedBy ]); return { success: true, message: '資料がアップロードされました', documentId: id, fileUrl: file.getUrl() }; } // 議事録を保存 function saveMinutes(meetingId, minutesData) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('議事録'); // 既存の議事録を確認 var data = sheet.getDataRange().getValues(); var existingRow = -1; for (var i = 1; i < data.length; i++) { if (data[i][0] === meetingId) { existingRow = i + 1; // スプレッドシートの行番号(1始まり) break; } } if (existingRow > 0) { // 既存の議事録を更新 sheet.getRange(existingRow, 2).setValue(minutesData.content); sheet.getRange(existingRow, 3).setValue(new Date()); sheet.getRange(existingRow, 4).setValue(Session.getActiveUser().getEmail()); } else { // 新しい議事録を作成 sheet.appendRow([ meetingId, minutesData.content, new Date(), Session.getActiveUser().getEmail() ]); } // 会議のステータスを更新 updateMeetingStatus(meetingId, 'Completed'); return { success: true, message: '議事録が保存されました' }; } // カレンダーに会議を追加 function addMeetingToCalendar(title, startTime, endTime, location, description) { var calendar = CalendarApp.getDefaultCalendar(); var event = calendar.createEvent( title, startTime, endTime, { location: location, description: description } ); return event.getId(); } // 会議の通知メールを送信 function sendMeetingNotification(title, date, location, description, participants) { var formattedDate = Utilities.formatDate(date, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm'); var subject = '【会議通知】' + title; var body = '以下の会議にご参加ください:\n\n' + '件名:' + title + '\n' + '日時:' + formattedDate + '\n' + '場所:' + location + '\n\n' + '概要:\n' + description; participants.forEach(function(email) { MailApp.sendEmail(email.trim(), subject, body); }); }

校内連絡システム

教職員間の連絡事項を効率的に共有するシステムの実装例です。

校内連絡システムの主な機能
  • 連絡事項の投稿・編集・削除
  • 宛先グループの指定(全体、学年別、教科別など)
  • 重要度や期限の設定
  • 既読管理と未読者への自動リマインド
  • カテゴリ別の表示・検索
  • ファイルの添付機能
ポイント:校内連絡システムは、従来の職員室の掲示板や朝の打ち合わせを補完・代替する重要なツールです。使いやすさとシンプルさを重視しつつ、確実に情報が伝わる仕組みを構築することが重要です。また、スマートフォンからのアクセスにも配慮したレスポンシブデザインを採用すると、より活用が進みます。

デモアプリの紹介

その他の教員向け校務効率化アプリアイデア
  • 授業計画作成支援ツール:年間指導計画や単元計画の作成、教材の整理、進度管理など
  • 校内研修管理システム:研修スケジュールの管理、資料の共有、アンケートの実施・集計など
  • 校内文書テンプレート集:各種文書のテンプレートを提供し、必要事項を入力するだけで文書が作成できる
  • 備品・教材管理システム:備品の在庫管理、貸出状況の記録、発注リストの自動作成など
  • 行事計画管理システム:年間行事予定の管理、準備タスクの割り当て、進捗管理など
校務効率化アプリ開発のポイント
  • 既存の紙の書類や手続きをそのままデジタル化するのではなく、プロセス自体を見直して効率化を図る
  • データの再利用性を高め、一度入力した情報を様々な用途で活用できるようにする
  • 特定の教員しか操作できないようなシステムは避け、誰でも使いやすいインターフェースを心がける
  • 業務の優先度や緊急度が視覚的に分かるダッシュボードを提供する
  • 教員の個人情報や学校の機密情報を扱う場合は、特に権限管理やセキュリティに注意する

15. 保護者連携アプリ例

学校と保護者の連携を強化するためのGASアプリケーション開発例を紹介します。情報共有や面談予約など、保護者とのコミュニケーションを円滑にするアプリを解説します。

お知らせ配信システム

学校から保護者へのお知らせを効率的に配信するシステムの実装例です。

お知らせ配信システムの主な機能
  • お知らせ内容の作成・編集・配信
  • 配信対象の選択(全校、学年別、クラス別など)
  • 添付ファイルの管理
  • 配信スケジュールの設定
  • 既読状況の確認と統計
  • 保護者からのフィードバック収集
ポイント:お知らせ配信システムでは、メールだけでなく、ウェブアプリとしてアクセス可能な形で情報を管理することで、過去のお知らせの参照やファイルのダウンロードなどが容易になります。また、既読管理機能を実装することで、重要なお知らせが確実に保護者に届いているかを確認できます。

面談予約システム

保護者面談の予約を効率的に管理するシステムの実装例です。

面談予約システムの主な機能
  • 教員の面談可能時間帯の設定
  • 保護者による希望日時の選択・予約
  • 予約状況の確認・変更・キャンセル
  • 自動リマインダー通知
  • オンライン面談リンクの自動生成
  • 面談記録の保存と参照
面談予約システムのCode.gs(一部)
// 面談可能時間枠の設定 function setupAvailableSlots(teacherId, dates, timeRanges) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('面談枠'); // 既存の予約枠をクリア var data = sheet.getDataRange().getValues(); var rowsToDelete = []; for (var i = data.length - 1; i >= 1; i--) { if (data[i][1] === teacherId && data[i][3] === 'available') { rowsToDelete.push(i + 1); } } // 行の削除は大きいインデックスから for (var j = 0; j < rowsToDelete.length; j++) { sheet.deleteRow(rowsToDelete[j]); } // 新しい予約枠を追加 var newSlots = []; dates.forEach(function(date) { timeRanges.forEach(function(timeRange) { var startTime = new Date(date); var [hours, minutes] = timeRange.start.split(':').map(Number); startTime.setHours(hours, minutes, 0, 0); var endTime = new Date(date); var [endHours, endMinutes] = timeRange.end.split(':').map(Number); endTime.setHours(endHours, endMinutes, 0, 0); // 面談時間枠(例: 15分単位) var slotDuration = 15 * 60 * 1000; // 15分をミリ秒に変換 while (startTime < endTime) { var slotEnd = new Date(startTime.getTime() + slotDuration); if (slotEnd > endTime) { slotEnd = endTime; } // 予約枠IDを生成 var slotId = Utilities.getUuid(); // 予約枠を追加 newSlots.push([ slotId, teacherId, startTime, 'available', '', slotEnd, timeRange.location || '面談室1' ]); startTime = slotEnd; } }); }); // バッチで追加 if (newSlots.length > 0) { sheet.getRange(sheet.getLastRow() + 1, 1, newSlots.length, newSlots[0].length).setValues(newSlots); } return { success: true, message: newSlots.length + '件の面談枠を設定しました' }; } // 教員の面談枠を取得 function getTeacherSlots(teacherId, startDate, endDate) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('面談枠'); var data = sheet.getDataRange().getValues(); var slots = []; // 日付の範囲を設定 var start = new Date(startDate); var end = new Date(endDate); // ヘッダー行をスキップ for (var i = 1; i < data.length; i++) { var slotDate = new Date(data[i][2]); // 指定した日付範囲と教員IDでフィルタリング if (data[i][1] === teacherId && slotDate >= start && slotDate <= end) { slots.push({ id: data[i][0], teacherId: data[i][1], startTime: slotDate, endTime: new Date(data[i][5]), status: data[i][3], studentId: data[i][4], location: data[i][6] }); } } // 開始時間でソート slots.sort(function(a, b) { return a.startTime - b.startTime; }); return slots; } // 保護者が面談を予約 function bookAppointment(slotId, studentId, parentEmail, comments) { var ss = SpreadsheetApp.openById('スプレッドシートID'); var sheet = ss.getSheetByName('面談枠'); var data = sheet.getDataRange().getValues(); var rowIndex = -1; var slot = null; // スロットを検索 for (var i = 1; i < data.length; i++) { if (data[i][0] === slotId) { rowIndex = i + 1; slot = { id: data[i][0], teacherId: data[i][1], startTime: new Date(data[i][2]), status: data[i][3], studentId: data[i][4], endTime: new Date(data[i][5]), location: data[i][6] }; break; } } // スロットが見つからない場合 if (rowIndex === -1) { return { success: false, message: '指定された面談枠が見つかりません' }; } // すでに予約済みの場合 if (slot.status !== 'available') { return { success: false, message: 'この面談枠はすでに予約されています' }; } // 予約を更新 sheet.getRange(rowIndex, 4).setValue('booked'); sheet.getRange(rowIndex, 5).setValue(studentId); // 予約詳細をログに記録 var logSheet = ss.getSheetByName('予約ログ'); logSheet.appendRow([ slotId, studentId, parentEmail, new Date(), comments ]); // 教員と保護者に確認メールを送信 sendAppointmentConfirmation(slot, studentId, parentEmail, comments); // Google Calendarに予約を追加 addAppointmentToCalendar(slot, studentId, parentEmail, comments); return { success: true, message: '面談の予約が完了しました', appointmentDetail: { date: Utilities.formatDate(slot.startTime, Session.getScriptTimeZone(), 'yyyy/MM/dd'), startTime: Utilities.formatDate(slot.startTime, Session.getScriptTimeZone(), 'HH:mm'), endTime: Utilities.formatDate(slot.endTime, Session.getScriptTimeZone(), 'HH:mm'), location: slot.location } }; } // 確認メールの送信 function sendAppointmentConfirmation(slot, studentId, parentEmail, comments) { // 教員情報を取得 var teacherInfo = getTeacherInfo(slot.teacherId); // 生徒情報を取得 var studentInfo = getStudentInfo(studentId); // メール本文を作成 var subject = '【面談予約確認】' + studentInfo.name + 'さんの保護者面談'; var body = studentInfo.name + 'さんの保護者面談が予約されました。\n\n' + '日時: ' + Utilities.formatDate(slot.startTime, Session.getScriptTimeZone(), 'yyyy/MM/dd HH:mm') + ' - ' + Utilities.formatDate(slot.endTime, Session.getScriptTimeZone(), 'HH:mm') + '\n' + '場所: ' + slot.location + '\n' + '担当教員: ' + teacherInfo.name + '\n\n'; if (comments) { body += '保護者からのコメント:\n' + comments + '\n\n'; } body += '変更・キャンセルをご希望の場合は、面談予約システムからお手続きください。\n' + 'URL: ' + ScriptApp.getService().getUrl() + '?page=appointments'; // 保護者にメール送信 MailApp.sendEmail(parentEmail, subject, body); // 教員にもメール送信 MailApp.sendEmail(teacherInfo.email, subject, body); } // Google Calendarに予約を追加 function addAppointmentToCalendar(slot, studentId, parentEmail, comments) { var calendar = CalendarApp.getDefaultCalendar(); // 生徒情報を取得 var studentInfo = getStudentInfo(studentId); // イベントタイトル var title = studentInfo.name + 'さん 保護者面談'; // イベント説明 var description = '保護者: ' + parentEmail + '\n'; if (comments) { description += '保護者からのコメント:\n' + comments; } // カレンダーイベント作成 var event = calendar.createEvent( title, slot.startTime, slot.endTime, { location: slot.location, description: description, guests: parentEmail, sendInvites: true } ); return event.getId(); }

デモアプリの紹介

その他の保護者連携アプリアイデア
  • 帰宅通知システム:放課後の児童の帰宅状況を保護者に通知する
  • 欠席・遅刻連絡システム:保護者からの欠席・遅刻連絡をオンラインで受け付け
  • 学校行事カレンダー共有アプリ:学校行事予定を保護者のカレンダーと同期
  • 家庭学習支援アプリ:家庭での学習サポートに役立つ教材や情報を提供
  • 給食メニュー・アレルギー情報共有アプリ:給食メニューとアレルギー情報を事前に共有
保護者連携アプリ開発のポイント
  • 保護者の多様なICTスキルレベルに対応できるよう、シンプルで直感的なインターフェースを設計する
  • スマートフォンからのアクセスを前提としたレスポンシブデザインを採用する
  • プッシュ通知やメール通知など、重要情報の確実な伝達方法を工夫する
  • 保護者の利用登録や認証方法を安全かつ簡便に設計する
  • 個人情報や機密情報の取り扱いに特に注意し、適切なアクセス制限を設ける
重要:保護者連携アプリでは、すべての保護者が利用できることを前提とせず、デジタル機器やインターネットにアクセスできない家庭への配慮も必要です。従来の紙ベースの連絡方法と併用するなど、情報格差が生じないような工夫が重要です。