# Common Patterns

Standard application patterns you'll use in most AwesCode UI applications.

## Table of Contents

- [List & Detail Pattern](#list--detail-pattern)
- [Create/Edit Pattern](#createedit-pattern)
- [Search & Filter Pattern](#search--filter-pattern)
- [Bulk Actions Pattern](#bulk-actions-pattern)
- [Dashboard Layout Pattern](#dashboard-layout-pattern)
- [Navigation Patterns](#navigation-patterns)

## List & Detail Pattern

Browse a list of items, click to view details, navigate to edit.

### List Page

```markup
<template>
    <AwPage title="Customers">
        <template #buttons>
            <AwButton
                :href="`/shops/${$route.params.shop_uuid}/customers/new`"
                color="accent"
            >
                Add Customer
            </AwButton>
        </template>

        <AwTableBuilder
            :collection="customers"
            @click:row="viewCustomer"
        >
            <AwTableCol field="name" title="Name" />
            <AwTableCol field="email" title="Email" />
            <AwTableCol field="phone" title="Phone" />
            <AwTableCol field="created_at" title="Created">
                <template #default="{ cell }">
                    {{ $dayjs(cell).format('ll') }}
                </template>
            </AwTableCol>
        </AwTableBuilder>
    </AwPage>
</template>

<script>
import Customers from '~/collections/Customers'

export default {
    data() {
        return {
            customers: new Customers([], {
                shop_uuid: this.$route.params.shop_uuid
            })
        }
    },

    mounted() {
        // Set back route for detail pages
        this.$router.setBack(`/shops/${this.$route.params.shop_uuid}/customers`)
    },

    methods: {
        viewCustomer(customer) {
            this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers/${customer.id}`)
        }
    }
}
</script>
```

### Detail/View Page

```markup
<template>
    <AwPageSingle
        hide-menu
        :title="customer.name"
    >
        <template #buttons>
            <AwButton
                :href="`/shops/${$route.params.shop_uuid}/customers/${customer.id}/edit`"
                color="accent"
            >
                Edit
            </AwButton>
        </template>

        <div v-if="loading" class="py-12 text-center">
            <AwProgress indeterminate />
        </div>

        <div v-else>
            <AwCard title="Customer Information">
                <AwGrid :col="2">
                    <div>
                        <AwDescription>Name</AwDescription>
                        <p class="font-medium">{{ customer.name }}</p>
                    </div>

                    <div>
                        <AwDescription>Email</AwDescription>
                        <p class="font-medium">{{ customer.email }}</p>
                    </div>

                    <div>
                        <AwDescription>Phone</AwDescription>
                        <p class="font-medium">{{ customer.phone }}</p>
                    </div>

                    <div>
                        <AwDescription>Member Since</AwDescription>
                        <p class="font-medium">{{ $dayjs(customer.created_at).format('LL') }}</p>
                    </div>
                </AwGrid>
            </AwCard>

            <AwCard title="Recent Orders" class="mt-6">
                <AwTableBuilder
                    :collection="orders"
                    @click:row="viewOrder"
                >
                    <AwTableCol field="number" title="Order #" />
                    <AwTableCol field="total" title="Total" />
                    <AwTableCol field="status" title="Status" />
                    <AwTableCol field="created_at" title="Date">
                        <template #default="{ cell }">
                            {{ $dayjs(cell).format('ll') }}
                        </template>
                    </AwTableCol>
                </AwTableBuilder>
            </AwCard>
        </div>
    </AwPageSingle>
</template>

<script>
import Customer from '~/models/Customer'
import Orders from '~/collections/Orders'

export default {
    data() {
        return {
            customer: new Customer(
                { id: this.$route.params.id },
                null,
                { shop_uuid: this.$route.params.shop_uuid }
            ),
            orders: new Orders([], {
                shop_uuid: this.$route.params.shop_uuid,
                customer_id: this.$route.params.id
            }),
            loading: true
        }
    },

    async mounted() {
        try {
            await this.customer.fetch()
        } catch (error) {
            if (error.response?.status === 404) {
                this.$notify({
                    message: 'Customer not found',
                    type: 'error'
                })
                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
            }
        } finally {
            this.loading = false
        }
    },

    methods: {
        viewOrder(order) {
            this.$router.push(`/shops/${this.$route.params.shop_uuid}/orders/${order.id}`)
        }
    }
}
</script>
```

## Create/Edit Pattern

Form that handles both creating new records and editing existing ones.

### Single Form for Create/Edit

```markup
<template>
    <AwPageSingle
        hide-menu
        :title="customer.isNew() ? 'New Customer' : 'Edit Customer'"
    >
        <template #buttons>
            <AwButton
                :loading="saving"
                color="accent"
                @click="save"
            >
                Save
            </AwButton>
        </template>

        <div v-if="loading" class="py-12 text-center">
            <AwProgress indeterminate />
        </div>

        <AwCard v-else title="Customer Information">
            <AwInput
                v-model="customer.name"
                :error="customer.errors.name"
                label="Name"
                required
            />

            <AwInput
                v-model="customer.email"
                :error="customer.errors.email"
                label="Email"
                type="email"
                required
            />

            <AwTel
                v-model="customer.phone"
                :error="customer.errors.phone"
                label="Phone"
            />

            <AwAddress
                v-model="customer.address"
                :error="customer.errors.address"
                label="Address"
            />
        </AwCard>
    </AwPageSingle>
</template>

<script>
import Customer from '~/models/Customer'

export default {
    data() {
        const uuid = this.$route.params.uuid

        return {
            customer: new Customer(
                uuid === 'new' ? {} : { id: uuid },
                null,
                { shop_uuid: this.$route.params.shop_uuid }
            ),
            loading: uuid !== 'new',
            saving: false
        }
    },

    async mounted() {
        if (!this.customer.isNew()) {
            try {
                await this.customer.fetch()
            } catch (error) {
                if (error.response?.status === 404) {
                    this.$notify({
                        message: 'Customer not found',
                        type: 'error'
                    })
                } else {
                    this.$notify({
                        message: 'Failed to load customer',
                        type: 'error'
                    })
                }
                this.$router.push(`/shops/${this.$route.params.shop_uuid}/customers`)
            } finally {
                this.loading = false
            }
        }
    },

    methods: {
        async save() {
            this.saving = true

            try {
                await this.customer.save()

                if (Object.keys(this.customer.errors).length > 0) {
                    this.$notify({
                        message: 'Please fix validation errors',
                        type: 'error'
                    })
                    return
                }

                this.$notify({
                    message: 'Customer saved',
                    type: 'success'
                })

                this.$router.pushBack(`/shops/${this.$route.params.shop_uuid}/customers`)
            } catch (error) {
                this.$notify({
                    message: 'Failed to save customer',
                    type: 'error'
                })
            } finally {
                this.saving = false
            }
        },

        cancel() {
            this.$router.pushBack(`/shops/${this.$route.params.shop_uuid}/customers`)
        }
    }
}
</script>
```

### With Confirmation for Unsaved Changes

```markup
<script>
export default {
    data() {
        return {
            initialData: null
        }
    },

    computed: {
        hasUnsavedChanges() {
            if (!this.initialData) return false
            return JSON.stringify(this.customer.attributes) !== JSON.stringify(this.initialData)
        }
    },

    async mounted() {
        if (!this.customer.isNew()) {
            await this.customer.fetch()
            // Store initial data for comparison
            this.initialData = JSON.parse(JSON.stringify(this.customer.attributes))
        } else {
            this.initialData = {}
        }
    },

    async beforeRouteLeave(to, from, next) {
        if (this.hasUnsavedChanges) {
            const confirmed = await this.$confirm({
                title: 'Unsaved Changes',
                message: 'You have unsaved changes. Are you sure you want to leave?',
                confirmText: 'Leave',
                cancelText: 'Stay'
            })

            if (confirmed) {
                next()
            } else {
                next(false)
            }
        } else {
            next()
        }
    }
}
</script>
```

## Search & Filter Pattern

Add search and filtering to list pages.

### With Search

```markup
<template>
    <AwPage title="Customers">
        <div class="flex justify-end mb-6">
            <AwSearch class="w-full lg:w-auto" />
        </div>

        <AwTableBuilder
            :collection="customers"
            :watch-params="['search']"
        >
            <AwTableCol field="name" title="Name" />
            <AwTableCol field="email" title="Email" />
        </AwTableBuilder>
    </AwPage>
</template>

<script>
import Customers from '~/collections/Customers'

export default {
    data() {
        return {
            customers: new Customers([], {
                shop_uuid: this.$route.params.shop_uuid
            })
        }
    }
}
</script>
```

**How it works:**
1. User types in AwSearch
2. Updates `$route.query.search`
3. AwTableBuilder detects change via `:watch-params`
4. Automatically refetches collection with new search param

### With Filters

```markup
<template>
    <AwPage title="Orders">
        <AwCard class="mb-6">
            <AwGrid :col="3">
                <AwSelect
                    v-model="filters.status"
                    :options="statusOptions"
                    label="Status"
                />

                <AwSelect
                    v-model="filters.payment_method"
                    :options="paymentOptions"
                    label="Payment Method"
                />

                <AwDate
                    v-model="filters.date_from"
                    label="From Date"
                />
            </AwGrid>

            <AwFlow justify="end" class="mt-4">
                <AwButton @click="applyFilters" color="accent">
                    Apply Filters
                </AwButton>

                <AwButton @click="resetFilters">
                    Reset
                </AwButton>
            </AwFlow>
        </AwCard>

        <AwTableBuilder :collection="orders">
            <AwTableCol field="number" title="Order #" />
            <AwTableCol field="customer_name" title="Customer" />
            <AwTableCol field="total" title="Total" />
            <AwTableCol field="status" title="Status" />
        </AwTableBuilder>
    </AwPage>
</template>

<script>
import Orders from '~/collections/Orders'

export default {
    data() {
        return {
            orders: new Orders([], {
                shop_uuid: this.$route.params.shop_uuid
            }),
            filters: {
                status: null,
                payment_method: null,
                date_from: null
            },
            statusOptions: ['all', 'pending', 'processing', 'completed', 'cancelled'],
            paymentOptions: ['all', 'card', 'cash', 'bank_transfer']
        }
    },

    methods: {
        applyFilters() {
            // Build filter options, excluding 'all' and null values
            const options = {
                shop_uuid: this.$route.params.shop_uuid
            }

            if (this.filters.status && this.filters.status !== 'all') {
                options.status = this.filters.status
            }

            if (this.filters.payment_method && this.filters.payment_method !== 'all') {
                options.payment_method = this.filters.payment_method
            }

            if (this.filters.date_from) {
                options.date_from = this.filters.date_from
            }

            // Update collection options and refetch
            this.orders.setOptions(options)
            this.orders.fetch()
        },

        resetFilters() {
            this.filters = {
                status: null,
                payment_method: null,
                date_from: null
            }

            this.orders.setOptions({
                shop_uuid: this.$route.params.shop_uuid
            })
            this.orders.fetch()
        }
    }
}
</script>
```

## Bulk Actions Pattern

Select multiple items and perform batch operations.

### With Selection and Bulk Delete

```markup
<template>
    <AwPage title="Customers">
        <template #buttons>
            <AwButton
                v-if="selectedIds.length > 0"
                @click="bulkDelete"
                color="error"
            >
                Delete {{ selectedIds.length }} Selected
            </AwButton>

            <AwButton
                :href="`/shops/${$route.params.shop_uuid}/customers/new`"
                color="accent"
            >
                Add Customer
            </AwButton>
        </template>

        <AwTableBuilder :collection="customers">
            <template #select="{ model }">
                <AwCheckbox
                    :value="selectedIds.includes(model.id)"
                    @input="toggleSelection(model.id)"
                />
            </template>

            <AwTableCol field="name" title="Name" />
            <AwTableCol field="email" title="Email" />
            <AwTableCol field="phone" title="Phone" />
        </AwTableBuilder>
    </AwPage>
</template>

<script>
import Customers from '~/collections/Customers'

export default {
    data() {
        return {
            customers: new Customers([], {
                shop_uuid: this.$route.params.shop_uuid
            }),
            selectedIds: []
        }
    },

    methods: {
        toggleSelection(id) {
            const index = this.selectedIds.indexOf(id)
            if (index > -1) {
                this.selectedIds.splice(index, 1)
            } else {
                this.selectedIds.push(id)
            }
        },

        async bulkDelete() {
            const confirmed = await this.$confirm({
                title: 'Delete Customers',
                message: `Are you sure you want to delete ${this.selectedIds.length} customers?`
            })

            if (!confirmed) return

            try {
                await this.$axios.post('/api/customers/bulk-delete', {
                    shop_uuid: this.$route.params.shop_uuid,
                    ids: this.selectedIds
                })

                this.$notify({
                    message: `${this.selectedIds.length} customers deleted`,
                    type: 'success'
                })

                this.selectedIds = []
                await this.customers.fetch()
            } catch (error) {
                this.$notify({
                    message: 'Failed to delete customers',
                    type: 'error'
                })
            }
        }
    }
}
</script>
```

### With Select All

```markup
<script>
export default {
    data() {
        return {
            selectedIds: [],
            selectAll: false
        }
    },

    watch: {
        selectAll(value) {
            if (value) {
                this.selectedIds = this.customers.models.map(c => c.id)
            } else {
                this.selectedIds = []
            }
        }
    }
}
</script>

<template>
    <AwTableBuilder :collection="customers">
        <template #head>
            <AwCheckbox
                v-model="selectAll"
                label="Select All"
            />
        </template>

        <template #select="{ model }">
            <AwCheckbox
                :value="selectedIds.includes(model.id)"
                @input="toggleSelection(model.id)"
            />
        </template>
    </AwTableBuilder>
</template>
```

## Dashboard Layout Pattern

Standard dashboard with metrics, charts, and recent activity.

### Complete Dashboard

```markup
<template>
    <AwPage title="Dashboard">
        <!-- Metric Cards -->
        <AwGrid :col="{ default: 1, sm: 2, lg: 4 }" class="gap-6">
            <AwCard>
                <AwFlow align="center" justify="between">
                    <div>
                        <AwDescription>Total Revenue</AwDescription>
                        <div class="text-3xl font-bold">
                            ${{ formatNumber(metrics.revenue) }}
                        </div>
                        <div class="text-sm text-green-600 mt-1">
                            +{{ metrics.revenueGrowth }}% from last month
                        </div>
                    </div>
                    <AwIcon name="dollar-sign" class="text-4xl text-green-600" />
                </AwFlow>
            </AwCard>

            <AwCard>
                <AwFlow align="center" justify="between">
                    <div>
                        <AwDescription>Orders</AwDescription>
                        <div class="text-3xl font-bold">
                            {{ formatNumber(metrics.orders) }}
                        </div>
                        <div class="text-sm text-blue-600 mt-1">
                            +{{ metrics.ordersGrowth }}% from last month
                        </div>
                    </div>
                    <AwIcon name="shopping-cart" class="text-4xl text-blue-600" />
                </AwFlow>
            </AwCard>

            <AwCard>
                <AwFlow align="center" justify="between">
                    <div>
                        <AwDescription>Customers</AwDescription>
                        <div class="text-3xl font-bold">
                            {{ formatNumber(metrics.customers) }}
                        </div>
                        <div class="text-sm text-purple-600 mt-1">
                            +{{ metrics.customersGrowth }}% from last month
                        </div>
                    </div>
                    <AwIcon name="users" class="text-4xl text-purple-600" />
                </AwFlow>
            </AwCard>

            <AwCard>
                <AwFlow align="center" justify="between">
                    <div>
                        <AwDescription>Conversion Rate</AwDescription>
                        <div class="text-3xl font-bold">
                            {{ metrics.conversionRate }}%
                        </div>
                        <div class="text-sm text-orange-600 mt-1">
                            +{{ metrics.conversionGrowth }}% from last month
                        </div>
                    </div>
                    <AwIcon name="trending-up" class="text-4xl text-orange-600" />
                </AwFlow>
            </AwCard>
        </AwGrid>

        <!-- Charts -->
        <AwGrid :col="{ default: 1, lg: 2 }" class="gap-6 mt-6">
            <AwCard title="Revenue Over Time">
                <AwChart
                    :data="revenueChartData"
                    type="line"
                    :options="chartOptions"
                />
            </AwCard>

            <AwCard title="Top Products">
                <AwChart
                    :data="productsChartData"
                    type="bar"
                    :options="chartOptions"
                />
            </AwCard>
        </AwGrid>

        <!-- Recent Activity -->
        <AwCard title="Recent Orders" class="mt-6">
            <AwTableBuilder
                :collection="recentOrders"
                @click:row="viewOrder"
            >
                <AwTableCol field="number" title="Order #" />
                <AwTableCol field="customer_name" title="Customer" />
                <AwTableCol field="total" title="Total">
                    <template #default="{ cell }">
                        ${{ cell.toFixed(2) }}
                    </template>
                </AwTableCol>
                <AwTableCol field="status" title="Status">
                    <template #default="{ cell }">
                        <AwBadge :color="getStatusColor(cell)">
                            {{ cell }}
                        </AwBadge>
                    </template>
                </AwTableCol>
                <AwTableCol field="created_at" title="Date">
                    <template #default="{ cell }">
                        {{ $dayjs(cell).format('ll') }}
                    </template>
                </AwTableCol>
            </AwTableBuilder>
        </AwCard>
    </AwPage>
</template>

<script>
import Orders from '~/collections/Orders'

export default {
    data() {
        return {
            metrics: {
                revenue: 0,
                revenueGrowth: 0,
                orders: 0,
                ordersGrowth: 0,
                customers: 0,
                customersGrowth: 0,
                conversionRate: 0,
                conversionGrowth: 0
            },
            recentOrders: new Orders([], {
                shop_uuid: this.$route.params.shop_uuid,
                limit: 10,
                sort: '-created_at'
            }),
            revenueChartData: [],
            productsChartData: [],
            chartOptions: {
                responsive: true,
                maintainAspectRatio: false
            }
        }
    },

    async mounted() {
        await Promise.all([
            this.loadMetrics(),
            this.loadChartData()
        ])
    },

    methods: {
        async loadMetrics() {
            try {
                const { data } = await this.$axios.get('/api/dashboard/metrics', {
                    params: {
                        shop_uuid: this.$route.params.shop_uuid
                    }
                })
                this.metrics = data
            } catch (error) {
                this.$notify({
                    message: 'Failed to load metrics',
                    type: 'error'
                })
            }
        },

        async loadChartData() {
            try {
                const { data } = await this.$axios.get('/api/dashboard/charts', {
                    params: {
                        shop_uuid: this.$route.params.shop_uuid
                    }
                })
                this.revenueChartData = data.revenue
                this.productsChartData = data.products
            } catch (error) {
                this.$notify({
                    message: 'Failed to load chart data',
                    type: 'error'
                })
            }
        },

        formatNumber(num) {
            return new Intl.NumberFormat().format(num)
        },

        getStatusColor(status) {
            const colors = {
                pending: 'warning',
                processing: 'info',
                completed: 'success',
                cancelled: 'error'
            }
            return colors[status] || 'mono'
        },

        viewOrder(order) {
            this.$router.push(`/shops/${this.$route.params.shop_uuid}/orders/${order.id}`)
        }
    }
}
</script>
```

## Navigation Patterns

### Breadcrumb Navigation

```markup
<template>
    <AwPageSingle
        hide-menu
        title="Edit Product"
    >
        <!-- Mobile breadcrumbs (auto-shown on mobile) -->
        <template #breadcrumbs>
            <AwLink :href="`/shops/${$route.params.shop_uuid}/dashboard`">
                Dashboard
            </AwLink>
            <AwLink :href="`/shops/${$route.params.shop_uuid}/products`">
                Products
            </AwLink>
            <span>Edit</span>
        </template>

        <!-- Content -->
    </AwPageSingle>
</template>
```

### Tab Navigation

```markup
<template>
    <AwPage title="Settings">
        <AwTabNav
            v-model="activeTab"
            :items="tabs"
        />

        <AwCard v-show="activeTab === 'general'" class="mt-6">
            <GeneralSettings />
        </AwCard>

        <AwCard v-show="activeTab === 'billing'" class="mt-6">
            <BillingSettings />
        </AwCard>

        <AwCard v-show="activeTab === 'notifications'" class="mt-6">
            <NotificationSettings />
        </AwCard>
    </AwPage>
</template>

<script>
export default {
    data() {
        return {
            activeTab: 'general',
            tabs: [
                { value: 'general', text: 'General' },
                { value: 'billing', text: 'Billing' },
                { value: 'notifications', text: 'Notifications' }
            ]
        }
    }
}
</script>
```

### Back Button

```markup
<script>
export default {
    mounted() {
        // On list page - set where back should go
        this.$router.setBack(`/shops/${this.$route.params.shop_uuid}/customers`)
    },

    methods: {
        cancel() {
            // Navigate back
            this.$router.pushBack()
        }
    }
}
</script>
```

## See Also

- [Advanced Patterns](./advanced-patterns.md) - Complex application patterns
- [Page Patterns](../guides/page-patterns/) - Detailed page pattern guides
- [Best Practices](../guides/best-practices.md) - Framework best practices
- [Component Cookbook](./index.md) - Additional UI recipes
