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:
- Cập nhật, lên lịch sự kiện từ form nhập liệu giao diện web.
- Load danh sách sự kiện có kèm hình ảnh lên giao diện web để quản lý.
- Có chức năng xóa một sự kiện khi không cần, hoặc đã thực hiện xong.
- Khi đến thời hạn ngày thực hiện công việc, tự động mail gửi nhác việc đến địa chỉ email deploy webapp.
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:
- Apps script Webapp | Lấy giá trị Input hiển thị lên – Web Get value input field display on web
- Google sheet Apps script | Todo List and Send mail – Tạo danh sách nhắc việc và gửi mail
- Web App Script | Bộ Icon CSS Rất dễ lập trình cho giao diện webapp
- Google sheet Webapp | Project Quản lý khách hàng – Cập nhật tiến độ sửa chữa
- Google sheet Apps script Webapp | Login form OTP xác minh qua email, Chuyển link cho từng User
- Web App Script CSS JS | Tạo hiệu ứng Click button Nổ tung các mảnh giấy và chuyển link
- Web App Script CSS | Tạo button Liên hệ gồm 3 option đẹp mắt cho trang web
- Web App Script Webapp | Hiệu ứng hoa rơi – Form nhập liệu Gửi nội dung đến email
- Web App Script | Thanh trạng thái Status bar – Giá trị thể hiện theo điểm và label.
- Google sheet Apps script | Data Entry Form – Tự động đọc số tiền thành chữ ở trường input
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>