Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
318 lines
11 KiB
Markdown
318 lines
11 KiB
Markdown
# GoodGo Platform Kiểm Toán Khả Năng Tiếp Cận - Tài Liệu Tham Khảo Nhanh
|
||
**Ngày:** 10 tháng 4 năm 2026 | **Trạng thái:** Tuân thủ WCAG 2.1 AA ở mức 70–75%
|
||
|
||
---
|
||
|
||
## 📊 Chỉ Số Chính
|
||
|
||
| Chỉ số | Số lượng | Trạng thái |
|
||
|--------|----------|------------|
|
||
| Tổng số tệp đã phân tích | 90+ | ✅ |
|
||
| Thuộc tính ARIA tìm thấy | 75 | ✅ |
|
||
| Tệp sử dụng ARIA | 14 | ✅ |
|
||
| Vấn đề nghiêm trọng | 2 | 🔴 |
|
||
| Vấn đề lớn | 3 | 🟡 |
|
||
| Vấn đề nhỏ | 2 | 🟢 |
|
||
|
||
---
|
||
|
||
## 🔴 VẤN ĐỀ NGHIÊM TRỌNG (Phải Sửa)
|
||
|
||
### 1. Thành Phần Dialog Thiếu Khả Năng Tiếp Cận
|
||
**Tệp:** `apps/web/components/ui/dialog.tsx`
|
||
**Vấn đề:**
|
||
- Thiếu `role="dialog"`
|
||
- Thiếu `aria-modal="true"`
|
||
- Không có bẫy tiêu điểm (focus trap)
|
||
- Không xử lý phím Escape
|
||
- Phần nền không bị ẩn khỏi trình đọc màn hình
|
||
|
||
**Thời gian sửa:** 2–3 giờ | **Ưu tiên:** 1
|
||
|
||
**Danh sách kiểm tra sửa nhanh:**
|
||
- [ ] Thêm role="dialog" vào vùng chứa dialog
|
||
- [ ] Thêm aria-modal="true"
|
||
- [ ] Triển khai trình lắng nghe phím Escape
|
||
- [ ] Thêm aria-hidden="true" vào lớp phủ nền
|
||
- [ ] Kiểm tra với trình đọc màn hình NVDA
|
||
|
||
---
|
||
|
||
### 2. Nút Hình Thu Nhỏ Trong Thư Viện Ảnh Thiếu Nhãn
|
||
**Tệp:** `apps/web/components/listings/image-gallery.tsx:69-84`
|
||
**Vấn đề:** Các nút hình thu nhỏ thiếu aria-label
|
||
|
||
**Thời gian sửa:** 15–30 phút | **Ưu tiên:** 2
|
||
|
||
**Sửa nhanh:**
|
||
```tsx
|
||
// Thêm vào mỗi nút hình thu nhỏ:
|
||
aria-label={`Select image ${index + 1}${img.caption ? ': ' + img.caption : ''}`}
|
||
aria-pressed={index === selectedIndex}
|
||
```
|
||
|
||
---
|
||
|
||
## 🟡 VẤN ĐỀ LỚN (Nên Sửa)
|
||
|
||
### 1. Tiêu Đề Layout Admin Thiếu Vai Trò Banner
|
||
**Tệp:** `apps/web/app/[locale]/(admin)/layout.tsx:134`
|
||
|
||
**Sửa nhanh:**
|
||
```tsx
|
||
// Đổi từ:
|
||
<header className="sticky...">
|
||
|
||
// Sang:
|
||
<header role="banner" className="sticky...">
|
||
```
|
||
|
||
**Thời gian sửa:** 2 phút | **Ưu tiên:** 3
|
||
|
||
---
|
||
|
||
### 2. Độ Tương Phản Màu Chưa Được Kiểm Tra
|
||
**Vấn đề:** Các biến CSS đã được định nghĩa nhưng tỷ lệ tương phản chưa được kiểm tra
|
||
**Tác động:** Có thể vi phạm WCAG 1.4.3
|
||
**Thời gian sửa:** 4–6 giờ kiểm tra
|
||
|
||
**Danh sách kiểm tra:**
|
||
- [ ] Trích xuất giá trị biến CSS từ globals.css
|
||
- [ ] Kiểm tra bằng WebAIM Contrast Checker
|
||
- [ ] Xác minh tỷ lệ 4.5:1 cho văn bản thông thường (WCAG AA)
|
||
- [ ] Xác minh tỷ lệ 7:1 cho tuân thủ AAA
|
||
- [ ] Kiểm tra ở cả chế độ sáng và tối
|
||
|
||
---
|
||
|
||
### 3. Ô Nhập Tìm Kiếm Trang Đầu Thiếu Nhãn Hiển Thị
|
||
**Tệp:** `apps/web/app/[locale]/(public)/page.tsx:87-92`
|
||
|
||
**Sửa nhanh:**
|
||
```tsx
|
||
<div className="flex flex-col gap-1">
|
||
<label htmlFor="search-input" className="sr-only">
|
||
{t('landing.searchPlaceholder')}
|
||
</label>
|
||
<Input
|
||
id="search-input"
|
||
placeholder={t('landing.searchPlaceholder')}
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
/>
|
||
</div>
|
||
```
|
||
|
||
**Thời gian sửa:** 30 phút | **Ưu tiên:** 4
|
||
|
||
---
|
||
|
||
## 🟢 VẤN ĐỀ NHỎ (Nên Có)
|
||
|
||
### 1. aria-label Thừa Trên Văn Bản Đã Hiển Thị
|
||
**Tệp:** `apps/web/app/[locale]/(dashboard)/layout.tsx:125`
|
||
**Vấn đề:** Các liên kết có aria-label trong khi văn bản đã hiển thị
|
||
|
||
**Khuyến nghị:** Xóa các aria-label thừa — văn bản hiển thị sẵn tốt hơn cho khả năng tiếp cận
|
||
|
||
---
|
||
|
||
## ✅ NHỮNG GÌ ĐANG HOẠT ĐỘNG TỐT
|
||
|
||
### Biểu Mẫu Xác Thực
|
||
- ✅ Tất cả các ô nhập có nhãn hiển thị với `<Label htmlFor="id">`
|
||
- ✅ Thông báo lỗi được liên kết qua `aria-describedby`
|
||
- ✅ Trạng thái không hợp lệ được đánh dấu bằng `aria-invalid`
|
||
- ✅ Nút hiện/ẩn mật khẩu có `aria-label` đúng chuẩn
|
||
- **Ví dụ:** `apps/web/app/[locale]/(auth)/login/page.tsx`
|
||
|
||
### Liên Kết Bỏ Qua Nội Dung (Skip-to-Content)
|
||
- ✅ Được triển khai đúng chuẩn với khả năng hiển thị tiêu điểm
|
||
- ✅ Ẩn theo mặc định, hiển thị khi được tiêu điểm
|
||
- ✅ Liên kết đến `id="main-content"`
|
||
- **Ví dụ:** `apps/web/app/[locale]/layout.tsx:105-110`
|
||
|
||
### Điều Hướng
|
||
- ✅ Tất cả các nav có `aria-label`
|
||
- ✅ Nút chuyển đổi menu di động có nhãn động
|
||
- ✅ Biểu tượng được ẩn đúng chuẩn với `aria-hidden="true"`
|
||
- **Ví dụ:** Tất cả các tệp layout
|
||
|
||
### Tìm Kiếm & Bộ Lọc
|
||
- ✅ Phần bộ lọc có `role="search"`
|
||
- ✅ Tất cả các ô select có `aria-label`
|
||
- ✅ Ô nhập kiểu dải (range) được gắn nhãn đúng chuẩn
|
||
- **Ví dụ:** `apps/web/components/search/filter-bar.tsx`
|
||
|
||
### Thư Viện Ảnh
|
||
- ✅ Các nút Trước/Sau có aria-label
|
||
- ✅ Cấu trúc ngữ nghĩa tốt
|
||
- **Ví dụ:** `apps/web/components/listings/image-gallery.tsx:47, 54`
|
||
|
||
---
|
||
|
||
## 📋 DANH SÁCH KIỂM TRA THỬ NGHIỆM
|
||
|
||
### Trước Khi Triển Khai
|
||
- [ ] Sửa khả năng tiếp cận của thành phần dialog
|
||
- [ ] Thêm nhãn cho các nút hình thu nhỏ
|
||
- [ ] Thêm vai trò banner vào tiêu đề admin
|
||
- [ ] Chạy kiểm toán khả năng tiếp cận Lighthouse (mục tiêu: 90+)
|
||
- [ ] Kiểm tra với trình đọc màn hình NVDA
|
||
- [ ] Kiểm tra điều hướng bàn phím (Tab, Shift+Tab, Enter, Escape)
|
||
- [ ] Xác minh tỷ lệ tương phản màu sắc
|
||
- [ ] Kiểm tra chuyển đổi giao diện (chế độ sáng/tối)
|
||
|
||
### Kiểm Tra Trình Đọc Màn Hình (NVDA)
|
||
- [ ] Biểu mẫu đăng nhập: Có thể điền trường, nghe thông báo lỗi
|
||
- [ ] Điều hướng: Có thể điều hướng menu, hiểu trang hiện tại
|
||
- [ ] Tìm kiếm: Có thể tìm và gửi biểu mẫu tìm kiếm
|
||
- [ ] Hộp thoại: Có thể mở, tương tác, đóng bằng phím Escape
|
||
- [ ] Thư viện ảnh: Có thể chọn ảnh, nghe ảnh nào đang được chọn
|
||
- [ ] Menu di động: Có thể mở/đóng, nút chuyển đổi hoạt động bình thường
|
||
|
||
### Điều Hướng Bàn Phím
|
||
- [ ] Tab: Di chuyển qua tất cả các phần tử tương tác theo thứ tự hợp lý
|
||
- [ ] Shift+Tab: Di chuyển ngược lại qua các phần tử
|
||
- [ ] Enter: Kích hoạt nút/liên kết/gửi biểu mẫu
|
||
- [ ] Space: Kích hoạt nút, bật/tắt hộp kiểm
|
||
- [ ] Escape: Đóng modal/menu
|
||
- [ ] Phím mũi tên: Hoạt động trong dropdown/băng chuyền
|
||
|
||
---
|
||
|
||
## 🎯 Lộ Trình Ưu Tiên
|
||
|
||
### Tuần 1 (40 giờ)
|
||
1. **Sửa Thành Phần Dialog** (2–3 giờ)
|
||
- [ ] Thêm role, aria-modal, focus trap, xử lý phím Escape
|
||
- [ ] Kiểm tra với trình đọc màn hình
|
||
|
||
2. **Thêm Nhãn Hình Thu Nhỏ** (0,5 giờ)
|
||
- [ ] Thêm aria-label vào các nút hình thu nhỏ
|
||
- [ ] Kiểm tra
|
||
|
||
3. **Sửa Tiêu Đề Admin** (0,1 giờ)
|
||
- [ ] Thêm role="banner"
|
||
|
||
4. **Xác Minh Tương Phản** (6–8 giờ)
|
||
- [ ] Trích xuất biến CSS
|
||
- [ ] Kiểm tra tất cả các tổ hợp
|
||
- [ ] Ghi lại kết quả
|
||
- [ ] Điều chỉnh nếu cần
|
||
|
||
5. **Thêm Nhãn Trang Đầu** (0,5 giờ)
|
||
- [ ] Thêm nhãn hiển thị vào ô nhập tìm kiếm
|
||
|
||
6. **Kiểm Tra Trình Duyệt** (8 giờ)
|
||
- [ ] Kiểm tra NVDA
|
||
- [ ] Kiểm tra Chrome/Firefox/Safari
|
||
- [ ] Kiểm tra trên thiết bị di động
|
||
|
||
### Tuần 2 (20 giờ)
|
||
1. **Xóa Nhãn Thừa** (0,5 giờ)
|
||
2. **Thêm Liên Kết Bỏ Qua Bổ Sung** (2–3 giờ)
|
||
3. **Kiểm Tra Toàn Diện** (8 giờ)
|
||
4. **Sửa Các Vấn Đề Phát Hiện** (6 giờ)
|
||
|
||
---
|
||
|
||
## 📁 Tài Liệu Tham Chiếu Tệp
|
||
|
||
### Các Tệp Quan Trọng Cần Xem Xét/Sửa
|
||
- `apps/web/components/ui/dialog.tsx` - 🔴 CẦN VIẾT LẠI
|
||
- `apps/web/components/listings/image-gallery.tsx` - 🔴 CẦN SỬA NHỎ
|
||
- `apps/web/app/[locale]/(admin)/layout.tsx` - 🟡 THÊM ROLE
|
||
- `apps/web/app/globals.css` - 🟡 XÁC MINH MÀU SẮC (chưa kiểm toán)
|
||
|
||
### Các Tệp Có Khả Năng Tiếp Cận Tốt
|
||
- `apps/web/app/[locale]/(public)/layout.tsx` - ✅ XUẤT SẮC
|
||
- `apps/web/app/[locale]/(auth)/login/page.tsx` - ✅ XUẤT SẮC
|
||
- `apps/web/app/[locale]/(auth)/register/page.tsx` - ✅ XUẤT SẮC
|
||
- `apps/web/components/ui/button.tsx` - ✅ TỐT
|
||
- `apps/web/components/ui/input.tsx` - ✅ TỐT
|
||
- `apps/web/components/ui/label.tsx` - ✅ TỐT
|
||
- `apps/web/components/ui/select.tsx` - ✅ TỐT
|
||
- `apps/web/components/search/filter-bar.tsx` - ✅ TỐT
|
||
|
||
---
|
||
|
||
## 🔍 Ví Dụ Mã
|
||
|
||
### Ví Dụ Sử Dụng ARIA Tốt
|
||
```tsx
|
||
// Tốt: aria-label động cập nhật theo trạng thái
|
||
<button
|
||
aria-label={mobileMenuOpen ? t('nav.closeMenu') : t('nav.openMenu')}
|
||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||
>
|
||
{mobileMenuOpen ? <X /> : <Menu />}
|
||
</button>
|
||
```
|
||
|
||
### Ví Dụ Khả Năng Tiếp Cận Biểu Mẫu
|
||
```tsx
|
||
// Tốt: Liên kết nhãn đúng chuẩn + xử lý lỗi
|
||
<div className="space-y-2">
|
||
<Label htmlFor="phone">{t('phone')}</Label>
|
||
<Input
|
||
id="phone"
|
||
type="tel"
|
||
aria-describedby={errors.phone ? 'phone-error' : undefined}
|
||
aria-invalid={!!errors.phone}
|
||
{...register('phone')}
|
||
/>
|
||
{errors.phone && (
|
||
<p id="phone-error" role="alert">{errors.phone.message}</p>
|
||
)}
|
||
</div>
|
||
```
|
||
|
||
### Ví Dụ Biểu Mẫu Tìm Kiếm
|
||
```tsx
|
||
// Tốt: Vai trò ngữ nghĩa đúng chuẩn với các điều khiển được gắn nhãn
|
||
<form role="search" aria-label={t('filters')}>
|
||
<Select aria-label={t('allTransactions')}>
|
||
{/* options */}
|
||
</Select>
|
||
</form>
|
||
```
|
||
|
||
---
|
||
|
||
## 📞 Tài Nguyên
|
||
|
||
### Tài Liệu
|
||
- Báo cáo kiểm toán đầy đủ: `ACCESSIBILITY_AUDIT_2026-04-10.md`
|
||
- Kết quả chi tiết trên 15 phần
|
||
- Phân tích từng tệp
|
||
- Đánh giá tuân thủ WCAG 2.1
|
||
|
||
### Công Cụ Kiểm Tra
|
||
- **Axe DevTools:** Tiện ích mở rộng Chrome/Firefox để kiểm tra nhanh
|
||
- **Lighthouse:** Tích hợp trong Chrome DevTools
|
||
- **WebAIM Contrast Checker:** https://webaim.org/resources/contrastchecker/
|
||
- **NVDA:** Trình đọc màn hình miễn phí - https://www.nvaccess.org/
|
||
|
||
### Tài Nguyên Học Tập
|
||
- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
|
||
- [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
|
||
- [MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
|
||
|
||
---
|
||
|
||
## 📝 Ghi Chú
|
||
|
||
**Phạm vi kiểm toán:** apps/web (giao diện Next.js 15)
|
||
**Số tệp đã phân tích:** 90+ tệp TSX/JSX
|
||
**Thời gian đạt tuân thủ AA:** 4–6 ngày làm toàn thời gian
|
||
**Bảo trì liên tục:** Nên tích hợp vào CI/CD và đào tạo lập trình viên
|
||
|
||
**Bước tiếp theo:**
|
||
1. Xem xét báo cáo kiểm toán đầy đủ (1552 dòng)
|
||
2. Tạo ticket Jira cho mỗi vấn đề
|
||
3. Phân công nhiệm vụ cho nhóm phát triển
|
||
4. Lên lịch kiểm tra khả năng tiếp cận
|
||
5. Lập kế hoạch buổi đào tạo lập trình viên
|
||
|