feat(hosting,prospecting): add hosting unit tests and fix template bugs
All checks were successful
All checks were successful
- 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>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{% extends "admin/base.html" %}
|
||||
{% from 'shared/macros/headers.html' import page_header %}
|
||||
{% from 'shared/macros/headers.html' import detail_page_header, section_header, tab_header %}
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/modals.html' import modal %}
|
||||
|
||||
{% block title %}Prospect Detail{% endblock %}
|
||||
|
||||
@@ -12,128 +13,132 @@
|
||||
|
||||
<div x-show="!loading && !error && prospect" class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between my-6 gap-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/admin/prospecting/prospects"
|
||||
class="p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
class="flex items-center justify-center p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
title="Back to prospects">
|
||||
<span x-html="$icon('arrow-left', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
<div class="relative hidden w-10 h-10 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-sm font-semibold text-purple-600 dark:text-purple-100"
|
||||
x-text="(prospect.business_name || prospect.domain_name)?.charAt(0).toUpperCase() || '?'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
|
||||
x-text="prospect.business_name || prospect.domain_name"></h2>
|
||||
<p class="text-sm text-gray-500" x-show="prospect.domain_name && prospect.business_name"
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400" x-show="prospect.domain_name && prospect.business_name"
|
||||
x-text="prospect.domain_name"></p>
|
||||
</div>
|
||||
<span class="px-3 py-1 text-xs font-semibold rounded-full"
|
||||
:class="prospect.channel === 'digital' ? 'text-blue-700 bg-blue-100' : 'text-purple-700 bg-purple-100'"
|
||||
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full"
|
||||
:class="prospect.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="prospect.channel"></span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Score Badge -->
|
||||
<div x-show="prospect.score" class="text-center">
|
||||
<div class="text-3xl font-bold" :class="scoreColor(prospect.score?.score)"
|
||||
x-text="prospect.score?.score"></div>
|
||||
<div class="text-xs text-gray-500 uppercase"
|
||||
x-text="prospect.score?.lead_tier?.replace('_', ' ')"></div>
|
||||
</div>
|
||||
<!-- Score Badge -->
|
||||
<div x-show="prospect.score" class="text-center">
|
||||
<div class="text-3xl font-bold" :class="scoreColor(prospect.score?.score)"
|
||||
x-text="prospect.score?.score"></div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 uppercase"
|
||||
x-text="prospect.score?.lead_tier?.replace('_', ' ')"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="flex items-center space-x-3 p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Status:</label>
|
||||
<select x-model="prospect.status" @change="updateStatus()"
|
||||
class="text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||
<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>
|
||||
<button @click="runEnrichment()" x-show="prospect.channel === 'digital'"
|
||||
class="ml-auto px-3 py-1 text-sm text-white bg-blue-600 rounded-lg hover:bg-blue-700">
|
||||
<span x-html="$icon('globe-alt', 'w-4 h-4 inline mr-1')"></span>
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-3 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-400">Status:</label>
|
||||
<select x-model="prospect.status" @change="updateStatus()"
|
||||
class="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="pending">Pending</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="contacted">Contacted</option>
|
||||
<option value="converted">Converted</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" @click="runEnrichment()" x-show="prospect.channel === 'digital'"
|
||||
class="sm:ml-auto inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-blue-600 border border-transparent rounded-lg hover:bg-blue-700 focus:outline-none">
|
||||
<span x-html="$icon('globe-alt', 'w-4 h-4 mr-2')"></span>
|
||||
Run Scan
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="border-b border-gray-200 dark:border-gray-700">
|
||||
<nav class="flex -mb-px space-x-8">
|
||||
<template x-for="tab in tabs" :key="tab.id">
|
||||
<button @click="activeTab = tab.id"
|
||||
:class="activeTab === tab.id ? 'border-purple-500 text-purple-600 dark:text-purple-400' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
||||
class="py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap"
|
||||
x-text="tab.label"></button>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
{{ tab_header([
|
||||
{'id': 'overview', 'label': 'Overview', 'icon': 'eye'},
|
||||
{'id': 'interactions', 'label': 'Interactions', 'icon': 'chat'},
|
||||
{'id': 'campaigns', 'label': 'Campaigns', 'icon': 'mail'},
|
||||
], active_var='activeTab') }}
|
||||
|
||||
<!-- Tab: Overview -->
|
||||
<div x-show="activeTab === 'overview'" class="grid gap-6 md:grid-cols-2">
|
||||
<!-- Contact Info -->
|
||||
<div class="p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-200 uppercase">Contact Info</h3>
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Contact Info', icon='phone') }}
|
||||
<template x-for="c in prospect.contacts || []" :key="c.id">
|
||||
<div class="flex items-center justify-between py-2 border-b dark:border-gray-700 last:border-0">
|
||||
<span class="text-xs text-gray-500 uppercase" x-text="c.contact_type"></span>
|
||||
<div class="flex items-center justify-between py-2 border-b border-gray-100 dark:border-gray-700 last:border-0">
|
||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase" x-text="c.contact_type"></span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300" x-text="c.value"></span>
|
||||
</div>
|
||||
</template>
|
||||
<p x-show="!prospect.contacts?.length" class="text-sm text-gray-400">No contacts found</p>
|
||||
<p x-show="!prospect.contacts?.length" class="text-sm text-gray-400 text-center py-4">No contacts found</p>
|
||||
</div>
|
||||
|
||||
<!-- Score Breakdown -->
|
||||
<div class="p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-200 uppercase">Score Breakdown</h3>
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Score Breakdown', icon='chart-bar') }}
|
||||
<template x-if="prospect.score">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">Technical Health</span>
|
||||
<span class="font-semibold" x-text="prospect.score.technical_health_score + '/40'"></span>
|
||||
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="prospect.score.technical_health_score + '/40'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">Modernity</span>
|
||||
<span class="font-semibold" x-text="prospect.score.modernity_score + '/25'"></span>
|
||||
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="prospect.score.modernity_score + '/25'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">Business Value</span>
|
||||
<span class="font-semibold" x-text="prospect.score.business_value_score + '/25'"></span>
|
||||
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="prospect.score.business_value_score + '/25'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">Engagement</span>
|
||||
<span class="font-semibold" x-text="prospect.score.engagement_score + '/10'"></span>
|
||||
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="prospect.score.engagement_score + '/10'"></span>
|
||||
</div>
|
||||
<div class="pt-3 border-t dark:border-gray-700">
|
||||
<h4 class="text-xs font-semibold text-gray-500 uppercase mb-2">Issues</h4>
|
||||
<div class="pt-3 border-t border-gray-100 dark:border-gray-700">
|
||||
<h4 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase mb-2">Issues</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template x-for="flag in prospect.score.reason_flags || []" :key="flag">
|
||||
<span class="px-2 py-1 text-xs bg-red-100 text-red-700 rounded-full dark:bg-red-900 dark:text-red-300"
|
||||
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-700 rounded-full dark:bg-red-900 dark:text-red-300"
|
||||
x-text="flag.replace('_', ' ')"></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p x-show="!prospect.score" class="text-sm text-gray-400">Not scored yet</p>
|
||||
<p x-show="!prospect.score" class="text-sm text-gray-400 text-center py-4">Not scored yet</p>
|
||||
</div>
|
||||
|
||||
<!-- Tech Profile Summary -->
|
||||
<div x-show="prospect.tech_profile" class="p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-200 uppercase">Technology</h3>
|
||||
<div x-show="prospect.tech_profile" class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Technology', icon='code') }}
|
||||
<div class="space-y-2 text-sm">
|
||||
<template x-for="[key, val] in techProfileEntries()" :key="key">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400 capitalize" x-text="key.replace('_', ' ')"></span>
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="val"></span>
|
||||
<span class="text-gray-600 dark:text-gray-400" x-text="key"></span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="val"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Performance Summary -->
|
||||
<div x-show="prospect.performance_profile" class="p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-700 dark:text-gray-200 uppercase">Performance</h3>
|
||||
<div x-show="prospect.performance_profile" class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
{{ section_header('Performance', icon='chart-bar') }}
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">Performance Score</span>
|
||||
@@ -143,11 +148,12 @@
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">Mobile Friendly</span>
|
||||
<span x-text="prospect.performance_profile?.is_mobile_friendly ? 'Yes' : 'No'"
|
||||
:class="prospect.performance_profile?.is_mobile_friendly ? 'text-green-600' : 'text-red-600'"></span>
|
||||
:class="prospect.performance_profile?.is_mobile_friendly ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'"
|
||||
class="font-medium"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">SEO Score</span>
|
||||
<span x-text="prospect.performance_profile?.seo_score ?? '—'"></span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="prospect.performance_profile?.seo_score ?? '—'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,25 +162,25 @@
|
||||
<!-- Tab: Interactions -->
|
||||
<div x-show="activeTab === 'interactions'" class="space-y-4">
|
||||
<div class="flex justify-end">
|
||||
<button @click="showInteractionModal = true"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 inline mr-1')"></span>
|
||||
<button type="button" @click="showInteractionModal = true"
|
||||
class="inline-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>
|
||||
Log Interaction
|
||||
</button>
|
||||
</div>
|
||||
<!-- Timeline -->
|
||||
<div class="space-y-4">
|
||||
<template x-for="interaction in interactions" :key="interaction.id">
|
||||
<div class="p-4 bg-white rounded-lg shadow dark:bg-gray-800 border-l-4"
|
||||
:class="interaction.outcome === 'positive' ? 'border-green-500' : interaction.outcome === 'negative' ? 'border-red-500' : 'border-gray-300'">
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800 border-l-4 transition-shadow hover:shadow-md"
|
||||
:class="interaction.outcome === 'positive' ? 'border-green-500' : interaction.outcome === 'negative' ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
||||
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400"
|
||||
x-text="interaction.interaction_type.replace('_', ' ')"></span>
|
||||
<span class="text-xs text-gray-500" x-text="new Date(interaction.created_at).toLocaleDateString()"></span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="new Date(interaction.created_at).toLocaleDateString()"></span>
|
||||
</div>
|
||||
<p x-show="interaction.subject" class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="interaction.subject"></p>
|
||||
<p x-show="interaction.notes" class="text-sm text-gray-600 dark:text-gray-400 mt-1" x-text="interaction.notes"></p>
|
||||
<p x-show="interaction.next_action" class="text-xs text-purple-600 mt-2">
|
||||
<p x-show="interaction.next_action" class="text-xs text-purple-600 dark:text-purple-400 mt-2">
|
||||
Next: <span x-text="interaction.next_action"></span>
|
||||
<span x-show="interaction.next_action_date" x-text="' — ' + interaction.next_action_date"></span>
|
||||
</p>
|
||||
@@ -187,92 +193,86 @@
|
||||
<!-- Tab: Campaigns -->
|
||||
<div x-show="activeTab === 'campaigns'" class="space-y-4">
|
||||
<div class="flex justify-end">
|
||||
<button @click="showSendCampaignModal = true"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700">
|
||||
<span x-html="$icon('mail', 'w-4 h-4 inline mr-1')"></span>
|
||||
<button type="button" @click="showSendCampaignModal = true"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-green-600 border border-transparent rounded-lg hover:bg-green-700 focus:outline-none">
|
||||
<span x-html="$icon('mail', 'w-4 h-4 mr-2')"></span>
|
||||
Send Campaign
|
||||
</button>
|
||||
</div>
|
||||
<template x-for="send in campaignSends" :key="send.id">
|
||||
<div class="p-4 bg-white rounded-lg shadow dark:bg-gray-800">
|
||||
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:shadow-md transition-shadow">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="send.rendered_subject || 'No subject'"></span>
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||
:class="send.status === 'sent' ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-600'"
|
||||
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full"
|
||||
:class="send.status === 'sent'
|
||||
? 'bg-green-100 text-green-700 dark:bg-green-700 dark:text-green-100'
|
||||
: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400'"
|
||||
x-text="send.status"></span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mt-1" x-text="send.sent_at ? new Date(send.sent_at).toLocaleString() : 'Draft'"></p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1" x-text="send.sent_at ? new Date(send.sent_at).toLocaleString() : 'Draft'"></p>
|
||||
</div>
|
||||
</template>
|
||||
<p x-show="campaignSends.length === 0" class="text-sm text-gray-400 text-center py-8">No campaigns sent yet</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interaction Modal --> {# noqa: FE-004 #}
|
||||
<div x-show="showInteractionModal" x-cloak
|
||||
class="fixed inset-0 z-30 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"
|
||||
@click.self="showInteractionModal = false">
|
||||
<div class="w-full px-6 py-4 overflow-hidden bg-white rounded-t-lg dark:bg-gray-800 sm:rounded-lg sm:m-4 sm:max-w-xl"
|
||||
@keydown.escape.window="showInteractionModal = false">
|
||||
<header class="flex justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">Log Interaction</h3>
|
||||
<button @click="showInteractionModal = false" class="text-gray-400 hover:text-gray-600">
|
||||
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</header>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Type</label>
|
||||
<select x-model="newInteraction.interaction_type"
|
||||
class="w-full mt-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||
<option value="note">Note</option>
|
||||
<option value="call">Phone Call</option>
|
||||
<option value="email_sent">Email Sent</option>
|
||||
<option value="email_received">Email Received</option>
|
||||
<option value="meeting">Meeting</option>
|
||||
<option value="visit">Visit</option>
|
||||
<option value="proposal_sent">Proposal Sent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Subject</label>
|
||||
<input type="text" x-model="newInteraction.subject"
|
||||
class="w-full mt-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Notes</label>
|
||||
<textarea x-model="newInteraction.notes" rows="3"
|
||||
class="w-full mt-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Outcome</label>
|
||||
<select x-model="newInteraction.outcome"
|
||||
class="w-full mt-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||
<option value="">Not specified</option>
|
||||
<option value="positive">Positive</option>
|
||||
<option value="neutral">Neutral</option>
|
||||
<option value="negative">Negative</option>
|
||||
<option value="no_answer">No Answer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400">Next Action</label>
|
||||
<input type="text" x-model="newInteraction.next_action" placeholder="Follow up by..."
|
||||
class="w-full mt-1 text-sm rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300">
|
||||
</div>
|
||||
<!-- Interaction Modal -->
|
||||
{% call modal('interactionModal', 'Log Interaction', show_var='showInteractionModal', size='md', show_footer=false) %}
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Type</label>
|
||||
<select x-model="newInteraction.interaction_type"
|
||||
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="note">Note</option>
|
||||
<option value="call">Phone Call</option>
|
||||
<option value="email_sent">Email Sent</option>
|
||||
<option value="email_received">Email Received</option>
|
||||
<option value="meeting">Meeting</option>
|
||||
<option value="visit">Visit</option>
|
||||
<option value="proposal_sent">Proposal Sent</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Subject</label>
|
||||
<input type="text" x-model="newInteraction.subject"
|
||||
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">Notes</label>
|
||||
<textarea x-model="newInteraction.notes" rows="3"
|
||||
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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Outcome</label>
|
||||
<select x-model="newInteraction.outcome"
|
||||
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="">Not specified</option>
|
||||
<option value="positive">Positive</option>
|
||||
<option value="neutral">Neutral</option>
|
||||
<option value="negative">Negative</option>
|
||||
<option value="no_answer">No Answer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Next Action</label>
|
||||
<input type="text" x-model="newInteraction.next_action" placeholder="Follow up by..."
|
||||
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>
|
||||
<footer class="flex justify-end mt-6 space-x-3">
|
||||
<button @click="showInteractionModal = false"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 dark:text-gray-300 dark:bg-gray-700">
|
||||
Cancel
|
||||
</button>
|
||||
<button @click="createInteraction()"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
Save
|
||||
</button>
|
||||
</footer>
|
||||
</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="showInteractionModal = 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="createInteraction()"
|
||||
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 transition-colors duration-150">
|
||||
<span x-html="$icon('check', 'w-4 h-4 mr-2')"></span>
|
||||
Save Interaction
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
|
||||
Reference in New Issue
Block a user