# Hình Ảnh - Thẻ Tham Khảo Nhanh

## 🎯 Tổng Quan

| Mục | Trạng Thái | Chi Tiết |
|-----|------------|----------|
| **Thẻ HTML `<img>`** | ✅ Tìm thấy 0 | Tất cả đã được thay thế bằng next/image |
| **next/image Đã Dùng** | ✅ 8 tệp | Triển khai đúng cách trên toàn ứng dụng |
| **Thành Phần Hình Ảnh** | ✅ 3 chuyên biệt | Gallery, Lightbox, Upload |
| **Cấu Hình** | ✅ Đã cấu hình | remotePatterns + tiêu đề CSP |
| **Khả Năng Tiếp Cận** | ✅ Hỗ trợ đầy đủ | Văn bản thay thế, điều hướng bàn phím, ARIA |
| **Bảo Mật** | ✅ Chỉ HTTPS | CSP đã cấu hình, blob URL cho xem trước |

---

## 📁 Vị Trí Sử Dụng Hình Ảnh

### **Thành Phần Hình Ảnh Cốt Lõi**
```
components/listings/image-gallery.tsx      ← Trình xem thư viện chính
components/listings/image-lightbox.tsx     ← Xem toàn màn hình
components/listings/image-upload.tsx       ← Tải lên với xem trước
```

### **Các Thành Phần Hiển Thị Hình Ảnh**
```
components/search/property-card.tsx        → Hình thu nhỏ trong kết quả tìm kiếm
components/agents/agent-profile-client.tsx → Ảnh đại diện + danh sách của môi giới
components/comparison/comparison-table.tsx → Hình ảnh so sánh
components/listings/listing-detail-client.tsx → Tích hợp ImageGallery
```

### **Thành Phần Trang**
```
app/[locale]/(public)/listings/[id]/page.tsx       → Chi tiết tin đăng (dùng ImageGallery)
app/[locale]/(public)/search/page.tsx              → Kết quả tìm kiếm (dùng PropertyCard)
app/[locale]/(public)/agents/[id]/page.tsx         → Hồ sơ môi giới
app/[locale]/(dashboard)/listings/page.tsx         → Danh sách bảng điều khiển
app/[locale]/(dashboard)/listings/new/page.tsx     → Tải lên tin đăng mới
```

---

## 🔧 Cấu Hình

### **next.config.js**
```javascript
images: {
  remotePatterns: [
    {
      protocol: 'https',
      hostname: '**',
    },
  ],
}
```

### **Tiêu Đề CSP**
```
img-src 'self' data: blob: https://*.mapbox.com https://*.tiles.mapbox.com https:
```
- ✅ Cho phép blob: (xem trước tệp)
- ✅ Cho phép data: (hình ảnh nội tuyến)
- ✅ Cho phép tất cả HTTPS

---

## 📊 Chi Tiết Thành Phần Hình Ảnh

### ImageGallery
```typescript
<ImageGallery 
  media={propertyMedia}  // PropertyMedia[]
  className="w-full"
/>
```
**Tính năng:** Ảnh chính + hình thu nhỏ, điều hướng, bộ đếm, tích hợp lightbox

### ImageLightbox
```typescript
<ImageLightbox 
  images={images}
  initialIndex={0}
  open={isOpen}
  onClose={() => setIsOpen(false)}
/>
```
**Tính năng:** Điều hướng bàn phím, vuốt, tải trước, bẫy tiêu điểm

### ImageUpload
```typescript
<ImageUpload 
  images={uploadedImages}
  onChange={setUploadedImages}
  maxFiles={20}
/>
```
**Tính năng:** Kéo-thả, xác thực (JPEG/PNG/WebP), xem trước, dọn dẹp

---

## 🎨 Kiểu Dữ Liệu Hình Ảnh

```typescript
interface PropertyMedia {
  id: string;
  url: string;              // URL hình ảnh
  type: 'image' | 'video';  // Loại phương tiện
  order: number;            // Thứ tự hiển thị
  caption?: string;         // Chú thích tùy chọn
}

interface ImageFile {
  file: File;               // Tệp trình duyệt
  preview: string;          // blob: URL
}
```

---

## ⚡ Tính Năng Hiệu Suất

| Tính Năng | Trạng Thái |
|-----------|------------|
| Kích thước thích ứng (thuộc tính `sizes`) | ✅ Đã triển khai |
| Tải ưu tiên cho nội dung phía trên nếp gấp | ✅ Đã triển khai |
| Tải trước hình ảnh trong lightbox | ✅ Đã triển khai |
| Dọn dẹp blob URL (bộ nhớ) | ✅ Đã triển khai |
| Placeholder xương | ⚠️ Chưa triển khai |
| Nén hình ảnh khi tải lên | ⚠️ Chưa triển khai |

---

## ♿ Tính Năng Khả Năng Tiếp Cận

| Tính Năng | Trạng Thái |
|-----------|------------|
| Văn bản thay thế trên hình ảnh | ✅ Tiếng Việt |
| Nhãn ARIA | ✅ Đã triển khai |
| Điều hướng bàn phím | ✅ Phím mũi tên + Escape |
| Bẫy tiêu điểm trong hộp thoại | ✅ Đã triển khai |
| Bẫy tab | ✅ Đã triển khai |

---

## 🔒 Danh Sách Kiểm Tra Bảo Mật

- ✅ Mẫu remote chỉ dùng HTTPS
- ✅ Tiêu đề CSP đã cấu hình
- ✅ blob: URL chỉ dùng cho xem trước phía máy khách
- ⚠️ Xác thực URL hình ảnh tại API - **CẦN LÀM**
- ⚠️ Quét tệp tải lên của người dùng - **CẦN LÀM**

---

## 📝 Các Tác Vụ Thông Dụng

### Thêm Hình Ảnh vào Thành Phần
```tsx
import Image from 'next/image';

<Image
  src={imageUrl}
  alt="Văn bản mô tả bằng tiếng Việt"
  fill
  sizes="(max-width: 768px) 100vw, 50vw"
  className="object-cover"
/>
```

### Hiển Thị Thư Viện Hình Ảnh
```tsx
import { ImageGallery } from '@/components/listings/image-gallery';

<ImageGallery 
  media={property.media}
/>
```

### Tải Lên Tệp
```tsx
import { ImageUpload } from '@/components/listings/image-upload';

const [images, setImages] = useState<ImageFile[]>([]);

<ImageUpload 
  images={images}
  onChange={setImages}
  maxFiles={20}
/>
```

---

## 🚨 Lưu Ý Quan Trọng

1. **Không bao giờ dùng thẻ HTML `<img>`** - Sử dụng `next/image` thay thế
2. **Ngoại lệ:** Xem trước blob URL trong image-upload là chấp nhận được
3. **Luôn cung cấp văn bản thay thế** - Dùng văn bản tiếng Việt
4. **Dùng thuộc tính `sizes`** - Cho hình ảnh thích ứng
5. **Đặt `priority`** - Cho hình ảnh phía trên nếp gấp
6. **Thu hồi blob URL** - Khi huỷ gắn kết để ngăn rò rỉ bộ nhớ
7. **Xác thực URL hình ảnh** - Tại lớp API trước khi trả về

---

## 📞 Câu Hỏi?

Xem `IMAGE_AUDIT_REPORT.md` để biết chi tiết đầy đủ và các khuyến nghị.
