Cho phép giáo viên điểm danh học sinh theo từng buổi ngay trong lớp, và cho chủ trường / quản lý chi nhánh theo dõi chuyên cần toàn chi nhánh — kèm cảnh báo học sinh cần chú ý.
Trung tâm / trường (B2B) đang điểm danh trên giấy hoặc Excel rời rạc — chủ trường không có bức tranh chuyên cần theo lớp / chi nhánh, và không phát hiện sớm học sinh sắp bỏ học (chuyên cần thấp là tín hiệu churn mạnh nhất).
Giáo viên: điểm danh 1 lớp trong < 30 giây, ngay nơi họ đang thao tác (màn chi tiết lớp).
Chủ trường / quản lý chi nhánh: xem tỉ lệ chuyên cần theo lớp, danh sách học sinh cần chú ý, xuất Excel gửi phụ huynh / báo cáo nội bộ.
FLYER: thêm lý do dùng hằng ngày (daily habit) cho FLYER Teacher; nền tảng cho tính năng thông báo phụ huynh qua ZNS/Zalo (Sprint 107+).
Trạng thái hiện tại
Prototype frontend hoàn chỉnh đã chạy trên branch dev (dữ liệu tạm lưu localStorage, chưa có backend). Toàn bộ luồng UI, business rules và edge cases trong tài liệu này đã được implement + audit — team backend chỉ cần thay lớp lưu trữ bằng API (mục 7).
2Người dùng & user stories
Vai trò
User story
Ưu tiên
Giáo viên
Là GV, tôi muốn đánh dấu Có mặt / Đi muộn / Nghỉ phép / Vắng cho từng học sinh trong buổi học hôm nay, kèm ghi chú lý do, để lưu hồ sơ chuyên cần.
P0
Giáo viên
Là GV, tôi muốn nhấn “Tất cả có mặt” rồi chỉ sửa vài học sinh vắng, để điểm danh thật nhanh.
P0
Giáo viên
Là GV, tôi muốn mở lại buổi cũ để sửa sai, và xem lịch sử chuyên cần của lớp.
P1
Chủ trường
Là chủ trường, tôi muốn xem % chuyên cần từng lớp trong chi nhánh theo khoảng thời gian, để đánh giá vận hành.
P0
Chủ trường
Là chủ trường, tôi muốn thấy danh sách học sinh chuyên cần < 80% (cần chú ý), để CS gọi phụ huynh sớm.
P0
Chủ trường
Là chủ trường, tôi muốn xuất Excel điểm danh theo buổi / theo lớp.
P1
3Phạm vi & phase
Phase
Nội dung
Trạng thái
Phase 1
Tab “Điểm danh” trong chi tiết lớp (4 trạng thái + ghi chú + lịch sử + xuất Excel) và trang “Báo cáo điểm danh” cấp chi nhánh trong menu Báo Cáo. Lưu localStorage.
Done — prototype
Phase 2
Backend API + DB (mục 7): dữ liệu dùng chung giữa các giáo viên / thiết bị; phân quyền theo role; audit log.
Cần build
Phase 3
Tự động thông báo phụ huynh khi con vắng (ZNS / Zalo OA — tận dụng hạ tầng Sprint 107), báo cáo chuyên cần trong weekly/monthly report.
Đề xuất
4Luồng sản phẩm
4a · Giáo viên điểm danh (tab trong chi tiết lớp)
Vào lớp → Quản Lý Lớp → chọn lớp → tab “Điểm danh” (tab cuối, value 8).
Chọn ngày (mặc định hôm nay; không chọn được ngày tương lai).
Đánh dấu: mỗi học sinh có 4 pill trạng thái; nhấn chọn — nhấn lại lần nữa để bỏ chọn. Nút “Tất cả có mặt” đánh cả lớp một phát. Ô ghi chú tự do cho từng học sinh (vd lý do vắng).
Theo dõi: 4 chip đếm (Có mặt / Đi muộn / Nghỉ phép / Vắng) cập nhật realtime + dòng “Đã điểm danh x/y học sinh”.
Lưu điểm danh → toast “Đã lưu điểm danh”. Lưu một ngày mà không đánh dấu gì = xoá buổi đó (cách xoá buổi lưu nhầm).
Lịch sử (toggle góc phải): bảng tổng hợp mỗi học sinh (số buổi từng trạng thái + % chuyên cần, tô màu theo ngưỡng) + danh sách các buổi đã điểm danh — nhấn 1 buổi để mở lại và sửa.
Prototype thực tế — tab Điểm danh trong chi tiết lớp (dữ liệu lớp thật).
4b · Chủ trường xem báo cáo (menu Báo Cáo → Báo cáo điểm danh)
Mở sidebar Báo Cáo → “Báo cáo điểm danh” (route /report/attendance). Phạm vi = chi nhánh đang chọn (đúng mô hình mỗi chi nhánh là 1 school).
Tổng quan: 4 stat — số lớp đã điểm danh · tổng buổi · % chuyên cần chung (tô màu ngưỡng) · tổng lượt vắng.
Chuyên cần theo lớp: bảng từng lớp (số buổi, đếm 4 trạng thái, % chuyên cần) — sort lớp thấp nhất lên đầu.
Học sinh cần chú ý: mọi học sinh có chuyên cần < 80% (tên, lớp, số buổi vắng, %).
Lọc khoảng ngày (mặc định 30 ngày; xoá = toàn thời gian) + Xuất Excel.
Prototype thực tế — Báo cáo điểm danh cấp chi nhánh (dữ liệu demo).
Demo mode
Khi chi nhánh chưa có dữ liệu, trang hiển thị nút “✨ Tạo dữ liệu demo” (sinh 8 lớp × 12 buổi, tên học sinh giả) phục vụ demo bán hàng; nút “Xoá dữ liệu demo” gỡ riêng dữ liệu demo của chi nhánh đó, không đụng dữ liệu thật. Giữ tính năng này ở Phase 2 sau một feature flag nội bộ.
5Spec UI — cho Designer
Trạng thái điểm danh (4 mức)
Trạng thái
Pill (chưa chọn)
Pill (đang chọn)
Màu chữ/nền chưa chọn
Nền khi chọn
Có mặt · Present
Có mặt
Có mặt
#16A34A / #DCFCE7
#16A34A, chữ trắng
Đi muộn · Late
Đi muộn
Đi muộn
#D97706 / #FEF3C7
#D97706, chữ trắng
Nghỉ phép · Excused
Nghỉ phép
Nghỉ phép
#2563EB / #DBEAFE
#2563EB, chữ trắng
Vắng · Absent
Vắng
Vắng
#DC2626 / #FEE2E2
#DC2626, chữ trắng
Thành phần & hành vi
Hàng học sinh: STT · avatar 36px tròn (fallback avatar mặc định) · tên (đậm, truncate) · 4 pill · input ghi chú 180px. Hàng chẵn nền #FBFBFC.
Pill: bo tròn full, cao 30px, đổi trạng thái tức thì (optimistic), nhấn lại = bỏ chọn.
Chống mất dữ liệu: đổi ngày / mở buổi khác khi đang có thay đổi chưa lưu → modal xác nhận “Chưa lưu điểm danh — Bỏ thay đổi / Ở lại” (nút Bỏ = danger).
Empty states: lớp 0 học sinh → Empty “Lớp chưa có học sinh”. Báo cáo phân biệt 2 case: chưa có dữ liệu vs có dữ liệu nhưng ngoài khoảng ngày đã lọc (copy khác nhau).
Hệ thống: antd (Segmented, DatePicker, Modal, Empty) + Tailwind; font Public Sans; song ngữ VI/EN (namespace attendance.*, đã có đủ key).
Cần designer: soát lại spacing/màu theo design system, thiết kế trạng thái mobile (hàng học sinh wrap), icon cho tab + menu báo cáo, và cân nhắc biểu đồ xu hướng chuyên cần theo tuần (chưa làm).
6Business rules
Rule
Định nghĩa
% chuyên cần
(Có mặt + Đi muộn) / tổng lượt đã đánh dấu × 100 — làm tròn số nguyên. Nghỉ phép không được tính là chuyên cần nhưng vẫn nằm trong mẫu số.
Buổi (session)
Một ngày có ≥ 1 học sinh được đánh trạng thái. Ngày lưu rỗng không tính là buổi và bị xoá khỏi lịch sử.
Học sinh cần chú ý
Chuyên cần < 80% trong khoảng ngày đang lọc; sort tăng dần theo %.
Lưu (save)
Merge theo từng học sinh vào dữ liệu đã có của ngày đó — không ghi đè cả ngày, để không mất record của học sinh đã rời lớp / ngoài trang danh sách.
Phạm vi báo cáo
Chi nhánh (school) đang chọn. Đổi chi nhánh ở school switcher để xem chi nhánh khác. (Gộp nhiều chi nhánh: Phase 3.)
Ngày
Key dạng YYYY-MM-DD theo giờ địa phương (không dùng UTC/ISO — đã dính bug lệch ngày).
7Data & API — cho Developer
Hiện tại (Phase 1 — localStorage)
flyer_attendance_<classId> = {
"2026-08-12": { // date key, giờ địa phương
"<learnerProfileId>": { "status": "present|late|excused|absent",
"note": "…", "name": "Nguyễn Văn A" }
}, …
}
flyer_attendance_index = { // để báo cáo gom theo chi nhánh
"<classId>": { "name": "KET 1", "schoolId": "…", "schoolName": "…", "demo": true? }
}
Toàn bộ đọc/ghi đi qua src/utils/attendance.ts (single source of truth) — Phase 2 chỉ cần thay ruột các hàm này bằng API call, UI giữ nguyên.
Đề xuất Phase 2 — DB
attendance_sessions id · class_id · date (unique class_id+date) · created_by · timestamps
attendance_records id · session_id · learner_profile_id
· status ENUM(present, late, excused, absent)
· note TEXT NULL · marked_by · timestamps
Đề xuất API (REST v3, cùng pattern openapi hiện có)
Endpoint
Mô tả
GET /v3/teacher/classes/{classId}/attendance?date=
Bản ghi 1 buổi (hoặc range from/to cho lịch sử).
PUT /v3/teacher/classes/{classId}/attendance/{date}
Upsert cả buổi (merge từng học sinh, xoá record khi client gửi status null; buổi rỗng → xoá session). Idempotent.
GET /v3/teacher/schools/{schoolId}/attendance/summary?from=&to=
Tổng hợp cho báo cáo: per-class stats + per-student stats (server tính theo rules mục 6).
Quyền: GV chỉ điểm danh lớp mình dạy; report cấp chi nhánh cho role ADMIN/Owner (Phase 1 đang chưa gate — cần thêm khi có backend).
Frontend đã có sẵn: component ClassAttendance (tab, value 8 trong [classId]/dashboard.tsx), view AttendanceReportView, route + sidebar + routeConfig + i18n VI/EN. Query danh sách học sinh dùng partnerViewerRollupStudents (limit 200 — Phase 2 cần phân trang nếu lớp lớn hơn).
8Edge cases đã xử lý (từ audit 26-agent)
Case
Hành vi chuẩn (đã implement)
Sửa buổi cũ khi có học sinh đã rời lớp
Merge-save: record của học sinh ngoài danh sách hiện tại được giữ nguyên, không bị xoá ngầm.
Đổi ngày khi chưa lưu
Modal xác nhận trước khi bỏ thay đổi (áp dụng cả khi nhấn chip buổi cũ từ Lịch sử).
Đánh dấu nhầm
Nhấn lại pill để bỏ chọn; lưu ngày trống = xoá buổi (không còn “buổi ma 0%”).
Buổi không có trạng thái nào
Không được tính là session trong mọi thống kê.
Lọc ngày không có dữ liệu
Thông báo “ngoài khoảng ngày” (khác với “chưa có dữ liệu”).
Múi giờ
Date key theo giờ địa phương — demo seed cũng vậy, không lệch ngày/cuối tuần.
Xoá demo
Chỉ xoá dữ liệu demo của chi nhánh hiện tại.
Còn mở (chưa làm, cần quyết ở Phase 2): gate theo role & feature flag; nhấn dòng lớp trong báo cáo để nhảy vào lớp; phân trang lớp > 200 học sinh; đồng bộ đa thiết bị (đến khi có backend).
9Acceptance criteria (Phase 2)
GV mở tab Điểm danh của lớp có N học sinh → thấy đủ N hàng, mặc định ngày hôm nay, chưa đánh dấu gì.
Nhấn “Tất cả có mặt” → N pill Có mặt bật; sửa 1 học sinh thành Vắng + ghi chú; Lưu → reload trang → dữ liệu đúng như đã lưu (từ server, thấy được trên thiết bị khác).
Nhấn lại pill đang chọn → bỏ đánh dấu; lưu buổi không còn đánh dấu nào → buổi biến mất khỏi Lịch sử và báo cáo.
Đổi ngày khi có thay đổi chưa lưu → hiện modal xác nhận; “Ở lại” giữ nguyên, “Bỏ thay đổi” chuyển ngày.
Học sinh rời lớp sau khi đã được điểm danh → sửa và lưu lại buổi cũ → record của em đó vẫn còn trong báo cáo.
Báo cáo chi nhánh: % chuyên cần lớp = công thức mục 6 (viết unit test); lớp thấp nhất đứng đầu bảng; học sinh < 80% xuất hiện trong “Cần chú ý”.
Lọc khoảng ngày không trùng dữ liệu → hiện thông báo “ngoài khoảng ngày”; xoá lọc → dữ liệu hiện lại.
Xuất Excel ở cả 2 màn ra đúng cột, đúng ngôn ngữ đang chọn.
GV không phải admin không thấy “Báo cáo điểm danh” trong menu (role gate).
Toàn bộ text hiển thị đúng ở cả VI và EN.
10Ngoài phạm vi / tương lai
Thông báo phụ huynh khi con vắng (ZNS / Zalo OA — nối với hạ tầng Sprint 107). Đây là “killer feature” bán cho B2B — đề xuất ưu tiên ngay sau Phase 2.
Chuyên cần trong weekly / monthly report gửi chủ trường (template có sẵn).
Biểu đồ xu hướng chuyên cần theo tuần; so sánh giữa các chi nhánh (view gộp toàn hệ thống).
Điểm danh trên mobile ứng dụng giáo viên; điểm danh bằng QR cho lớp đông.
Liên kết với Thu học phí (buổi vắng có phép ảnh hưởng tính buổi học phí hay không — cần quyết với vận hành).
Prototype chạy tại môi trường dev nội bộ (branch dev, tab Điểm danh trong chi tiết lớp + menu Báo Cáo → Báo cáo điểm danh). Liên hệ Tùng để xem demo trực tiếp.