# Work Plan - October 22, 2025 (Updated)
## Jinja2 Migration: Vendor & Users Pages

**Current Status:** Core migration complete ✅ | Icons fixed ✅ | Logout working ✅ | Dashboard complete ✅

---

## 🎯 Updated Goals

1. ✅ ~~Fix icon system and utils.js conflicts~~ **COMPLETED**
2. ✅ ~~Test and verify logout flow~~ **COMPLETED**
3. 🔄 **IN PROGRESS:** Migrate Vendors page to Jinja2
4. 🔄 **IN PROGRESS:** Create Users page with Jinja2
5. ⏳ Test and verify all functionality

**Estimated Time:** 2-3 hours

---

## 📋 Current Task List

### Priority 1: Migrate Vendors Page (HIGH) 🏪

The vendors page needs to be migrated from static HTML to Jinja2 templates, following the same pattern as the dashboard.

#### Task 1.1: Create Vendors Jinja2 Template
**File:** `app/templates/admin/vendors.html`

**Implementation Steps:**

```jinja2
{% extends "admin/base.html" %}

{% block title %}Vendors Management - LetzShop Admin{% endblock %}

{% block page_title %}Vendors Management{% endblock %}

{% block content %}
<div x-data="adminVendors()" x-init="init()">
    <!-- Header Section with Search and Actions -->
    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
        <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
            <!-- Search Bar -->
            <div class="flex-1 max-w-md">
                <div class="relative">
                    <input
                        type="text"
                        x-model="filters.search"
                        @input="debouncedSearch()"
                        placeholder="Search vendors..."
                        class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                    >
                    <div class="absolute left-3 top-2.5" x-html="$icon('search', 'w-5 h-5 text-gray-400')"></div>
                </div>
            </div>

            <!-- Filter and Action Buttons -->
            <div class="flex gap-3">
                <!-- Status Filter -->
                <select
                    x-model="filters.status"
                    @change="loadVendors()"
                    class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                >
                    <option value="">All Status</option>
                    <option value="pending">Pending</option>
                    <option value="approved">Approved</option>
                    <option value="rejected">Rejected</option>
                    <option value="suspended">Suspended</option>
                </select>

                <!-- Verification Filter -->
                <select
                    x-model="filters.verified"
                    @change="loadVendors()"
                    class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                >
                    <option value="">All</option>
                    <option value="true">Verified</option>
                    <option value="false">Not Verified</option>
                </select>

                <!-- Add Vendor Button -->
                <button
                    @click="openCreateModal()"
                    class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
                >
                    <span x-html="$icon('plus', 'w-5 h-5')"></span>
                    <span>Add Vendor</span>
                </button>
            </div>
        </div>
    </div>

    <!-- Statistics Cards -->
    <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Total Vendors</p>
                    <p class="text-2xl font-bold text-gray-900 dark:text-white" x-text="stats.total"></p>
                </div>
                <div class="p-3 bg-blue-100 dark:bg-blue-900 rounded-lg" x-html="$icon('users', 'w-8 h-8 text-blue-600 dark:text-blue-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Pending Approval</p>
                    <p class="text-2xl font-bold text-orange-600 dark:text-orange-400" x-text="stats.pending"></p>
                </div>
                <div class="p-3 bg-orange-100 dark:bg-orange-900 rounded-lg" x-html="$icon('clock', 'w-8 h-8 text-orange-600 dark:text-orange-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Approved</p>
                    <p class="text-2xl font-bold text-green-600 dark:text-green-400" x-text="stats.approved"></p>
                </div>
                <div class="p-3 bg-green-100 dark:bg-green-900 rounded-lg" x-html="$icon('check-circle', 'w-8 h-8 text-green-600 dark:text-green-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Verified</p>
                    <p class="text-2xl font-bold text-blue-600 dark:text-blue-400" x-text="stats.verified"></p>
                </div>
                <div class="p-3 bg-blue-100 dark:bg-blue-900 rounded-lg" x-html="$icon('shield-check', 'w-8 h-8 text-blue-600 dark:text-blue-400')"></div>
            </div>
        </div>
    </div>

    <!-- Vendors Table -->
    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
        <!-- Loading State -->
        <div x-show="loading" class="p-8 text-center">
            <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
            <p class="mt-2 text-gray-600 dark:text-gray-400">Loading vendors...</p>
        </div>

        <!-- Empty State -->
        <div x-show="!loading && vendors.length === 0" class="p-8 text-center">
            <div class="text-gray-400 dark:text-gray-500 mb-4" x-html="$icon('inbox', 'w-16 h-16 mx-auto')"></div>
            <p class="text-gray-600 dark:text-gray-400">No vendors found</p>
        </div>

        <!-- Vendors Table -->
        <div x-show="!loading && vendors.length > 0">
            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                <thead class="bg-gray-50 dark:bg-gray-900">
                    <tr>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Shop Info
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Owner
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Status
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Verification
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Products
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Created
                        </th>
                        <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Actions
                        </th>
                    </tr>
                </thead>
                <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
                    <template x-for="vendor in vendors" :key="vendor.id">
                        <tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
                            <!-- Shop Info -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <div class="flex items-center">
                                    <div class="flex-shrink-0 h-10 w-10 bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center">
                                        <span class="text-lg font-semibold text-gray-600 dark:text-gray-300" x-text="vendor.shop_name.charAt(0).toUpperCase()"></span>
                                    </div>
                                    <div class="ml-4">
                                        <div class="text-sm font-medium text-gray-900 dark:text-white" x-text="vendor.shop_name"></div>
                                        <div class="text-sm text-gray-500 dark:text-gray-400" x-text="vendor.shop_code"></div>
                                    </div>
                                </div>
                            </td>

                            <!-- Owner -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <div class="text-sm text-gray-900 dark:text-white" x-text="vendor.owner_name || 'N/A'"></div>
                                <div class="text-sm text-gray-500 dark:text-gray-400" x-text="vendor.contact_email"></div>
                            </td>

                            <!-- Status -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <span 
                                    class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full"
                                    :class="{
                                        'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200': vendor.status === 'pending',
                                        'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200': vendor.status === 'approved',
                                        'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200': vendor.status === 'rejected',
                                        'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300': vendor.status === 'suspended'
                                    }"
                                    x-text="vendor.status"
                                ></span>
                            </td>

                            <!-- Verification -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <div class="flex items-center">
                                    <template x-if="vendor.is_verified">
                                        <span class="flex items-center text-green-600 dark:text-green-400">
                                            <span x-html="$icon('shield-check', 'w-5 h-5 mr-1')"></span>
                                            <span class="text-sm">Verified</span>
                                        </span>
                                    </template>
                                    <template x-if="!vendor.is_verified">
                                        <span class="flex items-center text-gray-400 dark:text-gray-500">
                                            <span x-html="$icon('shield', 'w-5 h-5 mr-1')"></span>
                                            <span class="text-sm">Not Verified</span>
                                        </span>
                                    </template>
                                </div>
                            </td>

                            <!-- Products -->
                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
                                <span x-text="vendor.product_count || 0"></span>
                            </td>

                            <!-- Created -->
                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
                                <span x-text="Utils.formatDate(vendor.created_at)"></span>
                            </td>

                            <!-- Actions -->
                            <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                <div class="flex justify-end gap-2">
                                    <button
                                        @click="viewVendor(vendor)"
                                        class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
                                        title="View Details"
                                        x-html="$icon('eye', 'w-5 h-5')"
                                    ></button>
                                    <button
                                        @click="editVendor(vendor)"
                                        class="text-yellow-600 hover:text-yellow-900 dark:text-yellow-400 dark:hover:text-yellow-300"
                                        title="Edit"
                                        x-html="$icon('edit', 'w-5 h-5')"
                                    ></button>
                                    <button
                                        @click="deleteVendor(vendor)"
                                        class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
                                        title="Delete"
                                        x-html="$icon('trash', 'w-5 h-5')"
                                    ></button>
                                </div>
                            </td>
                        </tr>
                    </template>
                </tbody>
            </table>

            <!-- Pagination -->
            <div class="bg-gray-50 dark:bg-gray-900 px-6 py-4 border-t border-gray-200 dark:border-gray-700">
                <div class="flex items-center justify-between">
                    <div class="text-sm text-gray-700 dark:text-gray-300">
                        Showing <span class="font-medium" x-text="((pagination.page - 1) * pagination.per_page) + 1"></span>
                        to <span class="font-medium" x-text="Math.min(pagination.page * pagination.per_page, pagination.total)"></span>
                        of <span class="font-medium" x-text="pagination.total"></span> vendors
                    </div>
                    <div class="flex gap-2">
                        <button
                            @click="previousPage()"
                            :disabled="pagination.page === 1"
                            :class="pagination.page === 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 dark:hover:bg-gray-700'"
                            class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-700 dark:text-gray-300"
                        >
                            Previous
                        </button>
                        <button
                            @click="nextPage()"
                            :disabled="pagination.page >= pagination.pages"
                            :class="pagination.page >= pagination.pages ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 dark:hover:bg-gray-700'"
                            class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-700 dark:text-gray-300"
                        >
                            Next
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Modals will be added here -->
    <!-- Create/Edit Vendor Modal -->
    <!-- View Vendor Details Modal -->
    <!-- Delete Confirmation Modal -->
</div>
{% endblock %}

{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/vendors.js') }}"></script>
{% endblock %}
```

**Checklist:**
- [ ] Create the template file
- [ ] Verify Alpine.js data function `adminVendors()` exists in `static/admin/js/vendors.js`
- [ ] Test with sample data
- [ ] Verify all icons display correctly
- [ ] Test filters and search
- [ ] Test pagination

---

#### Task 1.2: Update Backend Route
**File:** `app/routes/admin.py` (or wherever admin routes are defined)

Ensure the route serves the Jinja2 template:

```python
from fastapi import APIRouter, Request, Depends
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

router = APIRouter()
templates = Jinja2Templates(directory="app/templates")

@router.get("/vendors", response_class=HTMLResponse)
async def admin_vendors_page(request: Request):
    """Render the admin vendors page."""
    return templates.TemplateResponse(
        "admin/vendors.html",
        {"request": request}
    )
```

**Checklist:**
- [ ] Add/update route in backend
- [ ] Test route returns correct template
- [ ] Verify authentication middleware is applied

---

#### Task 1.3: Update vendors.js Alpine Component

**File:** `static/admin/js/vendors.js`

Ensure the Alpine.js component is properly structured:

```javascript
function adminVendors() {
    return {
        // State
        vendors: [],
        loading: false,
        filters: {
            search: '',
            status: '',
            verified: ''
        },
        stats: {
            total: 0,
            pending: 0,
            approved: 0,
            verified: 0
        },
        pagination: {
            page: 1,
            per_page: 10,
            total: 0,
            pages: 0
        },

        // Initialization
        init() {
            Logger.info('Vendors page initialized', 'VENDORS');
            this.loadVendors();
            this.loadStats();
        },

        // Load vendors from API
        async loadVendors() {
            this.loading = true;
            try {
                const params = new URLSearchParams({
                    page: this.pagination.page,
                    per_page: this.pagination.per_page,
                    ...this.filters
                });

                const response = await ApiClient.get(`/admin/vendors?${params}`);
                
                if (response.items) {
                    this.vendors = response.items;
                    this.pagination.total = response.total;
                    this.pagination.pages = response.pages;
                }
            } catch (error) {
                Logger.error('Failed to load vendors', 'VENDORS', error);
                Utils.showToast('Failed to load vendors', 'error');
            } finally {
                this.loading = false;
            }
        },

        // Load statistics
        async loadStats() {
            try {
                const response = await ApiClient.get('/admin/vendors/stats');
                if (response) {
                    this.stats = response;
                }
            } catch (error) {
                Logger.error('Failed to load stats', 'VENDORS', error);
            }
        },

        // Search with debounce
        debouncedSearch: Utils.debounce(function() {
            this.pagination.page = 1;
            this.loadVendors();
        }, 500),

        // Pagination
        nextPage() {
            if (this.pagination.page < this.pagination.pages) {
                this.pagination.page++;
                this.loadVendors();
            }
        },

        previousPage() {
            if (this.pagination.page > 1) {
                this.pagination.page--;
                this.loadVendors();
            }
        },

        // Actions
        viewVendor(vendor) {
            Logger.info('View vendor', 'VENDORS', vendor);
            // TODO: Open view modal
        },

        editVendor(vendor) {
            Logger.info('Edit vendor', 'VENDORS', vendor);
            // TODO: Open edit modal
        },

        async deleteVendor(vendor) {
            if (!confirm(`Are you sure you want to delete ${vendor.shop_name}?`)) {
                return;
            }

            try {
                await ApiClient.delete(`/admin/vendors/${vendor.id}`);
                Utils.showToast('Vendor deleted successfully', 'success');
                this.loadVendors();
                this.loadStats();
            } catch (error) {
                Logger.error('Failed to delete vendor', 'VENDORS', error);
                Utils.showToast('Failed to delete vendor', 'error');
            }
        },

        openCreateModal() {
            Logger.info('Open create vendor modal', 'VENDORS');
            // TODO: Open create modal
        }
    };
}
```

**Checklist:**
- [ ] Update/create vendors.js file
- [ ] Test all functions work correctly
- [ ] Verify API endpoints exist
- [ ] Test error handling

---

### Priority 2: Create Users Page (HIGH) 👥

The users page needs to be created from scratch following the same pattern.

#### Task 2.1: Create Users Jinja2 Template
**File:** `app/templates/admin/users.html`

**Implementation Steps:**

```jinja2
{% extends "admin/base.html" %}

{% block title %}Users Management - LetzShop Admin{% endblock %}

{% block page_title %}Users Management{% endblock %}

{% block content %}
<div x-data="adminUsers()" x-init="init()">
    <!-- Header Section with Search and Actions -->
    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
        <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
            <!-- Search Bar -->
            <div class="flex-1 max-w-md">
                <div class="relative">
                    <input
                        type="text"
                        x-model="filters.search"
                        @input="debouncedSearch()"
                        placeholder="Search users by name, email, or username..."
                        class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                    >
                    <div class="absolute left-3 top-2.5" x-html="$icon('search', 'w-5 h-5 text-gray-400')"></div>
                </div>
            </div>

            <!-- Filter and Action Buttons -->
            <div class="flex gap-3">
                <!-- Role Filter -->
                <select
                    x-model="filters.role"
                    @change="loadUsers()"
                    class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                >
                    <option value="">All Roles</option>
                    <option value="customer">Customer</option>
                    <option value="vendor">Vendor</option>
                    <option value="admin">Admin</option>
                </select>

                <!-- Status Filter -->
                <select
                    x-model="filters.is_active"
                    @change="loadUsers()"
                    class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
                >
                    <option value="">All Status</option>
                    <option value="true">Active</option>
                    <option value="false">Inactive</option>
                </select>

                <!-- Add User Button -->
                <button
                    @click="openCreateModal()"
                    class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
                >
                    <span x-html="$icon('user-plus', 'w-5 h-5')"></span>
                    <span>Add User</span>
                </button>
            </div>
        </div>
    </div>

    <!-- Statistics Cards -->
    <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Total Users</p>
                    <p class="text-2xl font-bold text-gray-900 dark:text-white" x-text="stats.total"></p>
                </div>
                <div class="p-3 bg-blue-100 dark:bg-blue-900 rounded-lg" x-html="$icon('users', 'w-8 h-8 text-blue-600 dark:text-blue-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Active Users</p>
                    <p class="text-2xl font-bold text-green-600 dark:text-green-400" x-text="stats.active"></p>
                </div>
                <div class="p-3 bg-green-100 dark:bg-green-900 rounded-lg" x-html="$icon('user-check', 'w-8 h-8 text-green-600 dark:text-green-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Vendors</p>
                    <p class="text-2xl font-bold text-purple-600 dark:text-purple-400" x-text="stats.vendors"></p>
                </div>
                <div class="p-3 bg-purple-100 dark:bg-purple-900 rounded-lg" x-html="$icon('store', 'w-8 h-8 text-purple-600 dark:text-purple-400')"></div>
            </div>
        </div>

        <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
            <div class="flex items-center justify-between">
                <div>
                    <p class="text-sm text-gray-600 dark:text-gray-400">Admins</p>
                    <p class="text-2xl font-bold text-orange-600 dark:text-orange-400" x-text="stats.admins"></p>
                </div>
                <div class="p-3 bg-orange-100 dark:bg-orange-900 rounded-lg" x-html="$icon('shield', 'w-8 h-8 text-orange-600 dark:text-orange-400')"></div>
            </div>
        </div>
    </div>

    <!-- Users Table -->
    <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
        <!-- Loading State -->
        <div x-show="loading" class="p-8 text-center">
            <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
            <p class="mt-2 text-gray-600 dark:text-gray-400">Loading users...</p>
        </div>

        <!-- Empty State -->
        <div x-show="!loading && users.length === 0" class="p-8 text-center">
            <div class="text-gray-400 dark:text-gray-500 mb-4" x-html="$icon('inbox', 'w-16 h-16 mx-auto')"></div>
            <p class="text-gray-600 dark:text-gray-400">No users found</p>
        </div>

        <!-- Users Table -->
        <div x-show="!loading && users.length > 0">
            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                <thead class="bg-gray-50 dark:bg-gray-900">
                    <tr>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            User
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Email
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Role
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Status
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Registered
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Last Login
                        </th>
                        <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
                            Actions
                        </th>
                    </tr>
                </thead>
                <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
                    <template x-for="user in users" :key="user.id">
                        <tr class="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
                            <!-- User Info -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <div class="flex items-center">
                                    <div class="flex-shrink-0 h-10 w-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
                                        <span class="text-white font-semibold" x-text="(user.full_name || user.username || 'U').charAt(0).toUpperCase()"></span>
                                    </div>
                                    <div class="ml-4">
                                        <div class="text-sm font-medium text-gray-900 dark:text-white" x-text="user.full_name || 'N/A'"></div>
                                        <div class="text-sm text-gray-500 dark:text-gray-400" x-text="'@' + user.username"></div>
                                    </div>
                                </div>
                            </td>

                            <!-- Email -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <div class="text-sm text-gray-900 dark:text-white" x-text="user.email"></div>
                            </td>

                            <!-- Role -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <span 
                                    class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full"
                                    :class="{
                                        'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200': user.role === 'customer',
                                        'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200': user.role === 'vendor',
                                        'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200': user.role === 'admin'
                                    }"
                                    x-text="user.role"
                                ></span>
                            </td>

                            <!-- Status -->
                            <td class="px-6 py-4 whitespace-nowrap">
                                <span 
                                    class="px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full"
                                    :class="user.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'"
                                    x-text="user.is_active ? 'Active' : 'Inactive'"
                                ></span>
                            </td>

                            <!-- Registered -->
                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
                                <span x-text="Utils.formatDate(user.created_at)"></span>
                            </td>

                            <!-- Last Login -->
                            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
                                <span x-text="user.last_login ? Utils.formatDate(user.last_login) : 'Never'"></span>
                            </td>

                            <!-- Actions -->
                            <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                <div class="flex justify-end gap-2">
                                    <button
                                        @click="viewUser(user)"
                                        class="text-blue-600 hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300"
                                        title="View Details"
                                        x-html="$icon('eye', 'w-5 h-5')"
                                    ></button>
                                    <button
                                        @click="editUser(user)"
                                        class="text-yellow-600 hover:text-yellow-900 dark:text-yellow-400 dark:hover:text-yellow-300"
                                        title="Edit"
                                        x-html="$icon('edit', 'w-5 h-5')"
                                    ></button>
                                    <button
                                        @click="toggleUserStatus(user)"
                                        :class="user.is_active ? 'text-orange-600 hover:text-orange-900 dark:text-orange-400 dark:hover:text-orange-300' : 'text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300'"
                                        :title="user.is_active ? 'Deactivate' : 'Activate'"
                                        x-html="$icon(user.is_active ? 'user-x' : 'user-check', 'w-5 h-5')"
                                    ></button>
                                    <button
                                        @click="deleteUser(user)"
                                        class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300"
                                        title="Delete"
                                        x-html="$icon('trash', 'w-5 h-5')"
                                    ></button>
                                </div>
                            </td>
                        </tr>
                    </template>
                </tbody>
            </table>

            <!-- Pagination -->
            <div class="bg-gray-50 dark:bg-gray-900 px-6 py-4 border-t border-gray-200 dark:border-gray-700">
                <div class="flex items-center justify-between">
                    <div class="text-sm text-gray-700 dark:text-gray-300">
                        Showing <span class="font-medium" x-text="((pagination.page - 1) * pagination.per_page) + 1"></span>
                        to <span class="font-medium" x-text="Math.min(pagination.page * pagination.per_page, pagination.total)"></span>
                        of <span class="font-medium" x-text="pagination.total"></span> users
                    </div>
                    <div class="flex gap-2">
                        <button
                            @click="previousPage()"
                            :disabled="pagination.page === 1"
                            :class="pagination.page === 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 dark:hover:bg-gray-700'"
                            class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-700 dark:text-gray-300"
                        >
                            Previous
                        </button>
                        <button
                            @click="nextPage()"
                            :disabled="pagination.page >= pagination.pages"
                            :class="pagination.page >= pagination.pages ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-200 dark:hover:bg-gray-700'"
                            class="px-3 py-1 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-700 dark:text-gray-300"
                        >
                            Next
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

{% block extra_scripts %}
<script src="{{ url_for('static', path='admin/js/users.js') }}"></script>
{% endblock %}
```

**Checklist:**
- [ ] Create the template file
- [ ] Create `static/admin/js/users.js` Alpine component
- [ ] Test with sample data
- [ ] Verify all icons display correctly
- [ ] Test filters and search
- [ ] Test pagination

---

#### Task 2.2: Create users.js Alpine Component

**File:** `static/admin/js/users.js`

```javascript
function adminUsers() {
    return {
        // State
        users: [],
        loading: false,
        filters: {
            search: '',
            role: '',
            is_active: ''
        },
        stats: {
            total: 0,
            active: 0,
            vendors: 0,
            admins: 0
        },
        pagination: {
            page: 1,
            per_page: 10,
            total: 0,
            pages: 0
        },

        // Initialization
        init() {
            Logger.info('Users page initialized', 'USERS');
            this.loadUsers();
            this.loadStats();
        },

        // Load users from API
        async loadUsers() {
            this.loading = true;
            try {
                const params = new URLSearchParams({
                    page: this.pagination.page,
                    per_page: this.pagination.per_page,
                    ...this.filters
                });

                const response = await ApiClient.get(`/admin/users?${params}`);
                
                if (response.items) {
                    this.users = response.items;
                    this.pagination.total = response.total;
                    this.pagination.pages = response.pages;
                }
            } catch (error) {
                Logger.error('Failed to load users', 'USERS', error);
                Utils.showToast('Failed to load users', 'error');
            } finally {
                this.loading = false;
            }
        },

        // Load statistics
        async loadStats() {
            try {
                const response = await ApiClient.get('/admin/users/stats');
                if (response) {
                    this.stats = response;
                }
            } catch (error) {
                Logger.error('Failed to load stats', 'USERS', error);
            }
        },

        // Search with debounce
        debouncedSearch: Utils.debounce(function() {
            this.pagination.page = 1;
            this.loadUsers();
        }, 500),

        // Pagination
        nextPage() {
            if (this.pagination.page < this.pagination.pages) {
                this.pagination.page++;
                this.loadUsers();
            }
        },

        previousPage() {
            if (this.pagination.page > 1) {
                this.pagination.page--;
                this.loadUsers();
            }
        },

        // Actions
        viewUser(user) {
            Logger.info('View user', 'USERS', user);
            // TODO: Open view modal
        },

        editUser(user) {
            Logger.info('Edit user', 'USERS', user);
            // TODO: Open edit modal
        },

        async toggleUserStatus(user) {
            const action = user.is_active ? 'deactivate' : 'activate';
            if (!confirm(`Are you sure you want to ${action} ${user.username}?`)) {
                return;
            }

            try {
                await ApiClient.put(`/admin/users/${user.id}/status`, {
                    is_active: !user.is_active
                });
                Utils.showToast(`User ${action}d successfully`, 'success');
                this.loadUsers();
                this.loadStats();
            } catch (error) {
                Logger.error(`Failed to ${action} user`, 'USERS', error);
                Utils.showToast(`Failed to ${action} user`, 'error');
            }
        },

        async deleteUser(user) {
            if (!confirm(`Are you sure you want to delete ${user.username}? This action cannot be undone.`)) {
                return;
            }

            try {
                await ApiClient.delete(`/admin/users/${user.id}`);
                Utils.showToast('User deleted successfully', 'success');
                this.loadUsers();
                this.loadStats();
            } catch (error) {
                Logger.error('Failed to delete user', 'USERS', error);
                Utils.showToast('Failed to delete user', 'error');
            }
        },

        openCreateModal() {
            Logger.info('Open create user modal', 'USERS');
            // TODO: Open create modal
        }
    };
}
```

**Checklist:**
- [ ] Create users.js file
- [ ] Test all functions work correctly
- [ ] Verify API endpoints exist
- [ ] Test error handling

---

#### Task 2.3: Add Backend Route for Users Page

**File:** `app/routes/admin.py`

```python
@router.get("/users", response_class=HTMLResponse)
async def admin_users_page(request: Request):
    """Render the admin users page."""
    return templates.TemplateResponse(
        "admin/users.html",
        {"request": request}
    )
```

**Checklist:**
- [ ] Add route to backend
- [ ] Test route returns correct template
- [ ] Verify authentication middleware is applied

---

### Priority 3: Testing & Verification (HIGH) ✅

#### Task 3.1: Comprehensive Testing

**Test Checklist:**
- [ ] **Vendors Page:**
  - [ ] Page loads without errors
  - [ ] Statistics cards display correctly
  - [ ] Vendors table loads data
  - [ ] Search functionality works
  - [ ] Filters work correctly
  - [ ] Pagination works
  - [ ] Action buttons respond correctly
  - [ ] Icons display properly
  - [ ] Dark mode works

- [ ] **Users Page:**
  - [ ] Page loads without errors
  - [ ] Statistics cards display correctly
  - [ ] Users table loads data
  - [ ] Search functionality works
  - [ ] Role filter works
  - [ ] Status filter works
  - [ ] Pagination works
  - [ ] Toggle user status works
  - [ ] Action buttons respond correctly
  - [ ] Icons display properly
  - [ ] Dark mode works

- [ ] **Navigation:**
  - [ ] Sidebar links to vendors page works
  - [ ] Sidebar links to users page works
  - [ ] Active state highlights correctly
  - [ ] Breadcrumbs update (if applicable)

- [ ] **General:**
  - [ ] No console errors
  - [ ] No 404 errors in Network tab
  - [ ] Authentication still works
  - [ ] Logout still works
  - [ ] Dark mode toggle works across pages
  - [ ] Responsive design works on mobile

---

### Priority 4: Cleanup & Documentation (MEDIUM) 🧹

#### Task 4.1: Remove Old Files

**Files to delete (after backing up):**
- [ ] `static/admin/vendors.html` (old static version)
- [ ] `static/admin/users.html` (old static version, if exists)
- [ ] Any old partial HTML files

**Command to find old files:**
```bash
find static/admin -name "*.html" -type f
```

---

#### Task 4.2: Update Documentation

**Create/Update:**
- [ ] Add note about vendors page migration to changelog
- [ ] Add note about users page creation to changelog
- [ ] Update project structure documentation
- [ ] Document any new API endpoints used

---

## ⏰ Updated Time Estimates

| Task | Estimated Time | Priority |
|------|---------------|----------|
| Migrate Vendors page | 60-90 min | HIGH |
| Create Users page | 60-90 min | HIGH |
| Testing & verification | 30-45 min | HIGH |
| Cleanup old code | 15-30 min | MEDIUM |
| Documentation | 15-20 min | LOW |

**Total: 3-4 hours**

---

## ✅ Success Criteria

By end of today, we should have:
- [ ] Vendors page fully migrated to Jinja2
- [ ] Users page created and functional
- [ ] All pages tested and working
- [ ] No console errors
- [ ] Proper authentication on all pages
- [ ] Old static files removed
- [ ] Documentation updated

---

## 🎯 Next Steps (Future)

After completing vendors and users pages:

1. **Add Modals for CRUD Operations:**
   - Create/Edit Vendor modal
   - Create/Edit User modal
   - View details modals
   - Delete confirmation modals

2. **Enhanced Features:**
   - Bulk operations (select multiple users/vendors)
   - Export to CSV functionality
   - Advanced filters (date range, etc.)
   - Sorting by column

3. **Other Admin Pages:**
   - Products management
   - Orders management
   - Reports/Analytics
   - Settings page

4. **Vendor Portal Migration:**
   - Apply same Jinja2 pattern
   - Separate authentication
   - Vendor dashboard
   - Vendor-specific features

---

## 🐛 Troubleshooting Guide

### If Vendors Page Doesn't Load:
```javascript
// Check in console:
console.log('adminVendors function:', typeof adminVendors);
console.log('Alpine instance:', Alpine);

// Check network tab for API calls:
// Should see: GET /api/v1/admin/vendors
// Should see: GET /api/v1/admin/vendors/stats
```

### If Users Page Doesn't Load:
```javascript
// Check in console:
console.log('adminUsers function:', typeof adminUsers);

// Check network tab for API calls:
// Should see: GET /api/v1/admin/users
// Should see: GET /api/v1/admin/users/stats
```

### If Icons Don't Display:
```javascript
// Already fixed, but if issues persist:
console.log('$icon magic:', typeof Alpine.magic('icon'));
document.body.innerHTML += window.icon('test', 'w-6 h-6');
```

---

## 📝 Notes

- **Pattern to Follow:** Both pages follow the same structure as dashboard.html
- **Alpine.js Components:** Each page has its own JS file with an Alpine component
- **API Endpoints:** Make sure backend API endpoints exist before testing
- **Authentication:** All pages use the same cookie-based auth system
- **Consistency:** Keep UI consistent across all admin pages (same colors, same layout patterns)

---

**Let's get these pages migrated! 🚀**

Remember: Test frequently, commit often, and keep the same patterns we used for the dashboard.