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

487 lines
6.5 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';
const route = useRoute();
const router = useRouter();
const id = computed(() => (typeof route.params.id === 'string' ? route.params.id : ''));
const isNew = computed(() => route.name === 'company-new');
const loading = ref(false);
const saving = ref(false);
const error = ref('');
const name = ref('');
const nationalId = ref('');
const phone = ref('');
const email = ref('');
const website = ref('');
const address = ref('');
const notes = ref('');
const customFields = ref<Record<string, unknown>>({});
function fill(c: CompanyDto) {
name.value = c.name;
nationalId.value = c.nationalId ?? '';
phone.value = c.phone ?? '';
email.value = c.email ?? '';
website.value = c.website ?? '';
address.value = c.address ?? '';
notes.value = c.notes ?? '';
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,
nationalId: nationalId.value || undefined,
phone: phone.value || undefined,
email: email.value || undefined,
website: website.value || undefined,
address: address.value || undefined,
notes: notes.value || undefined,
customFields: customFields.value,
};
}
async function submit() {
saving.value = true;
error.value = '';
try {
if (isNew.value) {
const { data } = await api.post<CompanyDto>('/companies', payload());
await router.replace({ name: 'company-detail', params: { id: data.id } });
} else {
await api.patch(`/companies/${id.value}`, payload());
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 = 'حذف ناموفق بود';
}
}
onMounted(load);
</script>
<template>
<section class="page">
<header class="page-head">
<div>
<p class="eyebrow">شرکت</p>
<h1>{{ isNew ? 'شرکت جدید' : 'ویرایش شرکت' }}</h1>
</div>
<RouterLink class="btn btn-ghost" :to="{ name: 'contacts', query: { tab: 'companies' } }">
بازگشت
</RouterLink>
</header>
<p v-if="loading" class="muted">در حال بارگذاری…</p>
<form v-else class="form-card" @submit.prevent="submit">
<div class="grid">
<div class="field wide">
<label for="name">نام شرکت</label>
<input id="name" v-model="name" required minlength="2" />
</div>
<div class="field">
<label for="nationalId">شناسه ملی</label>
<input id="nationalId" v-model="nationalId" />
</div>
<div class="field">
<label for="phone">تلفن</label>
<input id="phone" v-model="phone" inputmode="tel" />
</div>
<div class="field">
<label for="email">ایمیل</label>
<input id="email" v-model="email" type="email" />
</div>
<div class="field">
<label for="website">وب‌سایت</label>
<input id="website" v-model="website" />
</div>
<div class="field wide">
<label for="address">آدرس</label>
<input id="address" v-model="address" />
</div>
<div class="field wide">
<label for="notes">یادداشت</label>
<textarea id="notes" v-model="notes" rows="4" />
</div>
</div>
<CustomFieldsSection entity-type="company" v-model="customFields" />
<EntityTimeline
v-if="!isNew && id"
entity-type="company"
:entity-id="id"
/>
<p v-if="error" class="error">{{ error }}</p>
<div class="form-actions">
<button class="btn" type="submit" :disabled="saving">
{{ saving ? 'در حال ذخیره…' : 'ذخیره' }}
</button>
<button
v-if="!isNew"
class="btn btn-danger"
type="button"
:disabled="saving"
@click="remove"
>
حذف
</button>
</div>
</form>
</section>
</template>
<style scoped>
.page {
display: grid;
gap: 1.1rem;
max-width: 820px;
}
.page-head {
display: flex;
justify-content: space-between;
gap: 1rem;
align-items: flex-start;
}
.eyebrow {
margin: 0;
color: var(--accent);
font-weight: 700;
}
h1 {
margin: 0.2rem 0 0;
font-size: 1.7rem;
}
.muted {
color: var(--muted);
}
.form-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.2rem;
display: grid;
gap: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.9rem;
}
.wide {
grid-column: 1 / -1;
}
textarea {
border: 1px solid var(--line);
border-radius: 12px;
padding: 0.8rem 0.95rem;
background: #fff;
font: inherit;
width: 100%;
}
.form-actions {
display: flex;
gap: 0.6rem;
flex-wrap: wrap;
}
@media (max-width: 700px) {
.grid {
grid-template-columns: 1fr;
}
}
</style>