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

430 lines
13 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import type { CompanyDto, CreateCompanyRequest } from '@mento/shared';
import CustomFieldsSection from '@/components/CustomFieldsSection.vue';
import EntityTimeline from '@/components/EntityTimeline.vue';
import RelatedTasksPanel from '@/components/routines/RelatedTasksPanel.vue';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import { formatLocalDateInput, parseLocalInput } from '@/lib/jalali';
import {
UiButton,
UiCard,
UiField,
UiInput,
UiPageHeader,
UiTextarea,
} from '@/components/ui';
import UiIcon from '@/components/UiIcon.vue';
const props = withDefaults(
defineProps<{
embedded?: boolean;
entityId?: string;
}>(),
{ embedded: false },
);
const emit = defineEmits<{ saved: []; cancel: [] }>();
const route = useRoute();
const router = useRouter();
const id = computed(() => {
if (props.embedded) return props.entityId ?? '';
return typeof route.params.id === 'string' ? route.params.id : '';
});
const isNew = computed(() => (props.embedded ? !props.entityId : route.name === 'company-new'));
const pageTitle = computed(() => (isNew.value ? 'شرکت جدید' : 'ویرایش شرکت'));
const loading = ref(false);
const saving = ref(false);
const error = ref('');
const name = ref('');
const code = ref('');
const sourceRef = ref('');
const importSource = ref('manual');
const nationalId = ref('');
const economicCode = ref('');
const registrationNumber = ref('');
const phone = ref('');
const otherPhones = ref('');
const fax = ref('');
const email = ref('');
const otherEmails = ref('');
const website = ref('');
const province = ref('');
const city = ref('');
const postalCode = ref('');
const address = ref('');
const groupName = ref('');
const registeredAt = ref('');
const notes = ref('');
const tagsRaw = ref('');
const customFields = ref<Record<string, unknown>>({});
function parseTags(raw: string): string[] {
return [
...new Set(
raw
.split(/[,،#\n]/)
.map((t) => t.trim())
.filter(Boolean),
),
].slice(0, 40);
}
function fill(c: CompanyDto) {
name.value = c.name;
code.value = c.code ?? '';
sourceRef.value = c.sourceRef ?? '';
importSource.value = c.importSource || 'manual';
nationalId.value = c.nationalId ?? '';
economicCode.value = c.economicCode ?? '';
registrationNumber.value = c.registrationNumber ?? '';
phone.value = c.phone ?? '';
otherPhones.value = c.otherPhones ?? '';
fax.value = c.fax ?? '';
email.value = c.email ?? '';
otherEmails.value = c.otherEmails ?? '';
website.value = c.website ?? '';
province.value = c.province ?? '';
city.value = c.city ?? '';
postalCode.value = c.postalCode ?? '';
address.value = c.address ?? '';
groupName.value = c.groupName ?? '';
registeredAt.value = c.registeredAt ? formatLocalDateInput(new Date(c.registeredAt)) : '';
notes.value = c.notes ?? '';
tagsRaw.value = (c.tags ?? []).join('، ');
customFields.value = { ...(c.customFields ?? {}) };
}
async function load() {
if (isNew.value) return;
loading.value = true;
error.value = '';
try {
const { data } = await api.get<CompanyDto>(`/companies/${id.value}`);
fill(data);
} catch {
error.value = 'بارگذاری شرکت ناموفق بود.';
} finally {
loading.value = false;
}
}
function payload(): CreateCompanyRequest {
return {
name: name.value,
code: code.value || null,
sourceRef: sourceRef.value || null,
...(sourceRef.value && importSource.value === 'manual' ? { importSource: 'didar' } : {}),
nationalId: nationalId.value || undefined,
economicCode: economicCode.value || null,
registrationNumber: registrationNumber.value || null,
phone: phone.value || undefined,
otherPhones: otherPhones.value || null,
fax: fax.value || null,
email: email.value || undefined,
otherEmails: otherEmails.value || null,
website: website.value || undefined,
province: province.value || null,
city: city.value || null,
postalCode: postalCode.value || null,
address: address.value || undefined,
groupName: groupName.value || null,
registeredAt: parseLocalInput(registeredAt.value)?.toISOString() ?? null,
notes: notes.value || undefined,
tags: parseTags(tagsRaw.value),
customFields: customFields.value,
};
}
async function submit() {
saving.value = true;
error.value = '';
try {
if (isNew.value) {
const { data } = await api.post<CompanyDto>('/companies', payload());
if (props.embedded) {
emit('saved');
return;
}
await router.replace({ name: 'company-detail', params: { id: data.id } });
} else {
await api.patch(`/companies/${id.value}`, payload());
if (props.embedded) {
emit('saved');
return;
}
await router.push({ name: 'contacts', query: { tab: 'companies' } });
}
} catch (e) {
if (axios.isAxiosError(e)) {
if (!e.response) {
error.value = 'اتصال به سرور برقرار نشد.';
} else {
const msg = (e.response.data as { message?: string | string[] })?.message;
error.value = Array.isArray(msg) ? msg.join('، ') : (msg ?? 'ذخیره ناموفق بود');
}
} else {
error.value = 'ذخیره ناموفق بود';
}
} finally {
saving.value = false;
}
}
async function remove() {
if (!id.value || !confirm('این شرکت حذف شود؟ افراد متصل بدون شرکت می‌مانند.')) return;
try {
await api.delete(`/companies/${id.value}`);
await router.push({ name: 'contacts', query: { tab: 'companies' } });
} catch {
error.value = 'حذف ناموفق بود';
}
}
function cancel() {
if (props.embedded) {
emit('cancel');
return;
}
void router.push({ name: 'contacts', query: { tab: 'companies' } });
}
onMounted(load);
</script>
<template>
<section class="crm-page company-form-page" :class="{ embedded }" dir="rtl">
<UiPageHeader
v-if="!embedded"
:title="pageTitle"
description="ثبت و ویرایش مشخصات حقوقی، اطلاعات تماس و فیلدهای سفارشی شرکت"
>
<template #breadcrumb>
<RouterLink :to="{ name: 'contacts', query: { tab: 'companies' } }" class="breadcrumb-link">
شرکت‌ها
</RouterLink>
<span class="breadcrumb-separator">/</span>
<span>{{ pageTitle }}</span>
</template>
<template #actions>
<UiButton :loading="saving" @click="submit">
<template #icon>
<UiIcon name="check-plain" />
</template>
ذخیره
</UiButton>
<UiButton variant="ghost" :disabled="saving" @click="cancel">
انصراف
</UiButton>
<UiButton v-if="!isNew" variant="danger" :disabled="saving" @click="remove">
<template #icon>
<UiIcon name="trash" />
</template>
حذف
</UiButton>
</template>
</UiPageHeader>
<div v-if="error" class="crm-alert crm-alert-danger">{{ error }}</div>
<form class="crm-form" @submit.prevent="submit">
<UiCard title="اطلاعات اصلی شرکت" class="section-card">
<div class="crm-form-grid-2">
<UiField label="نام شرکت" required class="col-span-2">
<UiInput
id="name"
v-model="name"
required
minlength="2"
placeholder="مثال: داده‌پردازی سپهر"
/>
</UiField>
<UiField label="کد شرکت">
<UiInput id="code" v-model="code" placeholder="کد داخلی" dir="ltr" />
</UiField>
<UiField label="کد دیدار شرکت">
<UiInput id="sourceRef" v-model="sourceRef" placeholder="مثال: 5277" dir="ltr" />
</UiField>
<UiField label="گروه بندی شرکت">
<UiInput id="groupName" v-model="groupName" placeholder="مثال: همکار، عمده‌فروش" />
</UiField>
<UiField label="تاریخ ثبت شرکت">
<JalaliDateInput id="registeredAt" v-model="registeredAt" />
</UiField>
<UiField label="برچسب‌ها" class="col-span-2">
<UiInput
id="company-tags"
v-model="tagsRaw"
placeholder="شرکتی، VIP (با کاما جدا کنید)"
/>
</UiField>
<UiField label="توضیحات شرکت" class="col-span-2">
<UiTextarea
id="notes"
v-model="notes"
:rows="4"
placeholder="توضیحات مهم، حوزه‌های همکاری..."
/>
</UiField>
</div>
</UiCard>
<UiCard title="اطلاعات تماس و آدرس" class="section-card">
<div class="crm-form-grid-2">
<UiField label="تلفن شرکت">
<UiInput id="phone" v-model="phone" type="tel" placeholder="۰۲۱..." dir="ltr" />
</UiField>
<UiField label="تلفن های دیگر شرکت">
<UiInput id="otherPhones" v-model="otherPhones" placeholder="با کاما جدا کنید" dir="ltr" />
</UiField>
<UiField label="ایمیل شرکت">
<UiInput
id="email"
v-model="email"
type="email"
placeholder="info@company.ir"
dir="ltr"
/>
</UiField>
<UiField label="ایمیل های دیگر شرکت">
<UiInput id="otherEmails" v-model="otherEmails" placeholder="با کاما جدا کنید" dir="ltr" />
</UiField>
<UiField label="شماره فکس">
<UiInput id="fax" v-model="fax" type="tel" dir="ltr" />
</UiField>
<UiField label="سایت های شرکت">
<UiInput id="website" v-model="website" placeholder="https://..." dir="ltr" />
</UiField>
<UiField label="استان">
<UiInput id="province" v-model="province" placeholder="مثال: تهران" />
</UiField>
<UiField label="شهرستان">
<UiInput id="city" v-model="city" placeholder="مثال: تهران" />
</UiField>
<UiField label="کد پستی">
<UiInput id="postalCode" v-model="postalCode" dir="ltr" />
</UiField>
<UiField label="آدرس" class="col-span-2">
<UiInput id="address" v-model="address" placeholder="نشانی، طبقه، پلاک..." />
</UiField>
</div>
</UiCard>
<UiCard title="اطلاعات حقوقی و ثبتی" class="section-card">
<div class="crm-form-grid-2">
<UiField label="شناسه ملی">
<UiInput id="nationalId" v-model="nationalId" placeholder="شناسه ملی ۱۱ رقمی" dir="ltr" />
</UiField>
<UiField label="شماره اقتصادی">
<UiInput id="economicCode" v-model="economicCode" dir="ltr" />
</UiField>
<UiField label="شماره ثبت">
<UiInput id="registrationNumber" v-model="registrationNumber" dir="ltr" />
</UiField>
</div>
</UiCard>
<CustomFieldsSection entity-type="company" v-model="customFields" />
<RelatedTasksPanel v-if="!isNew && id" :entity="{ type: 'company', id, label: name }" />
<EntityTimeline v-if="!isNew && id" entity-type="company" :entity-id="id" />
<div class="form-bottom-actions">
<UiButton type="submit" :loading="saving">
<template #icon>
<UiIcon name="check-plain" />
</template>
ذخیره اطلاعات
</UiButton>
<UiButton variant="ghost" type="button" :disabled="saving" @click="cancel">
انصراف
</UiButton>
<UiButton v-if="!isNew" variant="danger" type="button" :disabled="saving" @click="remove">
حذف شرکت
</UiButton>
</div>
</form>
</section>
</template>
<style scoped>
.company-form-page {
display: flex;
flex-direction: column;
gap: var(--sp-6);
width: 100%;
max-width: 100%;
}
.company-form-page.embedded {
max-width: none;
gap: var(--sp-4);
}
.embedded-actions {
display: flex;
gap: var(--sp-2);
}
.breadcrumb-link {
color: var(--color-text-secondary);
text-decoration: none;
}
.breadcrumb-link:hover {
color: var(--color-brand-600);
}
.breadcrumb-separator {
margin: 0 var(--sp-2);
color: var(--color-text-muted);
}
.section-card {
padding: var(--sp-6);
}
.col-span-2 {
grid-column: span 2;
}
.form-bottom-actions {
display: flex;
gap: var(--sp-3);
justify-content: flex-end;
margin-top: var(--sp-4);
}
@media (max-width: 640px) {
.col-span-2 {
grid-column: span 1;
}
.form-bottom-actions {
flex-direction: column;
}
}
</style>