Files
orion/app/modules/prospecting/templates/prospecting/admin/prospects.html
Samir Boulahtit 377d2d3ae8 feat(prospecting): add delete button to prospects list
- Trash icon button in Actions column with confirmation dialog
- Calls DELETE /admin/prospecting/prospects/{id} (existing endpoint)
- Reloads list after successful deletion
- Toast notification on success/failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:59:12 +02:00

246 lines
15 KiB
HTML

{% extends "admin/base.html" %}
{% from 'shared/macros/headers.html' import page_header %}
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
{% from 'shared/macros/tables.html' import table_wrapper, table_header, table_empty_state %}
{% from 'shared/macros/pagination.html' import pagination %}
{% from 'shared/macros/modals.html' import modal %}
{% block title %}Prospects{% endblock %}
{% block alpine_data %}prospectsList(){% endblock %}
{% block content %}
{{ page_header('Prospects', action_label='New Prospect', action_onclick='showCreateModal = true', action_icon='plus') }}
{{ loading_state('Loading prospects...') }}
{{ error_state('Error loading prospects') }}
<!-- Search and Filters Bar -->
<div x-show="!loading && !error" class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<!-- Search Input -->
<div class="flex-1 max-w-md">
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
</span>
<input type="text" x-model="search" @input.debounce.300ms="pagination.page = 1; loadProspects()"
placeholder="Search by domain or business name..."
class="w-full pl-10 pr-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300">
</div>
</div>
<!-- Filters -->
<div class="flex flex-wrap gap-3">
<select x-model="filterChannel" @change="pagination.page = 1; loadProspects()"
class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none">
<option value="">All Channels</option>
<option value="digital">Digital</option>
<option value="offline">Offline</option>
</select>
<select x-model="filterStatus" @change="pagination.page = 1; loadProspects()"
class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none">
<option value="">All Status</option>
<option value="pending">Pending</option>
<option value="active">Active</option>
<option value="contacted">Contacted</option>
<option value="converted">Converted</option>
<option value="inactive">Inactive</option>
</select>
<select x-model="filterTier" @change="pagination.page = 1; loadProspects()"
class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none">
<option value="">All Tiers</option>
<option value="top_priority">Top Priority</option>
<option value="quick_win">Quick Win</option>
<option value="strategic">Strategic</option>
<option value="low_priority">Low Priority</option>
</select>
</div>
</div>
</div>
<!-- Prospects Table -->
<div x-show="!loading && !error">
{% call table_wrapper() %}
{{ table_header(['Business / Domain', 'Channel', 'Status', 'Score', 'Tier', 'Contact', 'Actions']) }}
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
{{ table_empty_state(7, title='No prospects found', x_message="search || filterChannel || filterStatus || filterTier ? 'Try adjusting your search or filters' : 'Create your first prospect to get started'", show_condition='prospects.length === 0', icon='user-group') }}
<template x-for="p in prospects" :key="p.id">
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<!-- Business / Domain with Avatar -->
<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-purple-100 dark:bg-purple-600 flex items-center justify-center">
<span class="text-xs font-semibold text-purple-600 dark:text-purple-100"
x-text="(p.business_name || p.domain_name)?.charAt(0).toUpperCase() || '?'"></span>
</div>
</div>
<div>
<p class="font-semibold" x-text="p.business_name || p.domain_name"></p>
<p class="text-xs text-gray-600 dark:text-gray-400" x-show="p.domain_name && p.business_name" x-text="p.domain_name"></p>
</div>
</div>
</td>
<!-- Channel Badge -->
<td class="px-4 py-3 text-sm">
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full"
:class="p.channel === 'digital'
? 'text-blue-700 bg-blue-100 dark:text-blue-100 dark:bg-blue-700'
: 'text-purple-700 bg-purple-100 dark:text-purple-100 dark:bg-purple-700'"
x-text="p.channel"></span>
</td>
<!-- Status Badge -->
<td class="px-4 py-3 text-sm">
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full"
:class="statusBadgeClass(p.status)"
x-text="p.status"></span>
</td>
<!-- Score -->
<td class="px-4 py-3 text-sm">
<span x-text="p.score?.score ?? '—'" class="font-semibold"
:class="scoreColor(p.score?.score)"></span>
</td>
<!-- Tier Badge -->
<td class="px-4 py-3 text-sm">
<span x-show="p.score?.lead_tier"
class="px-2.5 py-0.5 text-xs font-medium rounded-full"
:class="tierBadgeClass(p.score?.lead_tier)"
x-text="p.score?.lead_tier?.replace('_', ' ')"></span>
<span x-show="!p.score?.lead_tier" class="text-gray-400"></span>
</td>
<!-- Contact -->
<td class="px-4 py-3 text-sm">
<template x-if="p.primary_email">
<span class="text-xs" x-text="p.primary_email"></span>
</template>
<span x-show="!p.primary_email" class="text-gray-400"></span>
</td>
<!-- Actions -->
<td class="px-4 py-3">
<div class="flex items-center space-x-2 text-sm">
<a :href="'/admin/prospecting/prospects/' + p.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="View details">
<span x-html="$icon('eye', 'w-5 h-5')"></span>
</a>
<button type="button" @click="deleteProspect(p)"
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">
<span x-html="$icon('trash', 'w-5 h-5')"></span>
</button>
</div>
</td>
</tr>
</template>
</tbody>
{% endcall %}
{{ pagination() }}
</div>
<!-- Create Prospect Modal -->
{% call modal('createProspectModal', 'New Prospect', show_var='showCreateModal', size='md', show_footer=false) %}
<!-- Channel Toggle -->
<div class="flex mb-5 space-x-2">
<button type="button" @click="newProspect.channel = 'digital'"
:class="newProspect.channel === 'digital'
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-700 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600'"
class="flex-1 px-4 py-2 text-sm font-medium rounded-lg border transition-colors duration-150">
<span x-html="$icon('globe', 'w-4 h-4 inline mr-1')"></span>
Digital (Domain)
</button>
<button type="button" @click="newProspect.channel = 'offline'"
:class="newProspect.channel === 'offline'
? 'bg-purple-600 text-white border-purple-600'
: 'bg-white text-gray-700 border-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600'"
class="flex-1 px-4 py-2 text-sm font-medium rounded-lg border transition-colors duration-150">
<span x-html="$icon('office-building', 'w-4 h-4 inline mr-1')"></span>
Offline (Manual)
</button>
</div>
<!-- Digital Fields -->
<div x-show="newProspect.channel === 'digital'" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">
Domain Name <span class="text-red-500">*</span>
</label>
<input type="text" x-model="newProspect.domain_name" placeholder="example.lu"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
</div>
</div>
<!-- Offline Fields -->
<div x-show="newProspect.channel === 'offline'" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">
Business Name <span class="text-red-500">*</span>
</label>
<input type="text" x-model="newProspect.business_name" placeholder="Business name"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Phone</label>
<input type="tel" x-model="newProspect.phone" placeholder="+352..."
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Email</label>
<input type="email" x-model="newProspect.email" placeholder="contact@..."
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">City</label>
<input type="text" x-model="newProspect.city" placeholder="Luxembourg"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Source</label>
<select x-model="newProspect.source"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300">
<option value="street">Street</option>
<option value="networking_event">Networking Event</option>
<option value="referral">Referral</option>
<option value="other">Other</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Notes</label>
<textarea x-model="newProspect.notes" rows="3" placeholder="Any notes about this business..."
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray dark:bg-gray-700 dark:text-gray-300"></textarea>
</div>
</div>
<!-- Footer Actions -->
<div class="flex justify-end mt-6 pt-4 border-t border-gray-200 dark:border-gray-700 space-x-3">
<button type="button" @click="showCreateModal = false"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors">
Cancel
</button>
<button type="button" @click="createProspect()"
:disabled="creating"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white 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 transition-colors duration-150">
<span x-show="!creating" x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
<span x-show="creating" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="creating ? 'Creating...' : 'Create Prospect'"></span>
</button>
</div>
{% endcall %}
{% endblock %}
{% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/prospects.js') }}"></script>
{% endblock %}