Files
orion/app/modules/prospecting/templates/prospecting/admin/capture.html
Samir Boulahtit 2287f4597d
All checks were successful
CI / ruff (push) Successful in 10s
CI / pytest (push) Successful in 48m48s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Successful in 38s
CI / deploy (push) Successful in 51s
feat(hosting,prospecting): add hosting unit tests and fix template bugs
- Add 55 unit tests for hosting module (hosted site service, client
  service service, stats service) with full fixture setup
- Fix table_empty_state macro: add x_message param for dynamic Alpine.js
  expressions rendered via x-text instead of server-side Jinja
- Fix hosting templates (sites, clients) using message= with Alpine
  expressions that rendered as literal text
- Fix prospecting templates (leads, scan-jobs, prospects) using
  nonexistent subtitle= param, migrated to x_message=
- Align hosting and prospecting admin templates with shared design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:18:26 +01:00

153 lines
9.6 KiB
HTML

{% extends "admin/base.html" %}
{% from 'shared/macros/headers.html' import page_header %}
{% block title %}Quick Capture{% endblock %}
{% block alpine_data %}quickCapture(){% endblock %}
{% block content %}
{{ page_header('Quick Capture', subtitle='Capture offline prospects on the go') }}
<!-- Success Message -->
<div x-show="saved" x-transition
class="mb-4 p-4 bg-green-100 text-green-700 rounded-lg dark:bg-green-900 dark:text-green-300 max-w-2xl mx-auto">
<span x-html="$icon('check-circle', 'w-5 h-5 inline mr-2')"></span>
<span x-text="'Prospect saved: ' + lastSaved"></span>
</div>
<div class="max-w-2xl mx-auto">
<div class="px-4 py-5 sm:p-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<!-- Business Name -->
<div class="mb-5">
<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="form.business_name" required autofocus
class="w-full px-3 py-2.5 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"
placeholder="e.g. Boulangerie Schmidt">
</div>
<!-- Phone + Email Row -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-5">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Phone</label>
<input type="tel" x-model="form.phone" placeholder="+352..."
class="w-full px-3 py-2.5 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="form.email" placeholder="contact@..."
class="w-full px-3 py-2.5 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>
<!-- Address -->
<div class="mb-5">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Address</label>
<input type="text" x-model="form.address" placeholder="Street and number"
class="w-full px-3 py-2.5 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>
<!-- City + Postal Code -->
<div class="grid grid-cols-2 gap-4 mb-5">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">City</label>
<input type="text" x-model="form.city" placeholder="Luxembourg"
class="w-full px-3 py-2.5 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">Postal Code</label>
<input type="text" x-model="form.postal_code" placeholder="L-1234"
class="w-full px-3 py-2.5 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>
<!-- Source -->
<div class="mb-5">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Source</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<template x-for="src in sources" :key="src.value">
<button type="button" @click="form.source = src.value"
:class="form.source === src.value
? '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="px-3 py-2 text-sm font-medium rounded-lg border transition-colors duration-150"
x-text="src.label"></button>
</template>
</div>
</div>
<!-- Tags -->
<div class="mb-5">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">Tags</label>
<div class="flex flex-wrap gap-2">
<template x-for="tag in availableTags" :key="tag">
<button type="button" @click="toggleTag(tag)"
:class="form.tags.includes(tag)
? 'bg-purple-100 text-purple-700 border-purple-300 dark:bg-purple-900 dark:text-purple-300 dark:border-purple-700'
: 'bg-gray-100 text-gray-600 border-gray-200 dark:bg-gray-700 dark:text-gray-400 dark:border-gray-600 hover:bg-gray-200 dark:hover:bg-gray-600'"
class="px-3 py-1.5 text-xs font-medium rounded-full border transition-colors duration-150"
x-text="tag"></button>
</template>
</div>
</div>
<!-- Notes -->
<div class="mb-5">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Notes</label>
<textarea x-model="form.notes" rows="3" placeholder="Quick notes..."
class="w-full px-3 py-2.5 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>
<!-- Location + Submit Row -->
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
<button type="button" @click="getLocation()"
:disabled="gettingLocation"
class="inline-flex items-center justify-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-300 transition-colors duration-150 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:shadow-outline-gray disabled:opacity-50">
<span x-html="$icon('location-marker', 'w-4 h-4 mr-2')"></span>
<span x-text="gettingLocation ? 'Getting location...' : (form.location_lat ? 'Location saved' : 'Get Location')"></span>
</button>
<span x-show="form.location_lat" class="text-xs text-green-600 dark:text-green-400 self-center">
<span x-text="form.location_lat?.toFixed(4)"></span>, <span x-text="form.location_lng?.toFixed(4)"></span>
</span>
<div class="sm:ml-auto">
<button type="button" @click="submitCapture()"
:disabled="submitting || !form.business_name"
class="w-full sm:w-auto inline-flex items-center justify-center px-6 py-2.5 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="!submitting" x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
<span x-show="submitting" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
<span x-text="submitting ? 'Saving...' : 'Save & Capture Next'"></span>
</button>
</div>
</div>
</div>
<!-- Recent Captures -->
<div x-show="recentCaptures.length > 0" class="mt-6">
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-3">
Recent Captures (<span x-text="recentCaptures.length"></span>)
</h3>
<div class="space-y-2">
<template x-for="cap in recentCaptures" :key="cap.id">
<div class="flex items-center justify-between p-3 bg-white rounded-lg shadow-xs dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<div class="flex items-center space-x-3">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-purple-100 dark:bg-purple-900">
<span class="text-sm font-semibold text-purple-600 dark:text-purple-300"
x-text="cap.business_name.charAt(0).toUpperCase()"></span>
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="cap.business_name"></span>
</div>
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="cap.city || cap.source"></span>
</div>
</template>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/capture.js') }}"></script>
{% endblock %}