# Data Fetching Guide

Complete guide to fetching data from your Laravel backend using vue-mc collections and models.

## Overview

The AwesCode UI framework uses **vue-mc** for data management, which provides:
- **Collections** - For lists of items with pagination
- **Models** - For single resources
- **Automatic API communication** - Built-in axios integration
- **Validation** - Client and server-side validation
- **Lifecycle hooks** - Before/after fetch, save, delete

## Collection Auto-Fetching with AwTableBuilder

### The Simple Way

AwTableBuilder automatically fetches collection data - no manual fetching needed!

```markup
<template>
    <AwPage title="Customers">
        <AwTableBuilder :collection="customers">
            <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
            })
        }
    }
    // No fetch() or mounted() needed!
}
</script>
```

**What happens:**
1. ✅ AwTableBuilder mounts
2. ✅ Automatically calls `customers.fetch()`
3. ✅ Shows loading state
4. ✅ Displays data when loaded
5. ✅ Handles pagination automatically

### ⚠️ Important: Don't Use v-if for Loading

```markup
<!-- ❌ BAD - Prevents AwTableBuilder from mounting -->
<template>
    <AwPage title="Customers">
        <div v-if="loading">Loading...</div>
        <AwTableBuilder v-else :collection="customers">
            <!-- Never mounts if loading=true initially -->
        </AwTableBuilder>
    </AwPage>
</template>

<!-- ✅ GOOD - Let AwTableBuilder handle loading -->
<template>
    <AwPage title="Customers">
        <AwTableBuilder :collection="customers">
            <!-- Mounts immediately, shows internal loading state -->
        </AwTableBuilder>
    </AwPage>
</template>
```

### With Search Parameters

AwTableBuilder watches for route query changes:

```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" />
        </AwTableBuilder>
    </AwPage>
</template>
```

**How it works:**
1. User types in AwSearch
2. Updates `$route.query.search`
3. AwTableBuilder detects change
4. Automatically refetches collection

### With Filter Options

Pass additional options to collection:

```markup
<template>
    <AwPage title="Orders">
        <AwSelect
            v-model="statusFilter"
            :options="['all', 'pending', 'completed']"
            label="Status"
        />

        <AwTableBuilder
            :collection="orders"
            :options="filterOptions"
        >
            <AwTableCol field="id" title="Order #" />
        </AwTableBuilder>
    </AwPage>
</template>

<script>
export default {
    data() {
        return {
            statusFilter: 'all'
        }
    },

    computed: {
        filterOptions() {
            return {
                shop_uuid: this.$route.params.shop_uuid,
                status: this.statusFilter !== 'all' ? this.statusFilter : undefined
            }
        }
    },

    watch: {
        filterOptions: {
            handler() {
                this.orders.fetch()
            },
            deep: true
        }
    }
}
</script>
```

## Manual Collection Fetching

### Basic Manual Fetch

For cases where you need manual control:

```markup
<template>
    <AwPage title="Analytics">
        <AwContentPlaceholder v-if="loading" type="text" :lines="6" />

        <div v-else>
            <div v-for="item in analytics.models" :key="item.id">
                {{ item.name }}
            </div>
        </div>
    </AwPage>
</template>

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

export default {
    data() {
        return {
            analytics: new Analytics([], {
                shop_uuid: this.$route.params.shop_uuid
            }),
            loading: true
        }
    },

    async mounted() {
        await this.loadAnalytics()
    },

    methods: {
        async loadAnalytics() {
            this.loading = true
            try {
                await this.analytics.fetch()
            } catch (error) {
                this.$notify({
                    message: 'Failed to load analytics',
                    type: 'error'
                })
            } finally {
                this.loading = false
            }
        }
    }
}
</script>
```

### With Parameters

Pass query parameters to fetch:

```javascript
async loadData() {
    await this.collection.fetch({
        params: {
            start_date: this.dateRange.start,
            end_date: this.dateRange.end,
            category: this.selectedCategory
        }
    })
}
```

### Refetching After Changes

Refetch collection after mutations:

```javascript
async deleteItem(item) {
    const confirmed = await this.$confirm({
        title: 'Delete Item',
        message: 'Are you sure?'
    })

    if (!confirmed) return

    try {
        await this.$axios.delete(`/api/items/${item.id}`)
        this.$notify({
            message: 'Item deleted',
            type: 'success'
        })

        // Refetch collection
        await this.items.fetch()
    } catch (error) {
        this.$notify({
            message: 'Failed to delete item',
            type: 'error'
        })
    }
}
```

## Model Fetching

### Fetch Existing Model

Load data for existing record:

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

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

    async mounted() {
        if (!this.customer.isNew()) {
            try {
                await this.customer.fetch()
            } catch (error) {
                // Handle 404
                if (error.response?.status === 404) {
                    this.$notify({
                        message: 'Customer not found',
                        type: 'error'
                    })
                    this.$router.push('/customers')
                    return
                }

                // Other errors
                this.$notify({
                    message: 'Failed to load customer',
                    type: 'error'
                })
            } finally {
                this.loading = false
            }
        } else {
            this.loading = false
        }
    }
}
</script>
```

### Conditional Fetching

Only fetch if not creating new:

```javascript
async mounted() {
    const uuid = this.$route.params.uuid

    if (uuid !== 'new') {
        this.loading = true
        try {
            await this.model.fetch()
        } catch (error) {
            this.$notify({
                message: 'Record not found',
                type: 'error'
            })
            this.$router.push('/list')
        } finally {
            this.loading = false
        }
    }
}
```

## AwSelect Async Loading

### Function Returns URL

For dropdowns with many options, use async loading:

```markup
<template>
    <AwSelect
        v-model="product.category_id"
        :options="loadCategories"
        option-label="name"
        track-by="id"
        label="Category"
    />
</template>

<script>
export default {
    methods: {
        // Return URL string for AwSelect to fetch from
        loadCategories(search) {
            const shopUuid = this.$route.params.shop_uuid
            return `/api/shops/${shopUuid}/categories?search=${search}`
        }
    }
}
</script>
```

**How it works:**
1. ✅ Pass **function** to `:options` (not array)
2. ✅ Function receives `search` parameter
3. ✅ Returns URL string
4. ✅ AwSelect makes GET request automatically
5. ✅ Autocomplete behavior as user types

### With Custom Label Formatting

```markup
<AwSelect
    v-model="campaign.template_key"
    :options="loadTemplates"
    :option-label="formatTemplateLabel"
    track-by="template_key"
    label="Template"
/>

<script>
export default {
    methods: {
        loadTemplates(search) {
            return `/api/templates?search=${search}`
        },

        formatTemplateLabel(template) {
            return `${template.name} (${template.channel})`
        }
    }
}
</script>
```

## Pagination

### Automatic with AwTableBuilder

AwTableBuilder handles pagination automatically:

```markup
<AwTableBuilder :collection="customers">
    <AwTableCol field="name" title="Name" />
</AwTableBuilder>
```

Collection response should include pagination meta:

```json
{
    "data": [...],
    "meta": {
        "current_page": 1,
        "last_page": 10,
        "per_page": 15,
        "total": 145
    }
}
```

### Manual Pagination

Use AwPagination component:

```markup
<template>
    <div>
        <div v-for="item in items.models" :key="item.id">
            {{ item.name }}
        </div>

        <AwPagination
            v-model="page"
            :total-pages="totalPages"
            @input="loadPage"
        />
    </div>
</template>

<script>
export default {
    data() {
        return {
            page: 1,
            totalPages: 1
        }
    },

    methods: {
        async loadPage(page) {
            await this.items.fetch({
                params: { page }
            })

            // Update total pages from response
            this.totalPages = this.items.response?.meta?.last_page || 1
        }
    }
}
</script>
```

## Error Handling

### Collection Fetch Errors

```javascript
async loadData() {
    try {
        await this.collection.fetch()
    } catch (error) {
        console.error('Fetch failed:', error)

        // Network error
        if (!error.response) {
            this.$notify({
                message: 'Network error. Please check your connection.',
                type: 'error'
            })
            return
        }

        // 404 - Not found
        if (error.response.status === 404) {
            this.$notify({
                message: 'Resource not found',
                type: 'error'
            })
            this.$router.push('/dashboard')
            return
        }

        // 403 - Forbidden
        if (error.response.status === 403) {
            this.$notify({
                message: 'You do not have permission to view this',
                type: 'error'
            })
            return
        }

        // Generic error
        this.$notify({
            message: 'Failed to load data',
            type: 'error'
        })
    }
}
```

### Model Fetch Errors

```javascript
async mounted() {
    if (!this.model.isNew()) {
        try {
            await this.model.fetch()
        } catch (error) {
            if (error.response?.status === 404) {
                this.$notify({
                    message: 'Record not found',
                    type: 'error'
                })
                this.$router.push('/list')
            } else {
                this.$notify({
                    message: 'Failed to load record',
                    type: 'error'
                })
            }
        }
    }
}
```

## Caching

### Simple Cache

Cache data to avoid unnecessary requests:

```javascript
export default {
    data() {
        return {
            cachedData: null
        }
    },

    methods: {
        async loadData() {
            // Return cached data if available
            if (this.cachedData) {
                return this.cachedData
            }

            // Fetch and cache
            const { data } = await this.$axios.get('/api/data')
            this.cachedData = data
            return data
        }
    }
}
```

### Vuex Store Cache

```javascript
// store/customers.js
export const state = () => ({
    customers: [],
    loaded: false
})

export const actions = {
    async fetch({ commit, state }) {
        // Return cached if already loaded
        if (state.loaded) {
            return state.customers
        }

        const { data } = await this.$axios.get('/api/customers')
        commit('SET_CUSTOMERS', data)
        commit('SET_LOADED', true)
        return data
    }
}

export const mutations = {
    SET_CUSTOMERS(state, customers) {
        state.customers = customers
    },
    SET_LOADED(state, loaded) {
        state.loaded = loaded
    }
}
```

## Polling for Updates

### Interval Polling

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

    mounted() {
        // Initial fetch
        this.loadData()

        // Poll every 30 seconds
        this.pollingInterval = setInterval(() => {
            this.loadData()
        }, 30000)
    },

    beforeDestroy() {
        // Clean up interval
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval)
        }
    },

    methods: {
        async loadData() {
            await this.collection.fetch()
        }
    }
}
</script>
```

### Conditional Polling

Only poll when page is visible:

```javascript
mounted() {
    // Start polling
    this.startPolling()

    // Stop polling when page hidden
    document.addEventListener('visibilitychange', this.handleVisibilityChange)
},

beforeDestroy() {
    this.stopPolling()
    document.removeEventListener('visibilitychange', this.handleVisibilityChange)
},

methods: {
    startPolling() {
        this.loadData()
        this.pollingInterval = setInterval(() => {
            if (!document.hidden) {
                this.loadData()
            }
        }, 30000)
    },

    stopPolling() {
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval)
        }
    },

    handleVisibilityChange() {
        if (document.hidden) {
            this.stopPolling()
        } else {
            this.startPolling()
        }
    }
}
```

## Loading States

### Collection Loading

Vue-mc collections provide `loading` state. Use `AwContentPlaceholder` for better UX:

```markup
<template>
    <div>
        <AwContentPlaceholder v-if="customers.loading" type="text" :lines="8" />

        <div v-else>
            <div v-for="customer in customers.models" :key="customer.id">
                {{ customer.name }}
            </div>
        </div>
    </div>
</template>
```

### Model Loading

Models provide `fetching` state. Use form placeholder for form data. When using `AwCard`, place the placeholder inside the card to replace the content:

```markup
<template>
    <AwCard title="Customer Details">
        <AwContentPlaceholder v-if="customer.fetching" type="form" :lines="6" />

        <AwGrid v-else>
            <!-- Customer data -->
        </AwGrid>
    </AwCard>
</template>
```

### Complex Content Loading

For pages with mixed content types, use multiple placeholders:

```markup
<template>
    <div v-if="loading">
        <AwGrid :col="{ md: 2 }">
            <AwContentPlaceholder type="image" />
            <AwContentPlaceholder type="text" :lines="6" />
        </AwGrid>
        <AwFlow class="mt-4">
            <AwContentPlaceholder type="avatar" />
            <AwContentPlaceholder type="text" :lines="3" />
        </AwFlow>
    </div>

    <div v-else>
        <!-- Actual content -->
    </div>
</template>
```

### Custom Loading Flag

For manual control:

```markup
<template>
    <AwContentPlaceholder v-if="loading" type="form" :lines="4" />

    <AwForm v-else url="/api/submit">
        <!-- Form fields -->
    </AwForm>
</template>

<script>
export default {
    data() {
        return {
            loading: false
        }
    },

    methods: {
        async loadData() {
            this.loading = true
            try {
                await this.collection.fetch()
            } finally {
                this.loading = false
            }
        }
    }
}
</script>
```

## Best Practices

### 1. Use AwTableBuilder Auto-Fetch

```markup
<!-- ✅ GOOD - Let AwTableBuilder fetch -->
<AwTableBuilder :collection="customers" />

<!-- ❌ BAD - Manual fetch not needed -->
<script>
async mounted() {
    await this.customers.fetch()  // Unnecessary!
}
</script>
```

### 2. Always Include shop_uuid

```javascript
// ✅ GOOD
customers: new Customers([], {
    shop_uuid: this.$route.params.shop_uuid
})

// ❌ BAD
customers: new Customers([])
```

### 3. Handle Errors

```javascript
// ✅ GOOD
try {
    await this.model.fetch()
} catch (error) {
    this.$notify({
        message: 'Failed to load',
        type: 'error'
    })
}

// ❌ BAD
await this.model.fetch()  // No error handling
```

### 4. Check isNew() Before Fetching

```javascript
// ✅ GOOD
if (!this.model.isNew()) {
    await this.model.fetch()
}

// ❌ BAD
await this.model.fetch()  // Fails if new model
```

### 5. Clean Up Intervals

```javascript
// ✅ GOOD
beforeDestroy() {
    clearInterval(this.pollingInterval)
}

// ❌ BAD
// No cleanup - memory leak
```

## See Also

- [Best Practices Guide](./best-practices.md) - Data management patterns
- [Error Handling Guide](./error-handling.md) - Error handling strategies
- [List Pages Guide](./page-patterns/list-pages.md) - List page patterns
- [Vue-MC Documentation](../../vue-mc/docs/) - Complete vue-mc API
