Files
CRM_Montzar/apps/web/src/views/LeadFormsSettingsView.vue
T

279 lines
7.3 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { RouterLink, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import {
UiBadge,
UiButton,
UiCard,
UiEmptyState,
UiPageHeader,
} from '@/components/ui';
import UiIcon from '@/components/UiIcon.vue';
import type { LeadFormDto } from '@mento/shared';
import { toFaDigits } from '@/lib/jalali';
const router = useRouter();
const loading = ref(false);
const saving = ref(false);
const error = ref('');
const forms = ref<LeadFormDto[]>([]);
async function load() {
loading.value = true;
error.value = '';
try {
const { data } = await api.get<LeadFormDto[]>('/lead-forms');
forms.value = data;
} catch (e) {
error.value = axios.isAxiosError(e) && !e.response
? 'اتصال به API برقرار نشد.'
: 'بارگذاری فرم‌ها ناموفق بود.';
} finally {
loading.value = false;
}
}
function nextFormName() {
const taken = new Set(forms.value.map((f) => f.name.trim()));
let n = forms.value.length + 1;
while (taken.has(`فرم جدید ${toFaDigits(n)}`)) n += 1;
return `فرم جدید ${toFaDigits(n)}`;
}
async function createForm() {
saving.value = true;
error.value = '';
try {
const { data } = await api.post<LeadFormDto>('/lead-forms', {
name: nextFormName(),
source: 'وب‌سایت',
createLead: true,
createPerson: false,
createDeal: false,
duplicateMode: 'update',
});
await router.push({ name: 'lead-form-builder', params: { id: data.id }, query: { new: '1' } });
} catch {
error.value = 'ساخت فرم ناموفق بود.';
} finally {
saving.value = false;
}
}
async function removeForm(form: LeadFormDto) {
if (!confirm(`فرم «${form.name}» حذف شود؟`)) return;
await api.delete(`/lead-forms/${form.id}`);
await load();
}
function publicUrl(form: LeadFormDto) {
const base = String(import.meta.env.VITE_LEAD_FORM_PUBLIC_BASE || '').replace(/\/$/, '')
|| window.location.origin;
return `${base}${form.publicPath}`;
}
async function copyPublishLink(form: LeadFormDto) {
await navigator.clipboard.writeText(publicUrl(form));
}
const countLabel = computed(() => toFaDigits(forms.value.length));
onMounted(load);
</script>
<template>
<section class="crm-page lead-forms-page" dir="rtl">
<UiPageHeader
title="فرم‌ساز وب"
description="فرم‌های آنلاین بسازید، در وب‌سایت منتشر کنید و لیدها را مستقیم در CRM دریافت کنید."
>
<template #actions>
<UiButton :loading="saving" @click="createForm">
<template #icon>
<UiIcon name="plus" />
</template>
فرم جدید
</UiButton>
</template>
</UiPageHeader>
<div v-if="error" class="crm-alert crm-alert-danger">{{ error }}</div>
<div v-else-if="!loading && forms.length" class="forms-summary">
<span class="forms-count-badge">{{ countLabel }} فرم ساخته شده</span>
</div>
<UiEmptyState
v-if="!loading && !forms.length"
title="هنوز فرمی نساخته‌اید"
description="با ساخت اولین فرم، می‌توانید در وب‌سایت یا شبکه‌های اجتماعی سرنخ و مشتری جذب کنید."
icon="document"
>
<template #action>
<UiButton :loading="saving" @click="createForm">
<template #icon>
<UiIcon name="plus" />
</template>
ساخت اولین فرم
</UiButton>
</template>
</UiEmptyState>
<div v-else class="forms-list">
<UiCard v-for="form in forms" :key="form.id" class="form-card">
<div class="form-info">
<div class="form-title-row">
<strong class="form-name">{{ form.name }}</strong>
<UiBadge :variant="form.isActive ? 'success' : 'neutral'">
{{ form.isActive ? 'فعال' : 'غیرفعال' }}
</UiBadge>
</div>
<p class="form-meta">
<span>{{ toFaDigits(form.fields.length) }} فیلد سفارشی</span>
<span class="meta-dot">·</span>
<span>{{ toFaDigits(form.submitCount ?? form.submissionCount ?? 0) }} ارسال</span>
<span class="meta-dot">·</span>
<span>{{ toFaDigits(form.leadCreateCount ?? 0) }} لید</span>
<span v-if="form.source" class="meta-dot">·</span>
<span v-if="form.source">منبع: {{ form.source }}</span>
</p>
</div>
<div class="form-actions">
<RouterLink :to="{ name: 'lead-form-builder', params: { id: form.id } }">
<UiButton variant="secondary" size="sm">
<template #icon>
<UiIcon name="pencil" />
</template>
ویرایش فیلدها
</UiButton>
</RouterLink>
<UiButton variant="secondary" size="sm" @click="copyPublishLink(form)">
<template #icon>
<UiIcon name="megaphone" />
</template>
کپی لینک انتشار
</UiButton>
<a :href="form.publicPath" target="_blank" rel="noopener" class="preview-link">
<UiButton variant="ghost" size="sm">
<template #icon>
<UiIcon name="eye" />
</template>
پیش‌نمایش
</UiButton>
</a>
<UiButton variant="danger" size="sm" @click="removeForm(form)">
<template #icon>
<UiIcon name="trash" />
</template>
حذف
</UiButton>
</div>
</UiCard>
</div>
</section>
</template>
<style scoped>
.lead-forms-page {
display: flex;
flex-direction: column;
gap: var(--sp-6);
width: 100%;
max-width: 100%;
}
.forms-summary {
display: flex;
align-items: center;
}
.forms-count-badge {
font-size: var(--fs-sm);
color: var(--color-text-secondary);
background: var(--color-surface-soft);
padding: var(--sp-1) var(--sp-3);
border-radius: var(--radius-full);
border: 1px solid var(--color-border);
}
.forms-list {
display: flex;
flex-direction: column;
gap: var(--sp-4);
}
.form-card {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--sp-4);
padding: var(--sp-4) var(--sp-5);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-card:hover {
border-color: var(--color-brand-300, #5eead4);
box-shadow: var(--shadow-sm);
}
.form-info {
display: flex;
flex-direction: column;
gap: var(--sp-1);
}
.form-title-row {
display: flex;
align-items: center;
gap: var(--sp-3);
}
.form-name {
font-size: var(--fs-md);
font-weight: 700;
color: var(--color-text-primary);
}
.form-meta {
margin: 0;
font-size: var(--fs-xs);
color: var(--color-text-secondary);
display: flex;
align-items: center;
gap: var(--sp-2);
}
.meta-dot {
color: var(--color-text-muted);
}
.form-actions {
display: flex;
align-items: center;
gap: var(--sp-2);
flex-wrap: wrap;
}
.preview-link {
text-decoration: none;
}
@media (max-width: 640px) {
.form-card {
flex-direction: column;
align-items: flex-start;
}
.form-actions {
width: 100%;
justify-content: flex-end;
}
}
</style>