Site icon Tài liệu miễn phí cho Giáo viên, học sinh.

Google sheet Apps script | Todo List and Send mail – Tạo danh sách nhắc việc và gửi mail

Trong bài này, giaoan.link chia sẻ đến các bạn một project khá thú vị về “Google sheet Apps script | Todo List and Send mail – Tạo danh sách nhắc việc và gửi mail”.

Trong project webapp này có các chức năng như sau:

Dưới đây là video hướng dẫn và demo cùng code mã apps script

Bạn tìm thêm nhiều project googleapps script tại đây.

Các project hiển thị ngẫu nhiên:

Mã apps script “Code.gs”

/*https://youtube.com/@netmediacctv
  website: https://giaoan.link
*/
function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index');
}

function addTodoItem(title, description, dueDate, image) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('TodoList');
  const lastRow = sheet.getLastRow();
  const nextRow = lastRow + 1;

  // Tạo thư mục "images" nếu chưa tồn tại
  let folder = DriveApp.getFoldersByName("images").hasNext() ? DriveApp.getFoldersByName("images").next() : DriveApp.createFolder("images");

  // Upload file
  const blob = Utilities.newBlob(Utilities.base64Decode(image.bytes), image.mimeType, image.filename);     
  const file = folder.createFile(blob);      
  const imageUrl = file.getUrl();

  sheet.getRange(nextRow, 1).setValue(title);
  sheet.getRange(nextRow, 2).setValue(description);
  sheet.getRange(nextRow, 3).setValue(new Date(dueDate));
  sheet.getRange(nextRow, 4).setValue(imageUrl);

  // Kiểm tra nếu dueDate trùng với ngày hiện tại
  const today = new Date().toDateString();
  if (new Date(dueDate).toDateString() === today) {
    sendEmailReminder(title, description);
  } else {
    // Lên lịch gửi email vào 1 giờ sáng
    const triggerDate = new Date(dueDate);
    triggerDate.setHours(1, 0, 0, 0);
    ScriptApp.newTrigger('sendEmailReminderTrigger')
      .timeBased()
      .at(triggerDate)
      .create();
  }

  // Trả về danh sách mới sau khi thêm
  return getTodoItems();
}

function sendEmailReminderTrigger() {
  checkDueDates();
}

function getTodoItems() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('TodoList');
  if (!sheet) {
    return [];
  }

  const data = sheet.getDataRange().getValues();
  if (!data || data.length === 0) {
    return [];
  }

  return data.slice(1).map((row, index) => ({
    id: index + 1, // Thêm ID để nhận diện
    title: row[0] || '',
    description: row[1] || '',
    dueDate: row[2] ? new Date(row[2]).toISOString() : '',
    imageUrl: row[5] || ''
  }));
}

function deleteTodoItem(itemId) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('TodoList');
  const data = sheet.getDataRange().getValues();

  if (itemId > 0 && itemId < data.length) {
    sheet.deleteRow(itemId + 1); // Xóa dòng tương ứng trong sheet
  }
}

function checkDueDates() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('TodoList');
  if (!sheet) {
    return;
  }

  const data = sheet.getDataRange().getValues();
  if (!data || data.length === 0) {
    return;
  }

  const today = new Date().toDateString();
  data.slice(1).forEach(row => {
    const dueDate = new Date(row[2]).toDateString();
    if (dueDate === today) {
      sendEmailReminder(row[0], row[1]);
    }
  });
}

function sendEmailReminder(title, description) {
  const emailAddress = Session.getActiveUser().getEmail();
  const subject = `Lời nhắc nhở: ${title}`;
  const body = `Đừng quên sự kiện: ${title}\n\nMô tả sự kiện: ${description}`;

  MailApp.sendEmail(emailAddress, subject, body);
}

function createDailyTrigger() {
  ScriptApp.newTrigger('sendEmailReminderTrigger')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create();
}

Mã apps script “Index.html”

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
  <style>
    body{
      font-family: Poppins, sans-serif;
      border: 1px solid #fab000;
      border-radius: 8px;
      box-shadow: 2px 2px 3px grey;
      width: 95%;      
      padding: 10px;
      margin-left: auto;
      margin-right: auto;
    }

    .div-block{
      display: block;
      gap: 20px;
    }
    .div-form{
      border: 1px solid grey;
      border-radius: 5px;      
      width: 600px;
      padding: 5px;
      margin-left: auto;
      margin-right: auto;
      margin-bottom: 20px;
    }

    input{
      font-size: 0.9em;
      width: 97%;
      margin-top: 3px;
      margin-bottom: 10px;
      border: 1px solid #fab000;
      border-radius: 5px;
      box-shadow: 1px 1px 1px grey;
      padding: 8px;
    }

    .title-textarea{
      font-size: 1.2em;
      width: 97%;
      margin-top: 3px;
      margin-bottom: 10px;
      border: 1px solid #fab000;
      border-radius: 5px;
      box-shadow: 1px 1px 1px grey;
      padding: 8px;
    }

    .title{
      font-size: 1.5em;
      font-weight: bold;
      text-align: center;
      color: blue;
    }

    .title1{
      font-size: 1em;
      font-weight: bold;
      font-style: italic;
      text-align: center;
      color:red;
      margin-bottom: 20px;
    }

    label{
      font-size: 1em;
      font-weight: bold;
    }
  
    table {
      width: 100%;
      border-collapse: collapse;
    }
    th, td {
      border: 1px solid #ccc;     
      padding: 8px;      
    }
    th {
      text-align: center;
      background-color: #ffcd48;
    }
    button.delete-button {
      background-color: #ff6666;
      color: white;
      border: none;
      padding: 5px 10px;
      cursor: pointer;
    }
    .bnt{      
      font-size: 1em;
      font-weight: bold;
      margin-top: 15px;
      padding: 10px 15px;
      border: 1px solid grey;
      background-color: #fab000;
      border-radius: 8px;
      outline: none;
      cursor: pointer;
    }
    .bnt:hover{
      box-shadow: 2px 2px 3px grey;
    }
    @media (max-width: 800px){
      .div-form{      
        width: 95%;      
      }
      input{
      width: 95%;
    }

    .title-textarea{
      width: 95%;
    }
  </style>
  <script>
    function addTodoItem() {
      const title = document.getElementById('title').value;
      const description = document.getElementById('description').value;
      const dueDate = document.getElementById('dueDate').value;
      const image = document.getElementById('image').files[0];
      const reader = new FileReader();

      reader.onload = function(e) {
        const imageData = {
          bytes: e.target.result.split(',')[1],
          mimeType: image.type,
          filename: image.name
        };
        google.script.run.withSuccessHandler(displayTodoItems).addTodoItem(title, description, dueDate, imageData);
        document.getElementById('todoForm').reset();
      };

      reader.readAsDataURL(image);
    }

    function loadTodoItems() {
      google.script.run.withSuccessHandler(displayTodoItems).getTodoItems();
    }

    function deleteTodoItem(itemId) {
      google.script.run.withSuccessHandler(loadTodoItems).deleteTodoItem(itemId);
    }

    function displayTodoItems(items) {
      console.log('Todo items:', items); // Log ra console để kiểm tra

      const list = document.getElementById('todoList');
      list.innerHTML = '';

      if (!items || items.length === 0) {
        list.innerHTML = '<p>Không tìm thấy danh sách việc làm.</p>';
        return;
      }

      const table = document.createElement('table');
      const headerRow = document.createElement('tr');
      ['Tiêu đề', 'Mô tả công việc', 'Ngày thực hiện', 'Hình ảnh', 'Hành động'].forEach(text => {
        const th = document.createElement('th');
        th.textContent = text;
        headerRow.appendChild(th);
      });
      table.appendChild(headerRow);

      items.forEach(item => {
        const row = document.createElement('tr');

        const titleCell = document.createElement('td');
        titleCell.textContent = item.title;
        row.appendChild(titleCell);

        const descriptionCell = document.createElement('td');
        descriptionCell.textContent = item.description;
        row.appendChild(descriptionCell);

        const dueDateCell = document.createElement('td');
        dueDateCell.style = 'text-align: center';
        dueDateCell.textContent = item.dueDate ? new Date(item.dueDate).toLocaleDateString() : 'No due date';
        row.appendChild(dueDateCell);

        const imageCell = document.createElement('td');
        imageCell.style = 'text-align: center';
        if (item.imageUrl) {
          const image = document.createElement('img');
          image.src = item.imageUrl;
          
          image.style.maxWidth = '50px';
          image.style.maxHeight = '50px';
          imageCell.appendChild(image);
          // Tạo liên kết <a> bao quanh hình ảnh và mở hình ảnh trong một popup
          const link = document.createElement('a');
          link.href = item.imageUrl;
          link.target = '_blank';
          link.onclick = function(event) {
            event.preventDefault();
            window.open(item.imageUrl, 'popup', 'width=600,height=600');
          };  
          link.appendChild(image);
          imageCell.appendChild(link);  // Thêm liên kết chứa hình ảnh vào ô

        }
        row.appendChild(imageCell);

        const actionCell = document.createElement('td');
        const deleteButton = document.createElement('button');
        actionCell.style = 'text-align: center';
        deleteButton.className = 'delete-button';
        deleteButton.textContent = 'Delete';
        deleteButton.onclick = function() {
          deleteTodoItem(item.id);
        };
        actionCell.appendChild(deleteButton);
        row.appendChild(actionCell);

        table.appendChild(row);
      });

      list.appendChild(table);
    }

    document.addEventListener('DOMContentLoaded', function() {
      loadTodoItems();
    });
  </script>
</head>
<body>
  <div class="title">DANH SÁCH VIỆC LÀM</div>
  <div class="title1">Có gửi email nhắc việc</div>
    <div >
      <form class="div-form" id="todoForm" onsubmit="event.preventDefault(); addTodoItem();">    
        <div>
          <label for="title"><i class="bi bi-arrow-bar-right"></i> Tiêu đề:</label>
          <input type="text" id="title" required>
        </div>
        <div>
          <label for="description"><i class="bi bi-book"></i> Mô tả công việc</label>
          <textarea class="title-textarea" id="description" required></textarea>
        </div>
        <div >
          <label for="dueDate"><i class="bi bi-calendar-date"></i> Ngày công việc:</label>
          <input type="date" id="dueDate" required>
        </div>
        <div>
          <label for="image"><i class="bi bi-card-image"></i> Tải ảnh lên:</label>
          <input type="file" id="image" required>
        </div>
        <button class="bnt" type="submit">Thêm công việc</button>
      </form> 
    </div>
  <div id="todoList"></div>
</body>
</html>
Exit mobile version