# UiBuilder Module - Elementor-like UI Builder

**Version**: 2.0  
**Laravel**: 11+  
**PHP**: 8.2+

## 📋 Tổng Quan

Module UiBuilder cung cấp một hệ thống **drag & drop visual builder** theo phong cách Elementor, cho phép admin xây dựng giao diện động (footer, header, banner...) cho từng tenant và domain mà không cần viết code.

### Tính Năng Chính

✅ **Drag & Drop Builder** - Kéo thả các element vào layout  
✅ **Multi-layout Support** - Hỗ trợ hàng (rows) và cột (columns) linh hoạt  
✅ **9 Element Types** - Text, Image, Links, Social, Menu, Newsletter, HTML, Divider, Spacer  
✅ **Real-time Preview** - Xem trước responsive (Desktop/Tablet/Mobile)  
✅ **Version Control** - Lưu lịch sử thay đổi và khôi phục  
✅ **Multi-tenant** - Quản lý riêng cho từng tenant và domain  
✅ **Server-side Rendering** - Render HTML từ structure JSON  
✅ **HTML Sanitization** - Bảo mật với HTMLPurifier  
✅ **Caching** - Cache kết quả render cho performance  
✅ **REST API** - API cho frontend (Next.js, React...)

---

## 🗄️ Database Schema

### Table: `ui_structures`

Lưu trữ cấu trúc layout do user tạo.

| Column | Type | Description |
|--------|------|-------------|
| id | bigint | Primary key |
| tenant_id | bigint | FK -> hocmai_tenants.id |
| domain_id | bigint (nullable) | FK -> hocmai_tenant_domains.id |
| component | varchar(50) | Component type (footer, header, banner) |
| name | varchar(255) | Tên cấu trúc |
| slug | varchar(255) | Slug unique per tenant+domain+component |
| structure | json | Layout structure (rows, columns, elements) |
| version | int | Version number (auto-increment on update) |
| is_active | boolean | Active status |
| created_by | bigint (nullable) | User ID |
| updated_by | bigint (nullable) | User ID |
| timestamps | timestamp | created_at, updated_at |

**Indexes:**
- `unique_structure_per_domain` (tenant_id, domain_id, component, slug)
- `idx_active_structures` (tenant_id, domain_id, component, is_active)

### Table: `ui_structure_revisions`

Lưu lịch sử thay đổi.

| Column | Type | Description |
|--------|------|-------------|
| id | bigint | Primary key |
| ui_structure_id | bigint | FK -> ui_structures.id |
| structure | json | Snapshot of structure |
| version | int | Version at this revision |
| created_by | bigint (nullable) | User ID |
| created_at | timestamp | When revision was created |

---

## 📐 Structure JSON Format

### Top-level Schema

```json
{
  "rows": [
    {
      "id": "row_1",
      "columns": [
        {
          "id": "col_1",
          "width": 6,
          "elements": [
            {
              "id": "el_1",
              "type": "text",
              "settings": {...}
            }
          ],
          "styles": {}
        }
      ],
      "styles": {}
    }
  ],
  "styles": {
    "background-color": "#fff",
    "padding": "20px"
  },
  "custom_css": "/* custom CSS */",
  "meta": {
    "name": "My Footer",
    "description": "..."
  }
}
```

### Grid System

- **12-column grid**: Width từ 1-12 (12 = 100% width)
- **Responsive**: Auto-stack trên mobile (<768px)

### Element Types & Settings

#### 1. **Text**
```json
{
  "type": "text",
  "settings": {
    "content": "Hello World",
    "tag": "p",           // p, h1, h2, h3, h4, h5, h6
    "align": "left",      // left, center, right
    "class": "my-class"
  }
}
```

#### 2. **Image**
```json
{
  "type": "image",
  "settings": {
    "src": "/storage/uibuilder/logo.png",
    "alt": "Logo",
    "width": "150px",
    "link": "https://example.com",  // optional
    "class": "logo"
  }
}
```

#### 3. **Links** (List)
```json
{
  "type": "links",
  "settings": {
    "items": [
      {"text": "Home", "url": "/"},
      {"text": "About", "url": "/about"}
    ],
    "layout": "vertical",  // vertical | horizontal
    "class": "nav-links"
  }
}
```

#### 4. **Social**
```json
{
  "type": "social",
  "settings": {
    "networks": [
      {"name": "facebook", "url": "https://facebook.com/..."},
      {"name": "twitter", "url": "https://twitter.com/..."}
    ],
    "style": "default",
    "class": "social-icons"
  }
}
```

**Supported networks**: facebook, twitter, instagram, linkedin, youtube

#### 5. **Menu**
```json
{
  "type": "menu",
  "settings": {
    "menu_slug": "main-menu",
    "class": "menu"
  }
}
```
*Note: Menu rendering cần implement logic fetch menu từ CMS*

#### 6. **Newsletter**
```json
{
  "type": "newsletter",
  "settings": {
    "placeholder": "Enter your email",
    "button_text": "Subscribe",
    "action_url": "/newsletter/subscribe",
    "class": "newsletter-form"
  }
}
```

#### 7. **HTML** (Custom)
```json
{
  "type": "html",
  "settings": {
    "content": "<div>Custom HTML</div>",
    "class": "custom-html"
  }
}
```
*Note: HTML sẽ được sanitize bởi HTMLPurifier*

#### 8. **Divider**
```json
{
  "type": "divider",
  "settings": {
    "height": "1px",
    "style": "solid",     // solid, dashed, dotted
    "color": "#ccc",
    "class": "divider"
  }
}
```

#### 9. **Spacer**
```json
{
  "type": "spacer",
  "settings": {
    "height": "20px"
  }
}
```

---

## 🔌 API Endpoints

### Admin API (Authenticated)

Base URL: `/api/admin/ui`  
Authentication: `web` middleware (session-based)

#### Structures CRUD

```
GET    /api/admin/ui/structures              # List all structures
POST   /api/admin/ui/structures              # Create new structure
GET    /api/admin/ui/structures/{id}         # Get structure by ID
PUT    /api/admin/ui/structures/{id}         # Update structure
DELETE /api/admin/ui/structures/{id}         # Delete structure
```

**Query params (GET list)**:
- `tenant_id` (int)
- `domain_id` (int)
- `component` (string): footer, header, banner
- `is_active` (bool)
- `search` (string): Search by name or slug
- `per_page` (int): Default 15

#### Additional Endpoints

```
POST /api/admin/ui/structures/{id}/publish           # Set as active
GET  /api/admin/ui/structures/{id}/revisions         # Get revisions
POST /api/admin/ui/structures/{id}/restore/{revId}   # Restore revision
POST /api/admin/ui/structures/{id}/preview           # Preview render
```

#### Upload

```
POST   /api/admin/ui/upload      # Upload image
DELETE /api/admin/ui/upload      # Delete image
```

### Public API (No Auth)

#### Render for Frontend

```
GET /api/ui/render?component=footer
```

**Query params**:
- `component` (required): footer, header, banner
- `tenant_id` (optional): For testing
- `domain_id` (optional): For testing

**Headers** (optional):
- `X-Tenant-Code`: Tenant domain code

**Response**:
```json
{
  "success": true,
  "data": {
    "component": "footer",
    "structure_id": 123,
    "html": "<div>...</div>",
    "css": ".ui-structure-container {...}",
    "version": 5
  }
}
```

#### Get Structure JSON (Debug)

```
GET /api/ui/structure?tenant_id=1&domain_id=2&component=footer
```

---

## 💻 Usage

### 1. Backend Setup

#### Run Migrations

```bash
php artisan migrate
```

#### Register Service Provider

Add to `config/app.php`:

```php
'providers' => [
    // ...
    App\Modules\UiBuilder\Providers\UiBuilderServiceProvider::class,
],
```

#### Install HTMLPurifier

```bash
composer require ezyang/htmlpurifier
```

### 2. Create Structure via API

```bash
curl -X POST http://yourapp.test/api/admin/ui/structures \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "tenant_id": 1,
    "domain_id": 2,
    "component": "footer",
    "name": "My Footer",
    "slug": "my-footer",
    "structure": {
      "rows": [...],
      "styles": {...}
    },
    "is_active": true
  }'
```

### 3. Frontend Integration (Next.js Example)

```javascript
// app/components/Footer.tsx
import { useEffect, useState } from 'react';

export default function Footer() {
  const [footerHtml, setFooterHtml] = useState('');
  const [footerCss, setFooterCss] = useState('');

  useEffect(() => {
    fetch('https://api.yourapp.com/api/ui/render?component=footer', {
      headers: {
        'X-Tenant-Code': 'yourdomain.com'
      }
    })
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          setFooterHtml(data.data.html);
          setFooterCss(data.data.css);
        }
      });
  }, []);

  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: footerCss }} />
      <div dangerouslySetInnerHTML={{ __html: footerHtml }} />
    </>
  );
}
```

---

## 🎨 Example Structures

### Load Example

```bash
# 3-column footer
php artisan tinker
>>> $json = file_get_contents(app_path('Modules/UiBuilder/Resources/examples/footer-3-column.json'));
>>> $structure = json_decode($json, true);
>>> App\Modules\UiBuilder\Models\UiStructure::create([
      'tenant_id' => 1,
      'domain_id' => 1,
      'component' => 'footer',
      'name' => '3-Column Footer',
      'slug' => 'footer-3-column',
      'structure' => $structure,
      'is_active' => true
    ]);
```

---

## 🔒 Security

### HTML Sanitization

- **Server-side**: HTMLPurifier sanitizes all HTML before rendering
- **Allowed tags**: p, a, img, div, span, h1-h6, ul, ol, li, table, footer, header, section, nav
- **Strips**: `<script>`, inline JavaScript, dangerous attributes

### Image Upload

- **Validation**: MIME type & size check
- **Storage**: `storage/app/public/uibuilder/`
- **Public URL**: `/storage/uibuilder/`

### Authentication

- Admin APIs require `web` or `sanctum` middleware
- Public render API is open (cacheable)

---

## 📦 Models & Services

### UiStructure Model

```php
use App\Modules\UiBuilder\Models\UiStructure;

// Get active structure
$structure = UiStructure::getActiveFor($tenantId, $domainId, 'footer');

// Publish structure
$structure->publish();

// Get revisions
$revisions = $structure->revisions;

// Restore revision
$structure->restoreRevision($revisionId);

// Clear cache
$structure->clearCache();
```

### UiRenderer Service

```php
use App\Modules\UiBuilder\Services\UiRenderer;

$renderer = new UiRenderer();
$result = $renderer->render($structureArray);

// Returns: ['html' => '...', 'css' => '...']
```

---

## 🚀 Performance

### Caching

- **Key format**: `tenant_ui:{tenant_id}:{domain_id}:{component}`
- **TTL**: 3600 seconds (1 hour)
- **Auto-clear**: On structure save/delete

### Optimization Tips

1. Enable Redis for cache driver
2. Use CDN for uploaded images
3. Minify CSS output in production
4. Use HTTP caching headers for render API

---

## 🔄 Version History

- **v2.0**: Elementor-like builder với structure JSON
- **v1.0**: Static template system (deprecated)

---

## 📝 TODO - Frontend Builder UI

**Note**: Backend infrastructure đã hoàn thành. Phần **Vue 3 Builder UI** cần implement:

### Remaining Tasks:

1. **Setup Vite for Vue 3** in module
   - Create `vite.config.js` in module
   - Install: `vue@3`, `@vitejs/plugin-vue`, `vue-draggable-next`, `sortablejs`

2. **Create Vue Components**:
   - `BuilderApp.vue` - Main app
   - `BlocksPalette.vue` - Left sidebar with draggable elements
   - `BuilderCanvas.vue` - Middle canvas with iframe preview
   - `SettingsPanel.vue` - Right sidebar for element settings
   - `TopBar.vue` - Responsive controls, undo/redo, save/publish

3. **Implement Features**:
   - Drag & drop with `vue-draggable-next`
   - Inline editing for text elements
   - Image upload modal
   - Color picker for colors
   - Undo/redo với history stack
   - Responsive preview (Desktop/Tablet/Mobile)

4. **Blade Views**:
   - `builder/index.blade.php` - List structures
   - `builder/edit.blade.php` - Mount Vue app
   - `builder/preview.blade.php` - Preview iframe

5. **Web Routes**:
   ```php
   Route::get('/admin/ui-builder', [BuilderController::class, 'index']);
   Route::get('/admin/ui-builder/create', [BuilderController::class, 'create']);
   Route::get('/admin/ui-builder/{id}/edit', [BuilderController::class, 'edit']);
   ```

**Estimate**: ~40-60 hours for full Vue 3 builder implementation

---

## 🤝 Contributing

### Add New Element Type

1. Add render method in `UiRenderer.php`:
```php
private function renderMyElement(array $settings, string $id): string
{
    // Your render logic
    return "<div id='{$id}'>...</div>";
}
```

2. Add to element type switch:
```php
return match($type) {
    // ...
    'my_element' => $this->renderMyElement($settings, $elementId),
    default => '',
};
```

3. Update documentation with settings schema

---

## 📄 License

Proprietary - Internal use only

---

## 📧 Support

For questions or issues, contact the development team.
