> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# View Scaffolding

> Automatically generate CRUD views

Learn how to automatically generate list and form views for admin pages with Sonamu's scaffolding system.

## View Scaffolding Overview

<CardGroup cols={2}>
  <Card title="Auto Generation" icon="wand-magic-sparkles">
    List/Form views Generate via web UI
  </Card>

  <Card title="Type Safe" icon="shield-check">
    Entity based Compile-time validation
  </Card>

  <Card title="Customizable" icon="sliders">
    Modify after generation Full control
  </Card>

  <Card title="Consistency" icon="clone">
    Unified UI/UX Best practices
  </Card>
</CardGroup>

## What is View Scaffolding?

### Problem: Repetitive CRUD View Writing

In traditional development, you have to **manually write similar CRUD screens repeatedly** for each entity.

**Problems with manual writing**:

1. **Time waste**: Writing list, create, edit pages all by hand
2. **Lack of consistency**: Different styles and structures per developer
3. **Hard to maintain**: Need to modify all views when entity changes
4. **Prone to mistakes**: Missing fields, wrong types, missing validation
5. **Boilerplate**: Repetitive code for tables, forms, buttons

**Example**: Pages needed for a single User entity

```
/admin/users        → List page (table, pagination, search)
/admin/users/form   → Create/Edit page (form, validation)
```

Each page requires 100-200+ lines of code, and this must be repeated for every entity.

### Solution: Auto Scaffolding

Sonamu **automatically generates admin page views** based on Entity definitions.

**Benefits**:

1. ✨ **Instant generation**: Generate views with just clicks in web UI
2. ✨ **Type safe**: Entity types directly reflected in components
3. ✨ **Consistent UI**: Unified design system
4. ✨ **Best practices**: Proven patterns applied
5. ✨ **Customizable**: Modify only necessary parts after generation

**Sonamu's scaffolding philosophy**:

> "80% automatic, 20% customization"

Most CRUD functionality is auto-generated, and only special business logic needs additional implementation.

## Running Scaffolding

### Using Sonamu Web UI

Sonamu provides a web-based management UI.

```bash theme={null}
# Run Sonamu dev server
pnpm dev

# Access Sonamu UI in browser
# http://localhost:3000/sonamu
```

**Generating views in web UI**:

1. Access Sonamu UI
2. Select "Scaffold" menu
3. Select Entity (e.g., User)
4. Click "Generate View List" button → `index.tsx` generated
5. Click "Generate View Form" button → `form.tsx` generated

<Info>
  **CLI commands are currently disabled**. View generation is only available through the Sonamu web
  UI.
</Info>

### Generated File Structure

```
web/src/routes/admin/
└── users/
    ├── index.tsx    # List page (includes table)
    └── form.tsx     # Integrated create/edit form
```

**Actually generated files**:

* **index.tsx**: Page with list table directly included
* **form.tsx**: Create and edit combined into one form

<Warning>
  **Note**: - Separate component files (UserTable.tsx, UserForm.tsx) are not generated - Detail page
  (\[id].tsx) is not auto-generated - All logic is integrated into two page files
</Warning>

## Generated View Structure

### List Page (index.tsx)

Displays list in a table and provides pagination and search functionality.

```typescript theme={null}
// web/src/routes/admin/users/index.tsx (auto-generated)
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { UserService } from "@/services/services.generated";

export default function UsersPage() {
  const navigate = useNavigate();
  const [page, setPage] = useState(1);
  const [search, setSearch] = useState("");

  // Using TanStack Query Hook
  const { data: users, isLoading } = UserService.useUsers("A", {
    page,
    pageSize: 20,
    search,
  });

  return (
    <div className="admin-page">
      <div className="header">
        <h1>Users</h1>
        <button onClick={() => navigate("/admin/users/form")}>
          Create New User
        </button>
      </div>

      <div className="search">
        <input
          type="text"
          placeholder="Search users..."
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        />
      </div>

      {/* Table directly included */}
      <table className="data-table">
        <thead>
          <tr>
            <th>ID</th>
            <th>Email</th>
            <th>Username</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {users?.map((user) => (
            <tr key={user.id}>
              <td>{user.id}</td>
              <td>{user.email}</td>
              <td>{user.username}</td>
              <td>
                <button onClick={() => navigate(`/admin/users/form?id=${user.id}`)}>
                  Edit
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {/* Pagination */}
      <div className="pagination">
        <button disabled={page === 1} onClick={() => setPage(page - 1)}>
          Previous
        </button>
        <span>Page {page}</span>
        <button onClick={() => setPage(page + 1)}>
          Next
        </button>
      </div>
    </div>
  );
}
```

**Features**:

* Pagination
* Search filter
* "Create New" button
* "Edit" button (each row)
* **Table directly included in page** (no separate component)

### Form Page (form.tsx)

Combines create and edit into one form.

```typescript theme={null}
// web/src/routes/admin/users/form.tsx (auto-generated)
import { useState, useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { UserService } from "@/services/services.generated";

export default function UserFormPage() {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const userId = searchParams.get("id");

  const [formData, setFormData] = useState({
    email: "",
    username: "",
    bio: "",
  });

  const [saving, setSaving] = useState(false);

  // Edit mode: Load existing data
  useEffect(() => {
    if (userId) {
      UserService.getUser("C", parseInt(userId)).then((user) => {
        setFormData({
          email: user.email,
          username: user.username,
          bio: user.bio || "",
        });
      });
    }
  }, [userId]);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setSaving(true);

    try {
      if (userId) {
        // Update
        await UserService.update(parseInt(userId), formData);
      } else {
        // Create
        await UserService.create(formData);
      }

      navigate("/admin/users");
    } catch (error) {
      alert("Failed to save");
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="admin-page">
      <div className="header">
        <h1>{userId ? "Edit User" : "Create New User"}</h1>
        <button onClick={() => navigate("/admin/users")}>
          Back to List
        </button>
      </div>

      <form onSubmit={handleSubmit} className="entity-form">
        <div className="form-field">
          <label>Email *</label>
          <input
            type="email"
            value={formData.email}
            onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            required
          />
        </div>

        <div className="form-field">
          <label>Username *</label>
          <input
            type="text"
            value={formData.username}
            onChange={(e) => setFormData({ ...formData, username: e.target.value })}
            required
          />
        </div>

        <div className="form-field">
          <label>Bio</label>
          <textarea
            value={formData.bio}
            onChange={(e) => setFormData({ ...formData, bio: e.target.value })}
            rows={4}
          />
        </div>

        <div className="form-actions">
          <button type="submit" disabled={saving}>
            {saving ? "Saving..." : "Save"}
          </button>
        </div>
      </form>
    </div>
  );
}
```

**Features**:

* **Create/Edit integrated**: Mode distinguished by URL parameter (`?id=`)
* Auto-loads existing data when editing
* Validation
* Button disabled while saving
* "Back to List" button

## Generated File Characteristics

### Integrated Structure

Sonamu's scaffolding consists of **minimal files**:

**Traditional structure** (complex):

```
pages/users/
  ├── index.tsx
  ├── [id].tsx
  ├── new.tsx
  └── [id]/edit.tsx
components/users/
  ├── UserTable.tsx
  ├── UserDetail.tsx
  └── UserForm.tsx
```

**Sonamu structure** (simple):

```
admin/users/
  ├── index.tsx    # Includes table
  └── form.tsx     # Create/Edit integrated
```

**Benefits of integrated structure**:

* Minimized file count
* Less navigation between files
* Easy maintenance
* High code cohesion

### Routing Pattern

```
GET  /admin/users           → index.tsx (list)
GET  /admin/users/form      → form.tsx  (create)
GET  /admin/users/form?id=1 → form.tsx  (edit)
```

Create/edit mode is distinguished by URL parameters.

## Customization

### Modify After Generation

Generated files are fully modifiable.

```typescript theme={null}
// web/src/routes/admin/users/index.tsx (customized)
export default function UsersPage() {
  // Added: Role filter
  const [role, setRole] = useState<string | null>(null);

  const { data: users } = UserService.useUsers("A", {
    page,
    pageSize: 20,
    search,
    role, // Custom parameter
  });

  return (
    <div>
      {/* Added: Role filter UI */}
      <select value={role || ""} onChange={(e) => setRole(e.target.value || null)}>
        <option value="">All Roles</option>
        <option value="admin">Admin</option>
        <option value="user">User</option>
      </select>

      {/* Existing table... */}
    </div>
  );
}
```

### Separate into Components

You can manually separate components if needed:

```typescript theme={null}
// components/users/UserTable.tsx (manually created)
export function UserTable({ users }: { users: User[] }) {
  return (
    <table className="data-table">
      {/* Table content */}
    </table>
  );
}

// admin/users/index.tsx (modified)
import { UserTable } from "@/components/users/UserTable";

export default function UsersPage() {
  const { data: users } = UserService.useUsers("A", { page, pageSize: 20 });

  return (
    <div>
      <UserTable users={users || []} />
    </div>
  );
}
```

### Add Detail Page

Add detail page manually since it's not auto-generated:

```typescript theme={null}
// admin/users/[id].tsx (manually created)
import { useParams } from "react-router-dom";
import { UserService } from "@/services/services.generated";

export default function UserDetailPage() {
  const { id } = useParams();
  const { data: user } = UserService.useUser("C", parseInt(id!));

  return (
    <div>
      <h1>{user?.username}</h1>
      <p>{user?.email}</p>
      <p>{user?.bio}</p>
    </div>
  );
}
```

## Best Practices

### 1. Git Commit Immediately After Generation

```bash theme={null}
# Generate views in Sonamu UI
git add web/src/routes/admin/users/
git commit -m "feat: scaffold User views"
```

This allows tracking customization history.

### 2. Extract Common Logic into Hooks

```typescript theme={null}
// hooks/useEntityList.ts
export function useEntityList<T>(entityName: string, useHook: any) {
  const [page, setPage] = useState(1);
  const [search, setSearch] = useState("");

  const { data, isLoading } = useHook("A", {
    page,
    pageSize: 20,
    search,
  });

  return { data, isLoading, page, setPage, search, setSearch };
}
```

### 3. Manage Styles Globally

```css theme={null}
/* styles/admin.css */
.admin-page {
}
.data-table {
}
.entity-form {
}
```

Use consistent styles across all admin pages.

## Cautions

<Warning>
  **Cautions when using View Scaffolding**: 1. **Only available via web UI** (CLI commands disabled)
  2\. **Only index.tsx, form.tsx generated** (no separate components) 3. Detail page needs to be
  **added manually** 4. Regenerating will **lose customizations** (Git commit required) 5.
  **Consider separating components** as needed after generation
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="Custom Components" icon="sliders" href="/en/frontend-integration/react-components/custom-components">
    Component customization
  </Card>

  <Card title="ID Async Select" icon="list-dropdown" href="/en/frontend-integration/react-components/id-async-select">
    Relational data selection
  </Card>

  <Card title="Search Input" icon="magnifying-glass" href="/en/frontend-integration/react-components/search-input">
    Search component
  </Card>

  <Card title="Using Services" icon="code" href="/en/frontend-integration/generated-services/using-services">
    Service integration
  </Card>
</CardGroup>
