feat: add complete company management UI
- Create companies list page with stats (total, verified, active, vendor count) - Add company creation form with owner account generation - Implement companies.js with full CRUD operations (list, create, edit, delete) - Add Companies menu item to admin sidebar (desktop + mobile) - Create company admin page routes (/admin/companies, /admin/companies/create) - Register companies API router in admin __init__.py Features: - List all companies with pagination - Create company with automatic owner user creation - Display temporary password for new owner accounts - Edit company information - Delete company (only if no vendors) - Toggle active/verified status - Show vendor count per company UI Components: - Stats cards (total companies, verified, active, total vendors) - Company table with status badges - Create form with validation - Success/error messaging - Responsive design with dark mode support
This commit is contained in:
@@ -28,8 +28,10 @@ from . import (
|
||||
audit,
|
||||
auth,
|
||||
code_quality,
|
||||
companies,
|
||||
content_pages,
|
||||
dashboard,
|
||||
logs,
|
||||
marketplace,
|
||||
monitoring,
|
||||
notifications,
|
||||
@@ -53,9 +55,12 @@ router.include_router(auth.router, tags=["admin-auth"])
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Management
|
||||
# Company & Vendor Management
|
||||
# ============================================================================
|
||||
|
||||
# Include company management endpoints
|
||||
router.include_router(companies.router, tags=["admin-companies"])
|
||||
|
||||
# Include vendor management endpoints
|
||||
router.include_router(vendors.router, tags=["admin-vendors"])
|
||||
|
||||
@@ -111,6 +116,9 @@ router.include_router(settings.router, tags=["admin-settings"])
|
||||
# Include notifications and alerts endpoints
|
||||
router.include_router(notifications.router, tags=["admin-notifications"])
|
||||
|
||||
# Include log management endpoints
|
||||
router.include_router(logs.router, tags=["admin-logs"])
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Code Quality & Architecture
|
||||
|
||||
@@ -17,6 +17,7 @@ Routes:
|
||||
- GET /vendors/{vendor_code} → Vendor details (auth required)
|
||||
- GET /vendors/{vendor_code}/edit → Edit vendor form (auth required)
|
||||
- GET /vendors/{vendor_code}/domains → Vendor domains management (auth required)
|
||||
- GET /vendor-themes → Vendor themes selection page (auth required)
|
||||
- GET /vendors/{vendor_code}/theme → Vendor theme editor (auth required)
|
||||
- GET /users → User management page (auth required)
|
||||
- GET /imports → Import history page (auth required)
|
||||
@@ -109,6 +110,48 @@ async def admin_dashboard_page(
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# COMPANY MANAGEMENT ROUTES
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/companies", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_companies_list_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Render companies management page.
|
||||
Shows list of all companies with stats.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/companies.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/companies/create", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_company_create_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Render company creation form.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/company-create.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# VENDOR MANAGEMENT ROUTES
|
||||
# ============================================================================
|
||||
@@ -231,6 +274,25 @@ async def admin_vendor_domains_page(
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/vendor-themes", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_vendor_themes_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Render vendor themes selection page.
|
||||
Allows admins to select a vendor to customize their theme.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/vendor-themes.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/vendors/{vendor_code}/theme", response_class=HTMLResponse, include_in_schema=False
|
||||
)
|
||||
@@ -302,6 +364,25 @@ async def admin_imports_page(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/marketplace", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_marketplace_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Render marketplace import management page.
|
||||
Allows admins to import products for any vendor and monitor all imports.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/marketplace.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# SETTINGS ROUTES
|
||||
# ============================================================================
|
||||
@@ -326,6 +407,25 @@ async def admin_settings_page(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/logs", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def admin_logs_page(
|
||||
request: Request,
|
||||
current_user: User = Depends(get_current_admin_from_cookie_or_header),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Render admin logs viewer page.
|
||||
View database and file logs with filtering and search.
|
||||
"""
|
||||
return templates.TemplateResponse(
|
||||
"admin/logs.html",
|
||||
{
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CONTENT MANAGEMENT SYSTEM (CMS) ROUTES
|
||||
# ============================================================================
|
||||
|
||||
272
app/templates/admin/companies.html
Normal file
272
app/templates/admin/companies.html
Normal file
@@ -0,0 +1,272 @@
|
||||
{# app/templates/admin/companies.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Companies{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminCompanies(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
Company Management
|
||||
</h2>
|
||||
<a
|
||||
href="/admin/companies/create"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
|
||||
>
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Company
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div x-show="loading" class="text-center py-12">
|
||||
<span x-html="$icon('spinner', 'inline w-8 h-8 text-purple-600')"></span>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Loading companies...</p>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div x-show="error && !loading" class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg flex items-start">
|
||||
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="font-semibold">Error loading companies</p>
|
||||
<p class="text-sm" x-text="error"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div x-show="!loading" class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
<!-- Card: Total Companies -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
||||
<span x-html="$icon('office-building', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Companies
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total || 0">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card: Verified Companies -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
||||
<span x-html="$icon('badge-check', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Verified
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.verified || 0">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card: Active Companies -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Active
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.active || 0">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card: Total Vendors -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
|
||||
<span x-html="$icon('shopping-bag', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Vendors
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.totalVendors || 0">
|
||||
0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Companies Table -->
|
||||
<div x-show="!loading" class="w-full overflow-hidden rounded-lg shadow-xs">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="w-full whitespace-no-wrap">
|
||||
<thead>
|
||||
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
|
||||
<th class="px-4 py-3">Company</th>
|
||||
<th class="px-4 py-3">Owner</th>
|
||||
<th class="px-4 py-3">Vendors</th>
|
||||
<th class="px-4 py-3">Status</th>
|
||||
<th class="px-4 py-3">Created</th>
|
||||
<th class="px-4 py-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<!-- Empty State -->
|
||||
<template x-if="paginatedCompanies.length === 0">
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<span x-html="$icon('office-building', 'w-12 h-12 mb-2 text-gray-300')"></span>
|
||||
<p class="font-medium">No companies found</p>
|
||||
<p class="text-xs mt-1">Create your first company to get started</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!-- Company Rows -->
|
||||
<template x-for="company in paginatedCompanies" :key="company.id">
|
||||
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
<!-- Company Info -->
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center text-sm">
|
||||
<div class="relative hidden w-8 h-8 mr-3 rounded-full md:block">
|
||||
<div class="absolute inset-0 rounded-full bg-blue-100 dark:bg-blue-600 flex items-center justify-center">
|
||||
<span class="text-xs font-semibold text-blue-600 dark:text-blue-100"
|
||||
x-text="company.name?.charAt(0).toUpperCase() || '?'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-semibold" x-text="company.name"></p>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400" x-text="company.contact_email"></p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Owner Email -->
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<p x-text="company.owner_email || 'N/A'"></p>
|
||||
</td>
|
||||
|
||||
<!-- Vendor Count -->
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<span class="px-2 py-1 font-semibold leading-tight text-blue-700 bg-blue-100 rounded-full dark:bg-blue-700 dark:text-blue-100"
|
||||
x-text="company.vendor_count || 0">
|
||||
0
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Status Badges -->
|
||||
<td class="px-4 py-3 text-xs">
|
||||
<div class="flex gap-1">
|
||||
<span class="inline-flex items-center px-2 py-1 font-semibold leading-tight rounded-full"
|
||||
:class="company.is_active ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700'">
|
||||
<span x-text="company.is_active ? 'Active' : 'Inactive'"></span>
|
||||
</span>
|
||||
<span x-show="company.is_verified" class="inline-flex items-center px-2 py-1 font-semibold leading-tight text-blue-700 bg-blue-100 rounded-full dark:bg-blue-700 dark:text-blue-100">
|
||||
<span x-html="$icon('badge-check', 'w-3 h-3 mr-1')"></span>
|
||||
Verified
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Created Date -->
|
||||
<td class="px-4 py-3 text-sm" x-text="formatDate(company.created_at)"></td>
|
||||
|
||||
<!-- Actions -->
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<!-- Edit Button -->
|
||||
<button
|
||||
@click="editCompany(company.id)"
|
||||
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||
title="Edit company"
|
||||
>
|
||||
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<button
|
||||
@click="deleteCompany(company)"
|
||||
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||
title="Delete company"
|
||||
:disabled="company.vendor_count > 0"
|
||||
:class="company.vendor_count > 0 ? 'opacity-50 cursor-not-allowed' : ''"
|
||||
>
|
||||
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination Footer -->
|
||||
<div class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t dark:border-gray-700 bg-gray-50 sm:grid-cols-9 dark:text-gray-400 dark:bg-gray-800">
|
||||
<!-- Results Info -->
|
||||
<span class="flex items-center col-span-3">
|
||||
Showing <span class="mx-1 font-bold" x-text="startIndex"></span>-<span class="mx-1 font-bold" x-text="endIndex"></span> of <span class="mx-1 font-bold" x-text="companies.length"></span>
|
||||
</span>
|
||||
<span class="col-span-2"></span>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
|
||||
<nav aria-label="Table navigation">
|
||||
<ul class="inline-flex items-center">
|
||||
<!-- Previous Button -->
|
||||
<li>
|
||||
<button
|
||||
@click="previousPage()"
|
||||
:disabled="currentPage === 1"
|
||||
class="px-3 py-1 rounded-md rounded-l-lg focus:outline-none focus:shadow-outline-purple"
|
||||
:class="currentPage === 1 ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
aria-label="Previous"
|
||||
>
|
||||
<svg class="w-4 h-4 fill-current" aria-hidden="true" viewBox="0 0 20 20">
|
||||
<path d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- Page Numbers -->
|
||||
<template x-for="page in pageNumbers" :key="page">
|
||||
<li>
|
||||
<button
|
||||
x-show="page !== '...'"
|
||||
@click="goToPage(page)"
|
||||
class="px-3 py-1 rounded-md focus:outline-none focus:shadow-outline-purple"
|
||||
:class="currentPage === page ? 'text-white bg-purple-600 border border-purple-600' : 'hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
x-text="page"
|
||||
></button>
|
||||
<span x-show="page === '...'" class="px-3 py-1">...</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<!-- Next Button -->
|
||||
<li>
|
||||
<button
|
||||
@click="nextPage()"
|
||||
:disabled="currentPage === totalPages"
|
||||
class="px-3 py-1 rounded-md rounded-r-lg focus:outline-none focus:shadow-outline-purple"
|
||||
:class="currentPage === totalPages ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-100 dark:hover:bg-gray-700'"
|
||||
aria-label="Next"
|
||||
>
|
||||
<svg class="w-4 h-4 fill-current" aria-hidden="true" viewBox="0 0 20 20">
|
||||
<path d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ url_for('static', path='admin/js/companies.js') }}"></script>
|
||||
{% endblock %}
|
||||
309
app/templates/admin/company-create.html
Normal file
309
app/templates/admin/company-create.html
Normal file
@@ -0,0 +1,309 @@
|
||||
{# app/templates/admin/company-create.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Create Company{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminCompanyCreate(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between my-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
Create New Company
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Create a company account with an owner user
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="/admin/companies"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-500 focus:outline-none focus:shadow-outline-gray"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Companies
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Success Message -->
|
||||
<div x-show="successMessage" x-cloak class="mb-6 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold">Company Created Successfully!</p>
|
||||
<div x-show="ownerCredentials" class="mt-2 p-3 bg-white rounded border border-green-300">
|
||||
<p class="text-sm font-semibold mb-2">Owner Login Credentials (Save these!):</p>
|
||||
<div class="space-y-1 text-sm font-mono">
|
||||
<div><span class="font-bold">Email:</span> <span x-text="ownerCredentials.email"></span></div>
|
||||
<div><span class="font-bold">Password:</span> <span x-text="ownerCredentials.password" class="bg-yellow-100 px-2 py-1 rounded"></span></div>
|
||||
<div><span class="font-bold">Login URL:</span> <span x-text="ownerCredentials.login_url"></span></div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-red-600">⚠️ The password will only be shown once. Please save it now!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div x-show="errorMessage" x-cloak class="mb-6 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg">
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('exclamation', 'w-5 h-5 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="font-semibold">Error Creating Company</p>
|
||||
<p class="text-sm mt-1" x-text="errorMessage"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Company Form -->
|
||||
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<form @submit.prevent="createCompany">
|
||||
<!-- Company Information Section -->
|
||||
<div class="mb-6">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">Company Information</h3>
|
||||
|
||||
<div class="grid gap-6 mb-4 md:grid-cols-2">
|
||||
<!-- Company Name -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Company Name <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="formData.name"
|
||||
required
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="ACME Corporation"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Contact Email (Business) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Business Contact Email <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
x-model="formData.contact_email"
|
||||
required
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="info@acmecorp.com"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500">Public business contact email</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
x-model="formData.description"
|
||||
rows="3"
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="Brief description of the company..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 mb-4 md:grid-cols-2">
|
||||
<!-- Contact Phone -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Phone Number
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
x-model="formData.contact_phone"
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="+352 123 456 789"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Website -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
x-model="formData.website"
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="https://www.acmecorp.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 mb-4 md:grid-cols-2">
|
||||
<!-- Business Address -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Business Address
|
||||
</label>
|
||||
<textarea
|
||||
x-model="formData.business_address"
|
||||
rows="2"
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="123 Main Street, Luxembourg City"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Tax Number -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Tax/VAT Number
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="formData.tax_number"
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="LU12345678"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Owner Information Section -->
|
||||
<div class="mb-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">Owner Account</h3>
|
||||
<div class="p-4 mb-4 bg-blue-50 border border-blue-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
|
||||
<p class="text-sm text-blue-800 dark:text-blue-300">
|
||||
<span x-html="$icon('information-circle', 'w-4 h-4 inline mr-1')"></span>
|
||||
A user account will be created for the company owner. If the email already exists, that user will be assigned as owner.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Owner Email (Login) <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
x-model="formData.owner_email"
|
||||
required
|
||||
class="block w-full px-3 py-2 text-sm border border-gray-300 rounded-lg dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-600"
|
||||
placeholder="john.smith@acmecorp.com"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500">This email will be used for owner login. Can be different from business contact email.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center justify-between pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
type="button"
|
||||
@click="window.location.href='/admin/companies'"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-white border border-gray-300 rounded-lg dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800 hover:border-gray-500 focus:outline-none focus:shadow-outline-gray"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span x-show="!loading">Create Company</span>
|
||||
<span x-show="loading" class="flex items-center">
|
||||
<span x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||
Creating...
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script>
|
||||
// Company Create Alpine Component
|
||||
function adminCompanyCreate() {
|
||||
return {
|
||||
// Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// Page identifier
|
||||
currentPage: 'companies',
|
||||
|
||||
// Form data
|
||||
formData: {
|
||||
name: '',
|
||||
description: '',
|
||||
owner_email: '',
|
||||
contact_email: '',
|
||||
contact_phone: '',
|
||||
website: '',
|
||||
business_address: '',
|
||||
tax_number: ''
|
||||
},
|
||||
|
||||
// UI state
|
||||
loading: false,
|
||||
successMessage: false,
|
||||
errorMessage: '',
|
||||
ownerCredentials: null,
|
||||
|
||||
// Initialize
|
||||
init() {
|
||||
console.log('Company Create page initialized');
|
||||
},
|
||||
|
||||
// Create company
|
||||
async createCompany() {
|
||||
this.loading = true;
|
||||
this.errorMessage = '';
|
||||
this.successMessage = false;
|
||||
this.ownerCredentials = null;
|
||||
|
||||
try {
|
||||
console.log('Creating company:', this.formData);
|
||||
|
||||
const response = await apiClient.post('/api/v1/admin/companies', this.formData);
|
||||
|
||||
console.log('Company created successfully:', response);
|
||||
|
||||
// Store owner credentials
|
||||
if (response.temporary_password && response.temporary_password !== 'N/A (Existing user)') {
|
||||
this.ownerCredentials = {
|
||||
email: response.owner_email,
|
||||
password: response.temporary_password,
|
||||
login_url: response.login_url
|
||||
};
|
||||
}
|
||||
|
||||
this.successMessage = true;
|
||||
|
||||
// Reset form
|
||||
this.formData = {
|
||||
name: '',
|
||||
description: '',
|
||||
owner_email: '',
|
||||
contact_email: '',
|
||||
contact_phone: '',
|
||||
website: '',
|
||||
business_address: '',
|
||||
tax_number: ''
|
||||
};
|
||||
|
||||
// Scroll to top to show success message
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
|
||||
// Redirect after 10 seconds if credentials shown, or 3 seconds otherwise
|
||||
const redirectDelay = this.ownerCredentials ? 10000 : 3000;
|
||||
setTimeout(() => {
|
||||
window.location.href = '/admin/companies';
|
||||
}, redirectDelay);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to create company:', error);
|
||||
this.errorMessage = error.message || 'Failed to create company';
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -20,6 +20,17 @@
|
||||
|
||||
<!-- Main Navigation -->
|
||||
<ul>
|
||||
<!-- Companies -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'companies'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'companies' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/companies">
|
||||
<span x-html="$icon('office-building')"></span>
|
||||
<span class="ml-4">Companies</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
@@ -42,14 +53,14 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Import Jobs -->
|
||||
<!-- Marketplace Import -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<span x-show="currentPage === 'marketplace'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
:class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/marketplace">
|
||||
<span x-html="$icon('shopping-bag')"></span>
|
||||
<span class="ml-4">Marketplace Import</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -83,6 +94,17 @@
|
||||
<span class="ml-4">Content Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendor Themes -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendor-theme'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendor-theme' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendor-themes">
|
||||
<span x-html="$icon('color-swatch')"></span>
|
||||
<span class="ml-4">Vendor Themes</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
@@ -138,6 +160,37 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Platform Monitoring Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Monitoring
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Application Logs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'logs'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'logs' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/logs">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Application Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
@@ -196,6 +249,17 @@
|
||||
|
||||
<!-- Main Navigation -->
|
||||
<ul>
|
||||
<!-- Companies -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'companies'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'companies' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/companies">
|
||||
<span x-html="$icon('office-building')"></span>
|
||||
<span class="ml-4">Companies</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendors -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendors'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
@@ -218,14 +282,14 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Import Jobs -->
|
||||
<!-- Marketplace Import -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<span x-show="currentPage === 'marketplace'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
:class="currentPage === 'marketplace' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/marketplace">
|
||||
<span x-html="$icon('shopping-bag')"></span>
|
||||
<span class="ml-4">Marketplace Import</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -259,6 +323,17 @@
|
||||
<span class="ml-4">Content Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Vendor Themes -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'vendor-theme'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'vendor-theme' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/vendor-themes">
|
||||
<span x-html="$icon('color-swatch')"></span>
|
||||
<span class="ml-4">Vendor Themes</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Developer Tools Section -->
|
||||
@@ -314,6 +389,37 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Platform Monitoring Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<p class="px-6 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider">
|
||||
Platform Monitoring
|
||||
</p>
|
||||
<ul class="mt-3">
|
||||
<!-- Import Jobs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'imports'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'imports' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/imports">
|
||||
<span x-html="$icon('cube')"></span>
|
||||
<span class="ml-4">Import Jobs</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Application Logs -->
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === 'logs'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
|
||||
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
|
||||
:class="currentPage === 'logs' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="/admin/logs">
|
||||
<span x-html="$icon('document-text')"></span>
|
||||
<span class="ml-4">Application Logs</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<div class="px-6 my-6">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
|
||||
237
static/admin/js/companies.js
Normal file
237
static/admin/js/companies.js
Normal file
@@ -0,0 +1,237 @@
|
||||
// static/admin/js/companies.js
|
||||
|
||||
// ✅ Use centralized logger
|
||||
const companiesLog = window.LogConfig.loggers.companies || window.LogConfig.createLogger('companies');
|
||||
|
||||
// ============================================
|
||||
// COMPANY LIST FUNCTION
|
||||
// ============================================
|
||||
function adminCompanies() {
|
||||
return {
|
||||
// Inherit base layout functionality
|
||||
...data(),
|
||||
|
||||
// ✅ Page identifier for sidebar active state
|
||||
currentPage: 'companies',
|
||||
|
||||
// Companies page specific state
|
||||
companies: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
verified: 0,
|
||||
active: 0,
|
||||
totalVendors: 0
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
|
||||
// Pagination state
|
||||
page: 1,
|
||||
itemsPerPage: 10,
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
companiesLog.info('=== COMPANIES PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._companiesInitialized) {
|
||||
companiesLog.warn('Companies page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._companiesInitialized = true;
|
||||
|
||||
companiesLog.group('Loading companies data');
|
||||
await this.loadCompanies();
|
||||
await this.loadStats();
|
||||
companiesLog.groupEnd();
|
||||
|
||||
companiesLog.info('=== COMPANIES PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
// Computed: Get paginated companies for current page
|
||||
get paginatedCompanies() {
|
||||
const start = (this.page - 1) * this.itemsPerPage;
|
||||
const end = start + this.itemsPerPage;
|
||||
return this.companies.slice(start, end);
|
||||
},
|
||||
|
||||
// Computed: Total number of pages
|
||||
get totalPages() {
|
||||
return Math.ceil(this.companies.length / this.itemsPerPage);
|
||||
},
|
||||
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.companies.length === 0) return 0;
|
||||
return (this.page - 1) * this.itemsPerPage + 1;
|
||||
},
|
||||
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.page * this.itemsPerPage;
|
||||
return end > this.companies.length ? this.companies.length : end;
|
||||
},
|
||||
|
||||
// Computed: Generate page numbers array with ellipsis
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const totalPages = this.totalPages;
|
||||
const current = this.page;
|
||||
|
||||
if (totalPages <= 7) {
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
|
||||
if (current > 3) {
|
||||
pages.push('...');
|
||||
}
|
||||
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(totalPages - 1, current + 1);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
if (current < totalPages - 2) {
|
||||
pages.push('...');
|
||||
}
|
||||
|
||||
pages.push(totalPages);
|
||||
}
|
||||
|
||||
return pages;
|
||||
},
|
||||
|
||||
// Load all companies
|
||||
async loadCompanies() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
companiesLog.info('Fetching companies from API...');
|
||||
const response = await apiClient.get('/admin/companies');
|
||||
|
||||
if (response.companies) {
|
||||
this.companies = response.companies;
|
||||
companiesLog.info(`Loaded ${this.companies.length} companies`);
|
||||
} else {
|
||||
companiesLog.warn('No companies in response');
|
||||
this.companies = [];
|
||||
}
|
||||
} catch (error) {
|
||||
companiesLog.error('Failed to load companies:', error);
|
||||
this.error = error.message || 'Failed to load companies';
|
||||
this.companies = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// Load statistics
|
||||
async loadStats() {
|
||||
try {
|
||||
companiesLog.info('Calculating stats from companies...');
|
||||
|
||||
this.stats.total = this.companies.length;
|
||||
this.stats.verified = this.companies.filter(c => c.is_verified).length;
|
||||
this.stats.active = this.companies.filter(c => c.is_active).length;
|
||||
this.stats.totalVendors = this.companies.reduce((sum, c) => sum + (c.vendor_count || 0), 0);
|
||||
|
||||
companiesLog.info('Stats calculated:', this.stats);
|
||||
} catch (error) {
|
||||
companiesLog.error('Failed to calculate stats:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// Edit company
|
||||
editCompany(companyId) {
|
||||
companiesLog.info('Edit company:', companyId);
|
||||
// TODO: Navigate to edit page
|
||||
window.location.href = `/admin/companies/${companyId}/edit`;
|
||||
},
|
||||
|
||||
// Delete company
|
||||
async deleteCompany(company) {
|
||||
if (company.vendor_count > 0) {
|
||||
companiesLog.warn('Cannot delete company with vendors');
|
||||
alert(`Cannot delete "${company.name}" because it has ${company.vendor_count} vendor(s). Please delete or reassign the vendors first.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to delete "${company.name}"?\n\nThis action cannot be undone.`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
companiesLog.info('Delete cancelled by user');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
companiesLog.info('Deleting company:', company.id);
|
||||
|
||||
await apiClient.delete(`/admin/companies/${company.id}?confirm=true`);
|
||||
|
||||
companiesLog.info('Company deleted successfully');
|
||||
|
||||
// Reload companies
|
||||
await this.loadCompanies();
|
||||
await this.loadStats();
|
||||
|
||||
alert(`Company "${company.name}" deleted successfully`);
|
||||
} catch (error) {
|
||||
companiesLog.error('Failed to delete company:', error);
|
||||
alert(`Failed to delete company: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
// Pagination methods
|
||||
previousPage() {
|
||||
if (this.page > 1) {
|
||||
this.page--;
|
||||
companiesLog.info('Previous page:', this.page);
|
||||
}
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
if (this.page < this.totalPages) {
|
||||
this.page++;
|
||||
companiesLog.info('Next page:', this.page);
|
||||
}
|
||||
},
|
||||
|
||||
goToPage(pageNum) {
|
||||
if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) {
|
||||
this.page = pageNum;
|
||||
companiesLog.info('Go to page:', this.page);
|
||||
}
|
||||
},
|
||||
|
||||
// Format date for display
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return 'N/A';
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
} catch (e) {
|
||||
companiesLog.error('Date parsing error:', e);
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Register logger for configuration
|
||||
if (!window.LogConfig.loggers.companies) {
|
||||
window.LogConfig.loggers.companies = window.LogConfig.createLogger('companies');
|
||||
}
|
||||
|
||||
companiesLog.info('✅ Companies module loaded');
|
||||
Reference in New Issue
Block a user