TutorLoom
The learning platform for companies, colleges and training institutes. Free and open source, with an Enterprise edition.
Thời khóa biểu • Điểm danh • Tính lương • Học phí
Translation pending
EducationGitHub @nguyencongtuyen11OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Web quản lý Thời khóa biểu · Điểm danh · Tính lương giáo viên · Học phí học sinh cho Trung tâm Nghệ thuật OPENMUSIC. Học theo (clone) dự án "Thời khóa biểu Giáo viên" và mở rộng thêm điểm danh, tính số buổi, tính lương/học phí, thông báo nhắc lớp và thông báo kết thúc khóa.
Web tĩnh (HTML/CSS/JS thuần) + Supabase (Auth + Postgres + RLS). Tối ưu cho máy tính, máy tính bảng và điện thoại.
Mở thử ngay không cần cài gì:
index.html.python -m http.server 5500
Hoặc dùng VS Code + Live Server.Khi chưa điền Supabase, web tự chạy chế độ DEMO với dữ liệu mẫu. Tài khoản đăng nhập thử:
| Vai trò | Mật khẩu | |
|---|---|---|
| Admin | [email protected] | 123456 |
| Giáo viên | [email protected] | 123456 |
| Giáo viên | [email protected] | 123456 |
Dữ liệu thử nghiệm chỉ lưu trên trình duyệt của bạn; thêm
?demo=1để sử dụng riêng dữ liệu mẫu.
supabase-setup.sql → Run.config.js, điền 2 giá trị (lấy ở Project Settings → Data API / API Keys):
window.APP_CONFIG = {
SUPABASE_URL: "https://xxxx.supabase.co", // Project URL (không kèm /rest/v1/)
SUPABASE_ANON_KEY: "eyJhbGciOi...", // anon / public key
};
anon keyđược phép công khai trên trình duyệt — bảo mật thật sự nằm ở RLS trongsupabase-setup.sql.
Mọi người đăng ký đều là giáo viên. Để cấp quyền Admin, sau khi tài khoản đã đăng ký, chạy (đổi email):
update public.profiles set role = 'admin'
where id = (select id from auth.users where email = '[email protected]');
Tính năng "Quên mật khẩu" gửi email chứa liên kết đặt lại. Để liên kết mở đúng web:
https://openmusic.vercel.app hoặc http://localhost:5500)http://localhost:5500/**)| Bảng | Vai trò |
|---|---|
profiles | Tài khoản đăng nhập + vai trò (teacher / admin). |
teachers | Danh sách giáo viên (roster). Có pay_per_session (lương mỗi buổi), user_id để gắn tài khoản. |
students | Học sinh + khóa học: total_sessions (số buổi của khóa), tuition_per_session (học phí/buổi), status. |
schedules | Buổi học theo ngày, gắn vào teachers. Có teacher_present (điểm danh giáo viên). |
attendance | Điểm danh học sinh từng buổi: present = null (chưa) / true (có mặt) / false (vắng); note = ghi chú lý do nghỉ (hiện trong PDF của học sinh). |
Cách tính:
attendance.present = true của em đó. Còn lại = total_sessions − đã học.tuition_per_session.schedules.teacher_present = true. Lương = đã dạy × pay_per_session.| Hành động | Giáo viên | Admin |
|---|---|---|
| Xem lịch của mình | ✅ | ✅ (xem tất cả) |
| Xem lịch giáo viên khác | ❌ | ✅ |
| Thêm/xóa buổi học (xây TKB) | ❌ | ✅ |
| Điểm danh HS + GV trong lớp của mình | ✅ | ✅ |
| Quản lý học sinh / giáo viên | ❌ | ✅ |
| Tính lương / học phí | ❌ | ✅ |
| File | Vai trò |
|---|---|
index.html | Cấu trúc trang (auth + sidebar menu + các view + modal). |
styles.css | Giao diện (theme tối/sáng, glass, responsive web/tablet/mobile). |
backend.js | Lớp truy cập dữ liệu: tự chọn Supabase hoặc DEMO (localStorage). |
app.js | Logic: auth, router, điểm danh, tính lương/học phí, thông báo, phân quyền. |
config.js | Bạn điền URL + anon key Supabase. |
supabase-setup.sql | SQL cài đặt đầy đủ (bảng + trigger + RLS). |
Web tĩnh, deploy dễ: kéo-thả thư mục lên Netlify (https://app.netlify.com/drop), Vercel hoặc Cloudflare Pages. Nhớ cập nhật Site URL / Redirect URLs trong Supabase theo tên miền mới (mục Quên mật khẩu ở trên).
vendor/ kèm giấy phép, không phụ thuộc CDN khi mở lại PWA.Chạy kiểm tra: npm ci, npm run check, npm test. GitHub Actions chạy cùng bộ kiểm tra khi push/PR.
Database hiện có: áp dụng supabase/migrations/20261028042601_courses_and_profile_hardening.sql
(bản tương đương tại supabase-migration-6.sql) sau các migration trước đó. Script chạy trong transaction,
không xóa dữ liệu, bổ sung khóa học/vắng có phép, ngăn tài khoản tự nâng vai trò và giới hạn dữ liệu giáo viên được đọc.
Không chạy lại setup cũ sau migration này vì các policy cũ sẽ mở lại quyền đọc toàn bộ.
MIGRATION_MODE trong app.js hiện vẫn bật theo luồng nhập dữ liệu cũ; giáo viên được điểm danh bù quá khứ.
Tắt khi trung tâm kết thúc nhập dữ liệu. Đây là giới hạn giao diện, không thay thế RLS trên server.
Màu theme được áp dụng trong head trước khi tải ứng dụng, đồng bộ màu nền gốc, header và theme-color. Header có nền đồng màu để không lộ nội dung cuộn dưới đồng hồ; vùng tai thỏ, Dynamic Island, home indicator và cạnh màn hình ngang dùng env(safe-area-inset-*), không suy đoán theo tên máy. Giữ status bar style=default để hệ thống chọn chữ phù hợp với theme. Service worker đổi cache sang om-v3-iphone-chrome.
Đã đạt 28 kiểm thử, gồm 5 trường hợp màu theme khi khởi động. Chưa xác minh trên iPhone vật lý. Sau deploy, đóng/mở lại PWA; nếu màu thanh trạng thái vẫn giữ bản cài cũ, thêm lại biểu tượng từ Safari.
Bỏ yêu cầu font Google khỏi đường tải ban đầu (iPhone dùng font hệ thống). Các truy vấn dữ liệu độc lập chạy trong cùng một đợt thay vì chờ hai đợt nối tiếp; đếm có mặt tái sử dụng dữ liệu điểm danh, không truy vấn lại bảng attendance. Callback xác thực được chuyển sang lượt event loop kế tiếp trước khi tải hồ sơ, tránh giữ khóa Supabase Auth khi bắt đầu truy vấn. Hiện trạng thái đang tải thay vì vùng lịch trống. Bộ kiểm thử: 30/30 đạt. Chưa đo thời gian tải trên mạng/iPhone thực tế của người dùng.
Ảnh lỗi thiết bị cho thấy trang bị Safari dừng sau nhiều lần tải lại; chưa có crash log thiết bị để xác định nguyên nhân. Đã áp dụng đường hiển thị nhẹ cho màn hình <=900px và thiết bị cảm ứng: bỏ filter/backdrop-filter trên các lớp lồng nhau, bỏ mask toàn màn hình và orb blur, dùng SVG trực tiếp cho khóa sol. Vẫn giữ nền ngọc, góc bo, bề mặt trong và viền sáng. Không có location.reload/controllerchange reload tự động trong ứng dụng. Cache mới om-v5-mobile-stability. Cần xác nhận lại trên iPhone thật; kiểm thử DOM không phát hiện được sự cố GPU/WebKit.
Đã khôi phục blur/mask như trước theo yêu cầu người dùng; bản giảm tải không chứng minh được nguyên nhân crash. Mỗi truy vấn khởi động ghi console với tiền tố [OPENMUSIC load], tên nhóm dữ liệu và thời gian (không ghi tài khoản/token/nội dung dữ liệu). Quá 15 giây không phản hồi sẽ hiện lỗi có tên nhóm dữ liệu thay vì treo ở Đang tải lịch học mãi. Đây là công cụ khoanh vùng lỗi, không phải xác nhận nguyên nhân đã được sửa. Đã đạt 31 kiểm thử. Đo HTTP từ máy phát triển: HTML 433ms, app.js 59ms, CSS 55ms, Supabase JS 343ms; các số đo không đại diện cho truy vấn đăng nhập hay tốc độ iPhone.
Code hỗ trợ iOS Home Screen Web Push qua Supabase Edge Function web-push.
Trong app: nút chuông → Bật thông báo thiết bị → Gửi thử. Thiết bị phải cấp quyền thông báo.
Giáo viên nhận nhắc lớp của mình; admin nhận nhắc toàn bộ lớp. Thời điểm theo Asia/Ho_Chi_Minh;
lead_minutes lưu riêng từng thiết bị, từ 0 đến 1440 phút. Push có thể trễ do mạng, Focus hoặc iOS.
Code trên Vercel không tự tạo Edge Function và cron. Cần đăng nhập CLI tài khoản sở hữu dự án:
cd 'D:\chi Quyen'
npx --yes supabase login --agent no --output-format text
.\scripts\enable-web-push.ps1
Script kích hoạt đúng dự án ivilqvnljoucbsxzdvwf, chạy migration push, tạo khóa VAPID cục bộ,
đưa secrets lên Supabase, deploy function bằng Management API và tạo lịch kiểm tra mỗi phút qua pg_cron/pg_net.
Khóa riêng ở .env.push, SQL kích hoạt chứa cron secret ở supabase/push-activate.local.sql:
cả hai đều bị gitignore. Không gửi khóa/token vào chat hoặc commit. Giữ nguyên .env.push khi deploy lại,
vì đổi VAPID key sẽ yêu cầu các thiết bị đăng ký nhận lại.
Sau kích hoạt: mở app từ màn hình chính iPhone, bấm chuông → bật thông báo → Cho phép → Gửi thử. Thử một buổi học của đúng giáo viên, đặt nhắc trước 5 phút, đóng app và đợi thời điểm gửi để xác nhận đầu cuối. Không thể xác nhận gửi thành công chỉ bằng kết quả unit test.
Bảng subscriptions chỉ đọc bởi chủ thiết bị; ghi qua function kiểm tra JWT bằng Auth.getUser. Cron có secret riêng trong Vault. Endpoint gửi phải thuộc các nhà cung cấp push đã cho phép; không nhận URL tùy ý. Claim delivery dùng khóa duy nhất và lease để giảm gửi trùng, tự xóa endpoint hết hạn (404/410). Push không bảo đảm đúng từng giây hay exactly-once: lỗi kết nối sau khi nhà cung cấp nhận có thể gây gửi lại; tag cố định giúp hệ điều hành gộp thông báo. Mỗi lượt gửi tối đa 50 thông báo, tối đa 5 yêu cầu song song. Nhắc lịch bắt kịp chậm dưới 5 phút; gián đoạn lâu hơn có thể bỏ lỡ thông báo. Đăng xuất chủ động gỡ đăng ký trên thiết bị, nếu máy chủ không liên lạc được sẽ báo cần tắt quyền trên iPhone.
Kiểm thử: npm test (36 trường hợp) và Deno crypto test:
npx --yes deno check --config supabase/functions/web-push/deno.json supabase/functions/web-push/index.ts
npx --yes deno test --allow-env=ECE_KEYLOG,NODE_DEBUG --config supabase/functions/web-push/deno.json supabase/functions/web-push/crypto_test.ts