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:
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 %}
|
||||
Reference in New Issue
Block a user