📢 Chào mừng các bạn đến với Rcom Dăm Yi blog - Kho tài liệu và giải pháp công nghệ bổ ích! Hệ thống liên tục cập nhật dữ liệu học tập mới.
Bài 20 (Phần 5) - Tích hợp Chuyên môn & Xuất Báo cáo

Thời gian dùng thử đã hết!

Hệ thống iTeach đã vượt quá 15 ngày dùng thử. Vui lòng nhập License Key.

Bài 20. Quản lý sổ đầu bài điện tử (Phần 5)
Tích hợp Lịch báo giảng & Phân công chuyên môn

Cấu trúc Cơ sở dữ liệu (Database Schema): Để hệ thống chặt chẽ, ta cần 3 Sheet tương tác với nhau:

  • Sheet `PhanCong`: (Lớp, Môn, Giáo viên). Quy định ai dạy lớp nào.
  • Sheet `LichBaoGiang`: (Tuần, Thứ, Tiết, Lớp, Môn, Bài dạy, Giáo viên). Được giáo viên nhập lên vào cuối tuần trước dựa vào PhanCong.
  • Sheet `SoDauBai`: (Ngày, Lớp, Tiết, Môn, Bài dạy, Xếp loại, Giáo viên). Khớp với thực tế giảng dạy.

1. Mã nguồn Back-end (Code.gs)

Viết các hàm truy xuất dữ liệu riêng biệt để chuẩn bị cho việc xuất file Excel từ Client.

// 1. Hàm lấy Lịch Báo Giảng theo Giáo viên
function layLichBaoGiangTheoGV(tenGV) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("LichBaoGiang");
  let data = sheet.getDataRange().getValues();
  
  // Bỏ dòng tiêu đề và lọc theo tên Giáo viên (Giả sử cột G - index 6 là tên GV)
  let ketQua = data.slice(1).filter(row => row[6] === tenGV);
  return ketQua;
}

// 2. Hàm lấy Sổ Đầu Bài theo Lớp
function laySoDauBaiTheoLop(tenLop) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SoDauBai");
  let data = sheet.getDataRange().getValues();
  
  // Bỏ dòng tiêu đề và lọc theo Lớp (Giả sử cột C - index 2 là Lớp)
  let ketQua = data.slice(1).filter(row => row[2] === tenLop);
  return ketQua;
}

2. Giao diện Tích hợp & Xuất Báo Cáo

NgàyLớpTiếtMôn & Bài dạyXLGV
TuầnThứTiếtLớpMôn & Bài dạy

Bảng Phân Công Chuyên Môn (Master Data)

Dữ liệu này là gốc rễ để giới hạn Giáo viên chỉ được lên lịch báo giảng và nhập sổ đầu bài đúng lớp, đúng môn được phân công.

Tổ Chuyên MônGiáo viênMôn dạyLớp phụ trách
Bài 20. Sổ Đầu Bài (Phần 4) - Xuất Báo Cáo & In Ấn

Thời gian dùng thử đã hết!

Phần mềm đã vượt quá 15 ngày dùng thử miễn phí. Vui lòng nhập mã bản quyền (License Key).

Bài 20. Sổ Đầu Bài: Xuất Excel & In Báo Cáo PDF

Có 2 cách tiếp cận để xuất báo cáo:

  1. Xuất Excel (Back-end): Google Sheets hỗ trợ một đường link bí mật giúp tải file tự động. Chỉ cần thay /edit bằng /export?format=xlsx trong đường dẫn của file Sheets, hệ thống sẽ trả về một file Excel.
  2. In PDF (Front-end): Thay vì dùng Apps Script tạo PDF rất phức tạp, ta dùng tính năng window.print() của trình duyệt kết hợp với CSS @media print. Kỹ thuật này sẽ "phù phép" trang web, ẩn đi các nút bấm, menu và biến bảng dữ liệu thành một trang A4 hoàn hảo.

1. Mã nguồn Back-end (Code.gs)

Chúng ta sẽ viết một hàm để lấy tự động ID của file Google Sheets hiện tại và ghép thành đường link tải Excel.

// Hàm lấy Link tải file Excel trực tiếp từ Google Sheets
function layLinkXuatExcel() {
  let spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  let fileId = spreadsheet.getId();
  
  // Cấu trúc link tải trực tiếp của Google
  let exportUrl = "https://docs.google.com/spreadsheets/d/" + fileId + "/export?format=xlsx";
  
  return exportUrl;
}
💡 Mẹo nhỏ: Nếu bạn muốn xuất ra file PDF từ Google Sheets, chỉ cần đổi format=xlsx thành format=pdf. Tuy nhiên, xuất PDF theo cách này sẽ lấy toàn bộ trang tính gốc (chưa được format đẹp mắt). Do đó, ở phần Front-end dưới đây, ta sẽ dùng CSS để tạo bản in PDF xịn xò hơn.

2. Giao diện & Kỹ thuật Xử lý (Front-end)

Báo cáo Sổ đầu bài

STT Ngày Lớp Tiết Môn Nội dung bài dạy XL Giáo viên
Bài 19. Sổ Đầu Bài (Phần 3) - Xóa & Thống kê

Thời gian dùng thử đã hết!

Phần mềm đã vượt quá 15 ngày dùng thử miễn phí. Vui lòng nhập mã bản quyền.

Bài 19. Sổ Đầu Bài: Xóa Dữ Liệu & Báo Cáo Thống Kê

Trọng tâm bài học:
1. Thuật toán Xóa (Delete): Cần xác định chính xác số thứ tự dòng (Row Index) trên Google Sheets để xóa đúng đối tượng. Bắt buộc phải có hộp thoại xác nhận (Confirm) để tránh vô tình click nhầm.
2. Xử lý mảng (Array processing): Ứng dụng kỹ thuật đếm phần tử trong mảng (tương tự vòng lặp trong Python) bằng hàm filter() của JavaScript để tạo Bảng điều khiển (Dashboard) theo dõi thi đua trực quan.

1. Mã nguồn Back-end (Code.gs)

Hàm xóa dữ liệu trên Google Sheets rất đơn giản nhờ phương thức deleteRow(). Tuy nhiên, sự nguy hiểm là nếu xóa sai dòng, dữ liệu sẽ mất vĩnh viễn.

// Hàm Xóa một tiết học khỏi Sổ Đầu Bài
function xoaTietHoc(rowIndex) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SoDauBai");
  
  // Xóa dòng tại vị trí rowIndex
  sheet.deleteRow(rowIndex);
  
  return "Đã xóa dữ liệu thành công!";
}
💡 Lưu ý quan trọng: Khác với Cập nhật (Update) dùng setValue(), khi sử dụng deleteRow(), toàn bộ các dòng bên dưới sẽ bị đẩy lên trên. Do đó, thao tác xóa nên được thực hiện cẩn thận, và ngay sau khi xóa thành công trên Server, Frontend phải lập tức gọi lại hàm traCuuSoDauBai() (đã học ở Bài 18) để cập nhật lại danh sách, đảm bảo các rowIndex của các dòng còn lại không bị sai lệch.

2. Thuật toán Thống kê (Front-end JS)

Thay vì bắt Server (Google Script) phải đếm số lượng rồi mới trả về, ta tận dụng luôn mảng dữ liệu (Array) đã tải xuống trình duyệt để thống kê. Việc này giúp ứng dụng chạy nhanh hơn và giảm tải cho Server.

// Trích xuất code trong hàm vẽ bảng (JS)
function capNhatThongKe(data) {
  // data là mảng 2 chiều chứa danh sách các tiết học
  let tongSo = data.length;
  
  // Lọc và đếm số tiết đạt loại A (cột thứ 6 trong mảng, index = 6)
  let soLoaiA = data.filter(row => row[6] === "A").length;
  let soLoaiB = data.filter(row => row[6] === "B").length;
  let soLoaiC = data.filter(row => row[6] === "C").length;

  // Đổ dữ liệu ra Giao diện HTML
  document.getElementById("valTotal").innerText = tongSo;
  document.getElementById("valA").innerText = soLoaiA;
  // ...
}

3. Giao diện Tổng hợp Báo cáo & Quản lý

Bảng Điều Khiển Ban Giám Hiệu (Dashboard)

Tổng số tiết đã dạy
0
Tiết học loại A (Tốt)
0
Tiết học loại B (Khá)
0
Tiết học loại C (TB)
0
Ngày Lớp Tiết Môn & Bài dạy XL Quản trị
Bài 18. Sổ Đầu Bài Điện Tử (Phần 2) - Tra cứu & Chỉnh sửa

Thời gian dùng thử đã hết!

Phần mềm đã vượt quá 15 ngày dùng thử miễn phí. Vui lòng nhập mã bản quyền để tiếp tục sử dụng hệ thống.

Bài 18. Sổ Đầu Bài: Tra cứu, Lọc dữ liệu & Chỉnh sửa

Mục tiêu: Sau khi Sổ đầu bài đã có dữ liệu, bài toán tiếp theo của người quản trị mạng là cung cấp công cụ để:
1. Giám thị có thể chọn một Lớp cụ thể và một Tuần/Ngày cụ thể để kiểm tra.
2. Nếu Giáo viên nhập nhầm tên bài dạy (VD: Nhập nhầm bài "Hàm số" thành "Đạo hàm" môn Toán), họ có quyền click vào dòng đó để Cập nhật (Update) lại dữ liệu trên Sheets thay vì tạo dòng mới.

1. Mã nguồn Back-end (Code.gs)

Để tìm kiếm dữ liệu hiệu quả, chúng ta sử dụng hàm filter() của JavaScript để lọc mảng 2 chiều lấy ra các dòng khớp với tiêu chí "Lớp" và "Ngày".

// 1. Hàm Tra cứu (Filter) Sổ đầu bài theo Lớp và Ngày
function traCuuSoDauBai(lopCanTim, ngayCanTim) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SoDauBai");
  let data = sheet.getDataRange().getValues(); // Lấy toàn bộ bảng
  
  // Bỏ dòng tiêu đề bằng slice(1)
  let ketQua = [];
  
  for (let i = 1; i < data.length; i++) {
    let row = data[i];
    let ngayDay = row[1]; // Cột B: Ngày dạy (chuỗi dd/MM/yyyy)
    let tenLop = row[2];  // Cột C: Tên lớp
    
    // Nếu khớp tiêu chí lọc (hoặc người dùng để trống tiêu chí)
    if ((lopCanTim === "" || tenLop === lopCanTim) && 
        (ngayCanTim === "" || ngayDay === ngayCanTim)) {
        
        // Đẩy thêm rowIndex vào mảng để phục vụ tính năng Update về sau
        row.push(i + 1); 
        ketQua.push(row);
    }
  }
  return ketQua;
}

// 2. Hàm Cập nhật (Update) một tiết học bị sai
function capNhatTietHoc(rowIndex, baiDayMoi, xepLoaiMoi) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SoDauBai");
  
  // Ghi đè vào Cột F (cột 6 - Bài dạy) và Cột G (cột 7 - Xếp loại)
  sheet.getRange(rowIndex, 6).setValue(baiDayMoi);
  sheet.getRange(rowIndex, 7).setValue(xepLoaiMoi);
  
  return "Cập nhật thành công!";
}
💡 Giải thích Kỹ thuật:
- Tại hàm traCuuSoDauBai, lệnh row.push(i + 1) là một "thủ thuật" rất quan trọng. Mảng dữ liệu ban đầu chỉ có từ Cột A đến Cột H. Ta chủ động nhét thêm số thứ tự dòng (Index của Google Sheets, bắt đầu từ 1) vào cuối mảng. Mục đích là khi hiển thị lên giao diện HTML, ta biết chính xác dòng dữ liệu này nằm ở Hàng số mấy trên Google Sheets, từ đó mới truyền cái RowIndex này cho hàm capNhatTietHoc thực hiện ghi đè.

2. Giao diện Người dùng (Front-end SPA)

Thực hành Mô phỏng: Tra cứu & Quản lý Sổ Đầu Bài

(Trường THPT Phan Chu Trinh - Ayun Pa, Gia Lai. Dữ liệu chạy mô phỏng không cần tải lại trang).

Ngày Lớp Tiết Môn & Bài dạy XL Hành động
Bài 17. Dự án Thực tế: Hệ thống Sổ Đầu Bài Điện Tử

Thời gian dùng thử đã hết!

Phần mềm đã vượt quá 15 ngày dùng thử miễn phí. Vui lòng nhập mã bản quyền (License Key) để tiếp tục sử dụng Hệ thống iTeach.

Bài 17. Dự án: Hệ thống Sổ Đầu Bài Điện Tử

Mục tiêu dự án: Xây dựng một ứng dụng Web (Web App) thay thế hoàn toàn sổ đầu bài giấy. Giáo viên lên lớp dùng smartphone/laptop để nhập liệu. Hiệu trưởng và giám thị có thể xem tình hình các lớp theo thời gian thực (Real-time).

1. Thiết lập Cấu trúc Google Sheets (Database)

Tạo một file Google Sheets và đổi tên các trang tính (Sheet) như sau:

  • Sheet 1: DM_Lop (Danh mục Lớp). Cột A: Tên lớp (VD: 10A1, 10A2, 11B1...).
  • Sheet 2: SoDauBai. Gồm các cột: A (Thời gian ghi), B (Ngày dạy), C (Lớp), D (Tiết), E (Môn học), F (Tên bài dạy), G (Xếp loại), H (Giáo viên).

2. Viết mã Back-end (Code.gs)

Trong môi trường Apps Script, chúng ta cần 3 hàm chính: Lấy danh sách lớp (cho Dropdown), Lấy dữ liệu của ngày hôm nay (cho Bảng), và Ghi dữ liệu mới vào Sheet.

// 1. Hàm lấy danh sách lớp học để đổ vào thẻ <select>
function layDanhSachLop() {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DM_Lop");
  return sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();
}

// 2. Hàm lưu Tiết học mới vào Sổ đầu bài
function luuTietHoc(formObj) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SoDauBai");
  let timeStamp = new Date(); // Thời gian bấm nút lưu
  
  // Định dạng ngày (dd/mm/yyyy) để dễ lọc dữ liệu
  let ngayDay = Utilities.formatDate(timeStamp, Session.getScriptTimeZone(), "dd/MM/yyyy");
  
  let rowData = [
    timeStamp, ngayDay, formObj.lop, formObj.tiet, 
    formObj.mon, formObj.baiDay, formObj.xepLoai, formObj.giaoVien
  ];
  
  sheet.appendRow(rowData);
  return "Đã lưu thành công!";
}
💡 Giải thích chi tiết: Ở hàm luuTietHoc, ta tạo ra 2 cột thời gian. timeStamp (Cột A) lưu cả ngày giờ phút giây chính xác để làm bằng chứng giáo viên đã nhập sổ lúc mấy giờ. Còn ngayDay (Cột B) chỉ lưu ngày/tháng/năm dưới dạng chuỗi (String) để thuật toán tìm kiếm và lọc dữ liệu (hiển thị theo ngày) hoạt động dễ dàng và nhẹ nhàng hơn.

3. Giao diện và Xử lý Front-end (Index.html)

Thực hành Giao diện Mô phỏng: Sổ Đầu Bài Điện Tử

(Giao diện dưới đây minh họa cách Web App thực tế hoạt động. Dữ liệu sẽ tự động làm mới ngay sau khi bấm Lưu).

Nhập Sổ Đầu Bài

Tiết dạy hôm nay

Mã nguồn JavaScript xử lý tương tác UI

<script>
  // 1. Hàm chạy ngay khi vừa mở ứng dụng
  window.onload = function() {
    // Gọi Server tải danh sách lớp vào Select
    google.script.run.withSuccessHandler(doDuLieuLop).layDanhSachLop();
    
    // Gọi Server tải danh sách tiết học hôm nay vào Bảng
    taiDuLieuBang(); 
  };

  // 2. Xử lý Form Submit (Cơ chế SPA)
  function submitFormSDB(formObj) {
    // Hiệu ứng Loading (Code ẩn hiện DOM bỏ qua cho gọn)
    google.script.run
      .withSuccessHandler(function() {
         document.getElementById("formSoDauBai").reset();
         taiDuLieuBang(true); // TẢI LẠI BẢNG (Kèm cờ true để làm hiệu ứng Highlight)
      })
      .luuTietHoc(formObj);
  }
</script>
💡 Nhấn mạnh: Chìa khóa để ứng dụng không bị tải lại trang (chớp màn hình) là gọi lại hàm taiDuLieuBang() bên trong withSuccessHandler. Khi Apps Script ghi xong vào Sheet, nó phản hồi về Client, lúc này Client ra lệnh "Xóa form" và "Kéo data mới nhất về vẽ lại bảng". Toàn bộ thao tác diễn ra ngầm.
Bài 19. Tích hợp toàn diện Form, Table & Apps Script (SPA)

Thời gian dùng thử đã hết!

Phần mềm đã vượt quá 15 ngày dùng thử miễn phí. Vui lòng nhập mã bản quyền (License Key) để tiếp tục sử dụng iTeach.

Bài 19. Xây dựng Ứng dụng Quản lý toàn diện (SPA)

1. Kiến thức bài học chuyên sâu

1. Mô hình SPA (Single Page Application) là gì?

Trong các trang web kiểu cũ, mỗi lần bạn bấm "Lưu" hoặc "Chuyển trang", màn hình sẽ chớp trắng một cái để tải lại toàn bộ nội dung. Với SPA, mọi thao tác (Thêm, Xóa, Sửa, Đọc) đều diễn ra trên một trang duy nhất. Dữ liệu được gửi và nhận ngầm ở chế độ nền. Màn hình của người dùng không bao giờ bị tải lại, mang đến cảm giác mượt mà như một phần mềm cài đặt trên máy tính (VD: Facebook, Gmail đều là SPA).

2. Tư duy xử lý Bất đồng bộ (Asynchronous) của google.script.run

Khi JavaScript ở trình duyệt gọi Apps Script (qua google.script.run), nó giống như bạn gọi món ở nhà hàng. Bạn order xong, nhân viên đi vào bếp (Server), còn bạn vẫn có thể ngồi lướt điện thoại (Browser không bị đơ). Khi nấu xong, nhân viên mang món ra (đây chính là lúc withSuccessHandler được kích hoạt).

3. Vòng tuần hoàn dữ liệu (The CRUD Cycle):

  • Bước 1 (Khởi tạo): Vừa mở trang, chạy hàm taiDuLieuBang().
  • Bước 2 (Tương tác): Người dùng điền Form -> Bấm Lưu.
  • Bước 3 (Đóng vòng lặp - Quan trọng nhất): Bắt sự kiện withSuccessHandler báo lưu thành công. Ngay tại đây, ta gọi lại hàm taiDuLieuBang() của Bước 1 để vẽ lại bảng với dữ liệu mới nhất.

2. Bài tập mẫu & Giải phẫu Mã nguồn

Ví dụ: Hệ thống Đăng ký và Quản lý Đề tài KHKT

Đề bài: Xây dựng một giao diện gồm 2 nửa. Trái: Form nhập liệu đề tài KHKT. Phải: Bảng danh sách đề tài. Yêu cầu nhập xong bên trái, bên phải tự động hiện dữ liệu mới.

Phần 1: Code Back-end (Code.gs)

function layDanhSachTuSheet() {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("KHKT");
  // Bỏ qua dòng 1 (tiêu đề), lấy từ dòng 2
  return sheet.getRange(2, 1, sheet.getLastRow() - 1, 3).getValues(); 
}

function luuDeTaiMoi(formObj) {
  let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("KHKT");
  sheet.appendRow([formObj.tenDT, formObj.tenHS, formObj.linhVuc]);
  return "Thành công"; 
}
💡 Giải thích Code.gs: Back-end của chúng ta rất đơn giản, chỉ gồm 2 hàm độc lập. layDanhSachTuSheet làm nhiệm vụ Read (Đọc), trả về một mảng 2 chiều. Còn luuDeTaiMoi làm nhiệm vụ Create (Tạo mới), lấy Object từ Form và nối thêm (append) vào dòng cuối cùng của Sheet.

Phần 2: Code Front-end (JavaScript trong file HTML)

// 1. Tự động tải dữ liệu khi trang web vừa load xong
window.onload = taiDuLieuBang;

function taiDuLieuBang() {
  google.script.run.withSuccessHandler(veBang).layDanhSachTuSheet();
}

function veBang(data) {
  let tbody = document.getElementById("myTableBody");
  tbody.innerHTML = ""; // Xóa dữ liệu cũ trước khi đắp cái mới
  
  data.forEach((row, index) => {
    tbody.innerHTML += `<tr><td>${index+1}</td><td>${row[0]}</td><td>${row[1]}</td></tr>`;
  });
}

// 2. Xử lý khi Submit Form
function xuLyThemMoi(formObj) {
  google.script.run
    .withSuccessHandler(function() {
       document.getElementById("formKHKT").reset(); // Xóa trắng form để nhập tiếp
       taiDuLieuBang(); // GỌI LẠI HÀM VẼ BẢNG: Chìa khóa của SPA nằm ở đây!
    })
    .luuDeTaiMoi(formObj);
}
💡 Giải thích JS: Khúc mắc lớn nhất của người mới học là không biết làm sao để bảng cập nhật. Bí quyết nằm ở dòng taiDuLieuBang(); bên trong withSuccessHandler của hàm lưu. Có nghĩa là: "Khi Server báo đã cất dữ liệu thành công, Client tự động gọi lại lệnh kéo dữ liệu mới nhất về và vẽ lại từ đầu". Ta phải gọi tbody.innerHTML = "" để tránh tình trạng dữ liệu cũ bị vẽ lặp lại (nhân bản).

Trải nghiệm Ứng dụng thực tế: Quản lý Đề tài KHKT

Hãy thử điền form bên trái và bấm Gửi, bạn sẽ thấy bảng bên phải tự làm mới mà trình duyệt không hề bị load lại.

Đăng ký đề tài mới

Danh sách đang theo dõi

3. Bài tập tự luyện

Bài tập: Xây dựng Hệ thống Sổ Đầu Bài Điện Tử

Đề bài: Vận dụng kiến thức SPA để tạo sổ đầu bài cho giáo viên bộ môn.

  1. Khởi tạo: Khi mở web, gọi Google Sheets lấy dữ liệu của tất cả các tiết dạy trong ngày hôm nay và vẽ ra bảng.
  2. Cập nhật (Form): Giáo viên chọn Lớp, Môn, Tiết, Tên bài dạy và Xếp loại (A, B, C, D) rồi bấm Lưu.
  3. Thử thách nâng cao (UX): Sau khi bảng tự động refresh để hiện tiết học vừa nhập, hãy viết code CSS/JS để đổi màu nền (Highlight) của hàng mới thêm thành màu vàng sáng trong 3 giây, sau đó từ từ mờ đi thành màu trắng. (Gợi ý: Dùng `setTimeout` trong JS kết hợp đổi `style.backgroundColor`).